Hi Francesco,

The case of Mr Bellini is very interesting, because in my view,
informations came from both side:
- the emails MX come from the IT university team (Group)
groups['University A'].mx="@university_a.net"
- the email identifier come from the Identity username (User) username="bellini"
Suppose that a resource is joined with the group "University A" and
"University B". So without human access to attributes, how can that
information be propagated to emails servers scripts to produce 2
different mailboxes ?
Where should I put this derived attribute: mail=username+mx, and when
did it be evaluated for use in a provisioning process of the email
resource ?

I'm sorry to insist, but if I had found more explanation on this use
case in the documentation, I wouldn't have brought it up again.

Best regards
Stéphane POPOFF

Le jeu. 15 janv. 2026 à 13:08, Francesco Chicchiriccò
<[email protected]> a écrit :
>
> Hi,
> I saw SYNCOPE-1944 but I had to fix the content.
>
> Let me repeat again that the purpose of Type Extension is not to copy Group's 
> attributes to members (Users or Any Objects) but to define additional 
> attributes that members will be able to hold, once a membership exists.
>
> With reference to the example given in
>
> https://syncope.apache.org/docs/4.0/reference-guide.html#type-extensions
>
> The user bellini has two email addresses, one for Membership with GROUP for 
> University A and one for Membership with GROUP for University B, but such 
> emails addresses had to be provided when creating or updating the user 
> bellini and are related to bellini itself, not the universities.
>
> The example is supposing that the Type Extensions for both university groups 
> are allowing the same email address for their members.
>
>
> Besides this, in case you need to consider group attributes (not membership 
> attributes) for Resource Mapping, you can use the syntax as explained by
>
> https://syncope.apache.org/docs/4.0/reference-guide.html#mapping
>
> e.g.
>
> groups[groupName].schema
>
> Hope this clarifies.
> Regards.
>
> On 15/01/26 08:45, Francesco Chicchiriccò wrote:
> > Hi,
> > by looking at the code, I think that derived attributes are not working 
> > correctly for memberships (as driven by group's type extension): please go 
> > ahead and open an issue on JIRA.
> >
> > Regarding plain attributes for memberships, instead, please consider that 
> > (a) Syncope will not store not return attributes with empty value and (b) 
> > it is up to the caller to provide such values when creating or updating the 
> > membership.
> >
> > Regards.
> >
> > On 14/01/26 12:51, Stéphane Popoff wrote:
> >> Hi Francesco,
> >>
> >> I see an explanation about Type Extensions in the reference guide. I
> >> created  a group appAdmin with Type Extension [1] but when applying
> >> the group to a user some attributes fail or don't appear [2].
> >> My question is how the Type Extension attribute can be evaluated when
> >> a group is associated with a user ? The case is the derived attribute:
> >> login = "'adm'+personId".
> >>
> >> Best regards,
> >> Stéphane POPOFF
> >>
> >> [1] {
> >>      "_class": "org.apache.syncope.common.lib.to.GroupTO",
> >>      "key": "019bbbf3-7b9f-7b6b-aa1c-6a210de2f615",
> >>      "type": "GROUP",
> >>      "realm": "/",
> >>      "name": "appAdmin",
> >>      "creator": "admin",
> >>      "creationDate": "2026-01-14T10:00:55.025528Z",
> >>      "creationContext": "REST",
> >>      "lastModifier": "admin",
> >>      "lastChangeDate": "2026-01-14T11:30:03.36464Z",
> >>      "lastChangeContext": "REST",
> >>      "dynRealms": [],
> >>      "status": null,
> >>      "auxClasses": [
> >>          "AdminGroup"
> >>      ],
> >>      "plainAttrs": [
> >>          {
> >>              "schema": "projects",
> >>              "values": [
> >>                  "projet1",
> >>                  "projet2"
> >>              ]
> >>          }
> >>      ],
> >>      "derAttrs": [
> >>          {
> >>              "schema": "login",
> >>              "values": [
> >>                  "adm-"
> >>              ]
> >>          }
> >>      ],
> >>      "resources": [
> >>          "admin"
> >>      ],
> >>      "relationships": [],
> >>      "userOwner": null,
> >>      "groupOwner": null,
> >>      "udynMembershipCond": null,
> >>      "staticUserMembershipCount": 4,
> >>      "dynamicUserMembershipCount": 0,
> >>      "staticAnyObjectMembershipCount": 0,
> >>      "dynamicAnyObjectMembershipCount": 0,
> >>      "adynMembershipConds": {},
> >>      "typeExtensions": [
> >>          {
> >>              "anyType": "USER",
> >>              "auxClasses": [
> >>                  "AdminGroup"
> >>              ]
> >>          }
> >>      ]
> >> }
> >> [2] {
> >>      "_class": "org.apache.syncope.common.lib.to.UserTO",
> >>      "key": "019bbbf4-232d-70e1-b940-1f091e4be34c",
> >>      "type": "USER",
> >>      "realm": "/",
> >>      "username": "tutu",
> >>      "creator": "admin",
> >>      "creationDate": "2026-01-14T10:01:37.940487Z",
> >>      "creationContext": "PULL Task 019bbbf1-7f0f-7935-b0c8-534be438ec42
> >> 'chargePersons'",
> >>      "lastModifier": "tutu",
> >>      "lastChangeDate": "2026-01-14T11:33:16.231655284Z",
> >>      "lastChangeContext": "REST",
> >>      "dynRealms": [],
> >>      "status": "active",
> >>      "auxClasses": [],
> >>      "plainAttrs": [
> >>          {
> >>              "schema": "nom",
> >>              "values": [
> >>                  "nom de tutu"
> >>              ]
> >>          },
> >>          {
> >>              "schema": "operationalUnit",
> >>              "values": [
> >>                  "boulot"
> >>              ]
> >>          },
> >>          {
> >>              "schema": "personId",
> >>              "values": [
> >>                  "tutu"
> >>              ]
> >>          },
> >>          {
> >>              "schema": "prenom",
> >>              "values": [
> >>                  "prénom de tutu"
> >>              ]
> >>          }
> >>      ],
> >>      "derAttrs": [],
> >>      "resources": [
> >>          "admin"
> >>      ],
> >>      "relationships": [],
> >>      "password": null,
> >>      "token": null,
> >>      "tokenExpireTime": null,
> >>      "lastLoginDate": "2026-01-14T11:32:54.613387809Z",
> >>      "changePwdDate": "2026-01-14T11:32:19.490419976Z",
> >>      "failedLogins": 0,
> >>      "securityQuestion": null,
> >>      "securityAnswer": null,
> >>      "suspended": false,
> >>      "mustChangePassword": false,
> >>      "memberships": [
> >>          {
> >>              "groupKey": "019bbbf3-7b9f-7b6b-aa1c-6a210de2f615",
> >>              "groupName": "appAdmin",
> >>              "plainAttrs": [],  //where are projects values ?
> >>              "derAttrs": [
> >>                  {
> >>                      "schema": "login",
> >>                      "values": [
> >>                          "adm-"    //should be adm-tutu
> >>                      ]
> >>                  }
> >>              ]
> >>          }
> >>      ],
> >>      "dynMemberships": [],
> >>      "roles": [],
> >>      "dynRoles": [],
> >>      "linkedAccounts": [],
> >>      "delegatingDelegations": [],
> >>      "delegatedDelegations": []
> >> }
> >>
> >> Le ven. 9 janv. 2026 à 08:17, Francesco Chicchiriccò
> >> <[email protected]> a écrit :
> >>> Hi,
> >>> I wouldn't say that Syncope is based on accounts, rather the opposite.
> >>>
> >>> I would also designed differently your use case, based on Syncope 
> >>> features:
> >>>
> >>> * no need for PERSON, just stick to USER
> >>> *     define a Group for each application, where:
> >>>      * the Type Extension for the Group contains all user attributes 
> >>> required by the application
> >>>      * the External Resource is assigned to the Group, not the single 
> >>> User, where the mapping of such External Resource is based on both USER 
> >>> and Membership attributes (as defined by the Type Extension)
> >>>
> >>> Now, when a user needs to be assigned to an application, a membership 
> >>> will be created and the given user will be propagated to the Resource 
> >>> assigned to the Group for the membership.
> >>> No need for any code customization.
> >>>
> >>> HTH
> >>> Regards.
> >>>
> >>> On 08/01/26 12:42, Stéphane Popoff wrote:
> >>>> Hi Francesco,
> >>>>
> >>>> My english is also poor, so that explains some misunderstanding.
> >>>>
> >>>> Basically one PERSON instance like 'titi' (personId="titi") may have
> >>>> many accounts (User instance) like: 'usr-titi', 'adm-titi' ...
> >>>> PERSON titi have their own lifecycle, managed by an external resource
> >>>> like an HR System (see https://github.com/spopoff/persons).
> >>>> So when an event of create / update / delete appears on 'titi', a
> >>>> "business" logic should apply rules that modify accounts.
> >>>> For example:
> >>>> - When 'toto' is created, we creates an account 'usr-toto' with
> >>>> minimal access to some applications
> >>>> - When 'toto' is updated and this operationalUnit attribute equals
> >>>> "SOC", we creates a new account 'adm-toto' with privilege access
> >>>> - When 'toto' quit, is deleted, all the associate accounts are deleted
> >>>> (based on the value 'toto' in the attribute personName)
> >>>>
> >>>> Actually I put this "Business" logic (create /delete) in an extension
> >>>> of DefaultAnyObjectProvisioningManager (see code below). But are there
> >>>> the best implementations ?
> >>>> Generally this "Business" logic takes place in a workflow, I also
> >>>> extend DefaultAnyObjectWorkflowAdapter, but I found a problem with the
> >>>> delete. It's also very deep in Syncope and not user friendly (pure
> >>>> Java code).
> >>>> My view on Syncope is that it's a good tool for synchronizing
> >>>> resources and accounts, and also for authenticating those resources
> >>>> and their accounts. However, its account-centric approach makes it
> >>>> difficult to integrate an identity-oriented logic.
> >>>> At this point in my thinking, I believe that part of the logic needs
> >>>> to be put upstream of Syncope and APIs used to control access
> >>>> management.
> >>>> What did you think ?
> >>>>
> >>>> Best regards,
> >>>>
> >>>> Stéphane POPOFF
> >>>>
> >>>> public class PersonProvisioningManager extends
> >>>> DefaultAnyObjectProvisioningManager {
> >>>>       @Autowired
> >>>>       UserProvisioningManager userProvisioningManager;
> >>>>       @Autowired
> >>>>       protected UserDAO userDAO;
> >>>>       protected static final Logger LOG =
> >>>> LogManager.getLogger(PersonProvisioningManager.class);
> >>>>       public PersonProvisioningManager(AnyObjectWorkflowAdapter
> >>>> awfAdapter, PropagationManager propagationManager,
> >>>> PropagationTaskExecutor taskExecutor, AnyObjectDAO anyObjectDAO) {
> >>>>           super(awfAdapter, propagationManager, taskExecutor, 
> >>>> anyObjectDAO);
> >>>>       }
> >>>>       @Transactional(propagation = Propagation.REQUIRES_NEW)
> >>>>       @Override
> >>>>       public ProvisioningResult<String> create(
> >>>>               final AnyObjectCR anyObjectCR,
> >>>>               final Set<String> excludedResources,
> >>>>               final boolean nullPriorityAsync,
> >>>>               final String creator,
> >>>>               final String context){
> >>>>           //on teste si c'est un objet PERSON car alors on va peut-être
> >>>> créer un compte d'accès primaire
> >>>>           if(!anyObjectCR.getType().equals("PERSON")){
> >>>>              LOG.info("pas un objet PERSON <> "+anyObjectCR.getType());
> >>>>              return super.create(anyObjectCR, excludedResources,
> >>>> nullPriorityAsync, creator, context);
> >>>>           }
> >>>>           LOG.info("1 Création d'une personne 
> >>>> name="+anyObjectCR.getName());
> >>>>           //on fait appel au workflow objet qui reconnait les PERSON
> >>>>           WorkflowResult<String> created =
> >>>> awfAdapter.create(anyObjectCR, creator, context);
> >>>>           LOG.info("retour du workflow "+created.getResult());
> >>>>           //je sais pas ce qu'il raconte en cas de réussite ou échec ?
> >>>>           //on cherche un USER avec un personId == anyObjectCR.getName();
> >>>>           List<User> users = null;
> >>>>           boolean erreur = false;
> >>>>           try{
> >>>>               users = userDAO.findByDerAttrValue("personName==",
> >>>> anyObjectCR.getName(), false);
> >>>>           }catch(Exception ex){
> >>>>               LOG.error("Erreur recherche USER "+ex);
> >>>>               erreur = true;
> >>>>           }
> >>>>           if(!erreur && users != null && !users.isEmpty()){
> >>>>               //on trouve un des comptes
> >>>>               LOG.info("2 Déjà des comptes, mais conforme aux besoins 
> >>>> métiers ?");
> >>>>           }else if(!erreur){
> >>>>               LOG.info("2 Pas comptes, mais répondre aux besoins métiers 
> >>>> ?");
> >>>>               UserCR novo = new UserCR.Builder("/", 
> >>>> "usr-"+anyObjectCR.getName())
> >>>>                       .plainAttr(new
> >>>> Attr.Builder("personName").value(anyObjectCR.getName()).build()).build();
> >>>>               userProvisioningManager.create(novo, nullPriorityAsync,
> >>>> creator, context);
> >>>>           }
> >>>>           //Je sais pas encore comment gérer cette partie
> >>>>           List<PropagationTaskInfo> taskInfos = 
> >>>> propagationManager.getCreateTasks(
> >>>>                   AnyTypeKind.ANY_OBJECT,
> >>>>                   created.getResult(),
> >>>>                   null,
> >>>>                   created.getPropByRes(),
> >>>>                   excludedResources);
> >>>>           //en fait je retourne zéro pour l'instant
> >>>>           LOG.info("3 Taches de propagation nb="+taskInfos.size());
> >>>>           for(PropagationTaskInfo taskInfo : taskInfos){
> >>>>               LOG.info("4 Tache de propagation key="+taskInfo.getKey());
> >>>>           }
> >>>>           PropagationReporter propagationReporter =
> >>>> taskExecutor.execute(taskInfos, nullPriorityAsync, creator);
> >>>>           return new ProvisioningResult<>(created.getResult(),
> >>>> propagationReporter.getStatuses());
> >>>>       }
> >>>>
> >>>>       @Transactional(propagation = Propagation.REQUIRES_NEW)
> >>>>       @Override
> >>>>       public List<PropagationStatus> delete(
> >>>>               final String key,
> >>>>               final Set<String> excludedResources,
> >>>>               final boolean nullPriorityAsync,
> >>>>               final String eraser,
> >>>>               final String context) {
> >>>>
> >>>>           LOG.info("1 Suppression un objet clé="+key);
> >>>>           Optional<? extends AnyObject> anyObject = 
> >>>> anyObjectDAO.findById(key);
> >>>>           boolean objDel = false;
> >>>>           if(anyObject.isEmpty() || !anyObject.isPresent()){
> >>>>               LOG.warn("Pas retrouvé l'objet clé="+key);
> >>>>           }else{
> >>>>               LOG.info("2 Suppression un objet 
> >>>> type="+anyObject.get().getType()+
> >>>>                       " avec name="+anyObject.get().getName());
> >>>>               objDel = true;
> >>>>           }
> >>>>           if(objDel &&
> >>>> anyObject.get().getType().toString().equals("JPAAnyType[PERSON]")){
> >>>>               LOG.info("3 Suppression un objet PERSON dans provisioning 
> >>>> name="+
> >>>>                       anyObject.get().getName()+" key="+key);
> >>>>               List<User> users = null;
> >>>>               boolean erreur = false;
> >>>>               try{
> >>>>                   users = userDAO.findByDerAttrValue("personName==",
> >>>> anyObject.get().getName(), false);
> >>>>               }catch(Exception ex){
> >>>>                   LOG.error("Erreur recherche USER "+ex);
> >>>>                   erreur = true;
> >>>>               }
> >>>>               if(!erreur && users != null && !users.isEmpty()){
> >>>>                   //on trouve un des comptes
> >>>>                   LOG.info("4 Trouver des comptes, on flingue");
> >>>>                   for(User user : users){
> >>>>                       userDAO.delete(user);
> >>>>                   }
> >>>>               }
> >>>>           }
> >>>>           PropagationByResource<String> propByRes = new 
> >>>> PropagationByResource<>();
> >>>>           propByRes.set(ResourceOperation.DELETE,
> >>>> anyObjectDAO.findAllResourceKeys(key));
> >>>>
> >>>>           // Note here that we can only notify about "delete", not any 
> >>>> other
> >>>>           // task defined in workflow process definition: this because 
> >>>> this
> >>>>           // information could only be available after 
> >>>> awfAdapter.delete(), which
> >>>>           // will also effectively remove user from db, thus making 
> >>>> virtually
> >>>>           // impossible by NotificationManager to fetch required any
> >>>> object information
> >>>>      //En gros les informations / objet produits dans le workflow sont
> >>>> inaccessibles car il va supprimer l'objet
> >>>>      //donc seuls les "ressources liées" sont prévenues
> >>>>           List<PropagationTaskInfo> taskInfos = 
> >>>> propagationManager.getDeleteTasks(
> >>>>                   AnyTypeKind.ANY_OBJECT,
> >>>>                   key,
> >>>>                   propByRes,
> >>>>                   null,
> >>>>                   excludedResources);
> >>>>           PropagationReporter propagationReporter =
> >>>> taskExecutor.execute(taskInfos, nullPriorityAsync, eraser);
> >>>>           awfAdapter.delete(key, eraser, context);
> >>>>           return propagationReporter.getStatuses();
> >>>>       }
> >>>> }
> >>>>
> >>>> Le jeu. 8 janv. 2026 à 08:50, Francesco Chicchiriccò
> >>>> <[email protected]> a écrit :
> >>>>> Hi,
> >>>>> my French is not as good as it used to be, so I am not very able to 
> >>>>> follow :-)
> >>>>>
> >>>>> Please try to explain your needs as clear as possible, in English, 
> >>>>> possibly in terms of requirements.
> >>>>>
> >>>>> So far, I have understood that:
> >>>>>
> >>>>> 1. you have defined a PERSON AnyType
> >>>>> 2. you have mapped PERSON to one or more External Resources
> >>>>> 3. (maybe?) multiple PERSON instances are in Relationship with a single 
> >>>>> USER instance
> >>>>> 4. there are events (REST calls to Syncope API? Console modifications?) 
> >>>>> that are triggering changes on PERSON instances ("arrives first", 
> >>>>> "change OrganizationalUnit") or possibly the related USER instance; 
> >>>>> such changes will propagate, through the mapping(s) provided, to the 
> >>>>> configured External Resource(s)
> >>>>>
> >>>>> What I am failing to understand so far:
> >>>>>
> >>>>> 1. why you should be modifying the PERSON or USER AnyType definition 
> >>>>> when the events occurs: I would expect changes to PERSON or USER 
> >>>>> instances (by adding / removing attributes, memberships or 
> >>>>> relationships for example) but not the AnyType itself
> >>>>> 2. whether your are encapsulating somehow the changes (maybe via a 
> >>>>> custom REST endpoint) which is meant to affect both a USER and related 
> >>>>> PERSON instance(s)
> >>>>>
> >>>>> Maybe you can just provide a couple of examples, using Syncope concepts 
> >>>>> as AnyType, Relationships, instances, Resources, etc.
> >>>>>
> >>>>> Regards.
> >>>>>
> >>>>> On 07/01/26 11:30, Stéphane Popoff wrote:
> >>>>>> Happy new year,
> >>>>>> About my use case I made a short video
> >>>>>> [https://sites.google.com/spopoff.net/integrationsyncope/democonnsync?usp=sharing]
> >>>>>> to explain the need.
> >>>>>>
> >>>>>> Best regards,
> >>>>>> Stéphane POPOFF
> >>>>>>
> >>>>>> Le mer. 31 déc. 2025 à 11:27, Stéphane Popoff <[email protected]> a 
> >>>>>> écrit :
> >>>>>>> Hi Francesco,
> >>>>>>> I want to manage access (User account) by an identity object, that 
> >>>>>>> was supported by an AnyObject PERSON and it's connector. So the life 
> >>>>>>> cycle of the object PERSON conducts the need of access of the 
> >>>>>>> identity. In fact PERSON is the the real digital identity, not as 
> >>>>>>> User in the Syncope's design, and User only supports access.
> >>>>>>> Hope it's more clear.
> >>>>>>>
> >>>>>>> Best regards,
> >>>>>>> Stéphane popoff
> >>>>>>>
> >>>>>>> Le mer. 31 déc. 2025, 07:59, Francesco Chicchiriccò 
> >>>>>>> <[email protected]> a écrit :
> >>>>>>>> Hi,
> >>>>>>>> I am not sure to understand your use case - or better, I am not sure 
> >>>>>>>> to understand why a change to the USER AnyType should be involved.
> >>>>>>>>
> >>>>>>>> Can you please describe what you are trying to achieve from an 
> >>>>>>>> abstract point of view?
> >>>>>>>>
> >>>>>>>> Regards.
> >>>>>>>>
> >>>>>>>> On 30/12/25 19:31, Stéphane Popoff wrote:
> >>>>>>>>> Hello,
> >>>>>>>>>
> >>>>>>>>> I have a use case where a flow of objects (JPAAnyType[PERSON]) may
> >>>>>>>>> produce change on accounts (User).
> >>>>>>>>> Beside the 2 flows I want to introduce a business logic of kind:
> >>>>>>>>> - If a person arrive first, we give it a basic access to RH system 
> >>>>>>>>> and
> >>>>>>>>> a basic access to the tool of this operationalUnit
> >>>>>>>>> - If a person changes this operationalUnit, previous access is 
> >>>>>>>>> closed,
> >>>>>>>>> newest is open (with or without overlay)
> >>>>>>>>> - well real time implementation of an IAM system ;-)
> >>>>>>>>> My question is where can I put this logic ? In the Provisioning
> >>>>>>>>> Manager or/and/xor in the Workflow ?
> >>>>>>>>>
> >>>>>>>>> I did some tests and found that mixing operations across types
> >>>>>>>>> (JPAAnyType[PERSON] and User) in a basic workflow creates problems.
> >>>>>>>>>
> >>>>>>>>> Best regards, happy new eve,
> >>>>>>>>> Stéphane POPOFF
> >
>
> --
> Francesco Chicchiriccò
>
> Tirasa - Open Source Excellence
> http://www.tirasa.net/
>
> Member at The Apache Software Foundation
> Syncope, Cocoon, Olingo, CXF, OpenJPA
> https://about.me/ilgrosso
>

Reply via email to