[midPoint] MidPoint 4.9: Help Mapping Resource Group Membership (icfs:groups) to User Role Assignment (assignment) - Query Issues in Inbound Script

Rafael Mantellatto rafael.mantellatto at nomadglobal.com
Fri Apr 25 01:07:59 CEST 2025


Hello MidPoint Community,

Mederly, thank you for your referral.
I tried to configure an inbound mapping on MidPoint 4.9 to synchronize
resource group memberships (stored in an icfs:groups attribute on the
__ACCOUNT__ object) into focus assignments (specifically, assigning the
RoleType that owns the corresponding group entitlement).
I have successfully configured the simulated reference in
<capabilities><configured><cap:references> linking the account's
icfs:groups attribute to the group's unique identifier (icfs:uid). Let's
call this reference UserGroupMembershipRef.
The challenge lies in configuring the <inbound> mapping for the icfs:groups
attribute within <schemaHandling><objectType name="__ACCOUNT__">. According
to the documentation (like the Grouper connector example at
https://docs.evolveum.com/midpoint/reference/master/resources/entitlements/#associationsreferences-versus-attributes),
I should use the associationSynchronization evaluator.
However, I am encountering contradictory XML validation errors when trying
different structures based on the documentation and logical alternatives:

*Attempt 1: Following Documentation Structure*
Based on the documentation, I tried this structure for the <inbound> block:

>
> *<inbound>*
> *    <name>Sync Group Membership</name>*
> *    <strength>strong</strength>*
> *    <target><path>assignment</path></target>*
> *    <expression>*
> *        <associationSynchronization>*
> *            <associationType>UserGroupMembershipRef</associationType>
> <!-- As per docs example -->*
> *            <objectRef>*
> *                <mapping>*
> *                    <name>Find Role Owner</name>*
> *                    <strength>strong</strength>*
> *                    <expression> <!-- As per docs example -->*
> *
>  <evaluator>shadowOwnerReferenceSearch</evaluator>*
> *                    </expression>*
> *                    <target><path>targetRef</path></target>*
> *                </mapping>*
> *            </objectRef>*
> *            <correlation>*
> *
> <correlators><items><item><ref>targetRef</ref></item></items></correlators>*
> *            </correlation>*
> *            <synchronization>*
> *
> <reaction><situation>unmatched</situation><action>addFocusValue</action></reaction>*
> *
> <reaction><situation>matched</situation><action>synchronize</action></reaction>*
> *
> <reaction><situation>unlinked</situation><action>delete</action></reaction>*
> *            </synchronization>*
> *        </associationSynchronization>*
> *    </expression>**</inbound>*


*Errors Encountered with Attempt 1:*

   1. No field 'evaluator' in class class
   com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType: This
   error occurs pointing to the
   <evaluator>shadowOwnerReferenceSearch</evaluator> line within
   <objectRef><mapping>. It seems the generic <evaluator> tag is not allowed
   here in 4.9.
   2. If I could get past the first error, I also previously received Item
   associationType has no definition (in value CTD
   ({.../common/common-3}AssociationSynchronizationExpressionEvaluatorType))
   pointing to the <associationType> line, suggesting it's also not allowed
   directly under <associationSynchronization> as shown in the docs.


*Attempt 2: Using Specific Tags & Removing <associationType>*
Based on the errors above, I tried using the specific
<shadowOwnerReferenceSearch/> tag and removing <associationType>:

>
> *<inbound>*
> *    <name>Sync Group Membership</name>*
> *    <strength>strong</strength>*
> *    <target><path>assignment</path></target>*
> *    <expression>*
> *        <associationSynchronization>*
> *            <!-- associationType removed based on previous error -->*
> *            <objectRef>*
> *                <mapping>*
> *                    <name>Find Role Owner</name>*
> *                    <strength>strong</strength>*
> *                    <shadowOwnerReferenceSearch/> <!-- USING SPECIFIC TAG
> -->*
> *                    <target><path>targetRef</path></target>*
> *                </mapping>*
> *            </objectRef>*
> *            <correlation>*
> *
> <correlators><items><item><ref>targetRef</ref></item></items></correlators>*
> *            </correlation>*
> *            <synchronization>*
> *
>  <reaction><situation>unmatched</situation><action>addFocusValue</action></reaction>*
> *
>  <reaction><situation>matched</situation><action>synchronize</action></reaction>*
> *
>  <reaction><situation>unlinked</situation><action>delete</action></reaction>*
> *            </synchronization>*
> *        </associationSynchronization>*
> *    </expression>**</inbound>*


*Error Encountered with Attempt 2:*

   - Item shadowOwnerReferenceSearch has no definition (in value CTD
   ({.../common/common-3}InboundMappingType)): This error now occurs pointing
   to the <shadowOwnerReferenceSearch/> tag within <objectRef><mapping>. The
   validator rejects the specific tag here as well.


*Summary of Contradiction:*
The MidPoint 4.9 validator seems to reject both the documented
<expression><evaluator>shadowOwnerReferenceSearch</evaluator></expression>
structure and the logical alternative <shadowOwnerReferenceSearch/> tag
within the <objectRef><mapping> context of an inbound
associationSynchronization. It also seems to reject the documented
placement of <associationType>.
I have also confirmed via the Admin GUI documentation 1 that the GUI
expression editor is limited and does not support selecting
associationSynchronization directly.

*Request:*
Could someone please provide the exact, validated XML syntax required in
MidPoint 4.9 for an <inbound> mapping (within <schemaHandling>) that
correctly uses the associationSynchronization evaluator, including the
proper way to configure the nested objectRef mapping with
shadowOwnerReferenceSearch (or its equivalent) and specify the
associationType (if needed)?
Is this a known issue, a documentation inconsistency, or am I missing a
different structural approach for 4.9?

Thank you very much for your help!

Best regards,
Rafael Mantellatto

Em qui., 24 de abr. de 2025 às 12:20, Pavol Mederly via midPoint <
midpoint at lists.evolveum.com> escreveu:

> Hello Rafael,
>
> the <association> tag is the way to go. Please see
> https://docs.evolveum.com/midpoint/reference/support-4.9/resources/entitlements/,
> in particular how to define a simulated reference type (of
> subject-to-object type, in your case), and then how to define an inbound
> mapping to assignments.
>
> Regards,
>
> --
> Pavol Mederly
> Software developerevolveum.com
>
> On 24/04/2025 14:52, Rafael Mantellatto via midPoint wrote:
>
> Hello MidPoint Community,
>
> I am currently working with MidPoint 4.9 and integrating a JumpCloud
> <https://jumpcloud.com/> resource. I need assistance with mapping group
> memberships from the JumpCloud resource shadow (__ACCOUNT__) to RoleType
> assignments on the corresponding MidPoint UserType focus object.
>
>
> *Goal: *The resource shadow (__ACCOUNT__) has an attribute icfs:groups
> which contains the identifier(s) of the JumpCloud group(s) the user belongs
> to. I want to configure MidPoint so that during synchronization, it
> creates/removes AssignmentType entries under the user's assignment path,
> where each assignment's targetRef points to the RoleType object in MidPoint
> that corresponds to the JumpCloud group ID.
>
> *Configuration Overview:*
>
>    - Resource: JumpCloud Connector
>    - MidPoint Version: 4.9
>    - Account Mapping: __ACCOUNT__ (kind: account) maps icfs:groups
>    (String/Multivalue String).
>    - Role Mapping: __GROUP__ (kind: entitlement) maps icfs:uid (JumpCloud
>    Group ID) to the RoleType's identifier attribute.
>
> *Attempts and Failures:*
> I have tried several approaches within the schemaHandling for the
> __ACCOUNT__ object type's icfs:groups attribute, but have encountered
> issues:
>
> 1) Direct Inbound Mapping ("As Is"):
>
>    - Configured <inbound> with <target><path>c:assignment</path></target>
>    and no <expression> (implicit "As Is").
>    - Error: Failed during reconciliation with
>    java.lang.IllegalArgumentException: Expected class
>    com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType type,
>    but got class java.lang.String. MidPoint expected an AssignmentType object,
>    not the raw group ID string.
>
> 2) <association> Tag:
>
>    - Tried adding an <association> definition within the <attribute
>    ref="icfs:groups"> tag in schemaHandling.
>    - Error: Failed to save the resource XML with a schema validation
>    error: Item association has no definition (in value CTD
>    ({.../common/common-3}ResourceAttributeDefinitionType)).
>
> 3) Inbound Script (Returning AssignmentType):
>
>    - Wrote a Groovy script within
>    <inbound><expression><script><code>...</code></script></expression>
>    targeting c:assignment. The script aims to:
>       - Receive the group ID(s).
>       - Find the corresponding RoleType OID using midpoint.searchObjects.
>       - Construct and return an AssignmentType with the targetRef
>       populated.
>    - Errors Encountered in Script:
>       - Using midpoint.searchObjects(RoleType.class, objectQuery): Failed
>       with groovy.lang.MissingMethodException: No signature of method:
>       ...searchObjects() is applicable for argument types: (Class,
>       com.evolveum.midpoint.prism.query.ObjectQuery) ....
>       - Using midpoint.searchObjects(RoleType.class, queryFilter)
>       (passing EqualFilterImpl directly): Failed with the same
>       MissingMethodException.
>       - Using ObjectQuery.createObjectQuery(queryFilter): Failed with
>       java.lang.NoSuchMethodError: No signature of method: static
>       com.evolveum.midpoint.prism.query.ObjectQuery.createObjectQuery() is
>       applicable for argument types:
>       (com.evolveum.midpoint.prism.impl.query.EqualFilterImpl) ....
>       - Using QueryBuilder.queryFor(...): Failed compilation with unable
>       to resolve class com.evolveum.midpoint.prism.query.QueryBuilder.
>       - Conclusion: It seems the necessary query API (searchObjects,
>       QueryBuilder, ObjectQuery.createObjectQuery) is not available or working
>       correctly within the context of this specific inbound script expression in
>       MidPoint 4.9.
>
> *Relevant XML Snippets:*
> <!-- Within Resource XML -->
> <schemaHandling>        <objectType id="5">
>             <kind>account</kind>
>             <displayName>__ACCOUNT__</displayName>
>             <delineation>
>                 <objectClass>ri:AccountObjectClass</objectClass>
>             </delineation>
>             <focus>
>                 <type>c:UserType</type>
>             </focus>
>             <attribute id="9">
>                 <ref>ri:email</ref>
>                 <inbound id="10">
>                     <name>Map User Email</name>
>                     <strength>strong</strength>
>                     <target>
>                         <path>emailAddress</path>
>                     </target>
>                 </inbound>
>             </attribute>
>             <attribute id="11">
>                 <ref>ri:firstname</ref>
>                 <inbound id="12">
>                     <name>Map User Firstname</name>
>                     <strength>strong</strength>
>                     <target>
>                         <path>givenName</path>
>                     </target>
>                 </inbound>
>             </attribute>
>             <attribute id="13">
>                 <ref>ri:lastname</ref>
>                 <inbound id="14">
>                     <name>Map User Lastname</name>
>                     <strength>strong</strength>
>                     <target>
>                         <path>familyName</path>
>                     </target>
>                 </inbound>
>             </attribute>
>             <attribute id="15">
>                 <ref>icfs:uid</ref>
>                 <inbound id="16">
>                     <name>Map User UID</name>
>                     <strength>strong</strength>
>                     <target>
>                         <path>extension/jumpCloudUid</path>
>                     </target>
>                 </inbound>
>             </attribute>
>             <attribute id="17">
>                 <ref>icfs:name</ref>
>                 <inbound id="18">
>                     <name>Map User Username</name>
>                     <strength>strong</strength>
>                     <target>
>                         <path>name</path>
>                     </target>
>                 </inbound>
>             </attribute>
>             <attribute id="19">
>                 <ref>ri:suspended</ref>
>                 <inbound id="20">
>                     <name>Map User Status</name>
>                     <strength>strong</strength>
>                     <expression>
>                         <script>
>                             <code>if (input == null) {
>     return 'enabled'
> }
>
> // No JumpCloud, suspended=true significa usuário desabilitado
> // No MidPoint, precisamos retornar 'disabled' quando suspended=true
> return input ? 'disabled' : 'enabled'</code>
>                         </script>
>                     </expression>
>                     <target>
>                         <path>activation/administrativeStatus</path>
>                     </target>
>                 </inbound>
>             </attribute>
>             <attribute id="170">
>                 <ref>icfs:groups</ref>
>                 <inbound id="196">
>                     <name>Map User Membership to Role Assignment
> (Script)</name>
>                     <strength>strong</strength>
>                     <expression>
>                         <script>
>                             <code>import
> com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType
> import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType
> import
> com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType
> import com.evolveum.midpoint.prism.path.ItemPath
> import com.evolveum.midpoint.prism.PrismContext
>
> log.info("SCRIPT(SeparateFilter) STARTED - Input: {}, User: {}", input,
> focus?.getName())
>
> PrismContext prismCtx = midpoint.getPrismContext()
> if (prismCtx == null) {
>     log.error("PrismContext is null! Cannot proceed.")
>     return null // Ou [] se esperar lista
> }
>
> // Lista temporária para normalizar a entrada
> def normalizedInput = []
> if (input != null) {
>     if (input instanceof Collection) {
>         normalizedInput.addAll((Collection)input)
>     } else {
>         normalizedInput.add(input)
>     }
> }
>
> // Filtrar valores nulos e depois strings vazias/em branco
> def groupIdsToProcess = normalizedInput
>     .findAll { it != null } // Primeiro, remove nulos
>     .collect { it.toString().trim() } // Converte para String e remove
> espaços extras
>     .findAll { !it.isEmpty() } // Remove strings vazias resultantes
>
> if (groupIdsToProcess.isEmpty()) {
>     log.info("No valid group IDs found to process after filtering.")
>     return [] // Retorna lista vazia de Assignments
> }
>
> log.info("Processing filtered group IDs: {}", groupIdsToProcess)
> def assignments = [] // Lista para guardar AssignmentType
> def roleIdentifierPath = ItemPath.create(RoleType.F_IDENTIFIER)
>
> groupIdsToProcess.each { groupId ->
>     log.info("Searching Role with identifier '{}' = {}",
> roleIdentifierPath, groupId)
>
>     def queryFilter
>     try {
>         queryFilter = prismCtx.queryFor(RoleType.class)
>             .item(roleIdentifierPath).eq(groupId)
>             .buildFilter()
>     } catch (Exception e) {
>         log.error("Error building query filter for Role ID {}: {}",
> groupId, e.getMessage())
>         return // continue para o próximo ID
>     }
>
>     def results
>     try {
>         // Tentando passar o ObjectFilter diretamente - FALHA AQUI!
>         results = midpoint.searchObjects(RoleType.class, queryFilter)
>     } catch (Exception e) {
>         log.error("!!! FAILED HERE: Failed midpoint.searchObjects (direct
> filter) for Role ID {}: {} ({})",
>                   groupId, e.getMessage(), e.getClass().getName())
>         return // continue
>     }
>
>     // Código abaixo não é alcançado devido ao erro acima
>     if (results.isEmpty()) {
>         log.warn("No Role found for JumpCloud Group ID: {}", groupId)
>     } else if (results.size() > 1) {
>         log.warn("Multiple Roles found for JumpCloud Group ID: {}. OIDs:
> {}. Ambiguous.",
>                  groupId, results.collect { it.getOid() })
>     } else {
>         def matchingRole = results.get(0)
>         log.info("Found Role: {} (OID: {})", matchingRole.getName(),
> matchingRole.getOid())
>
>         def roleRef = new ObjectReferenceType()
>         roleRef.setOid(matchingRole.getOid())
>         roleRef.setType(RoleType.COMPLEX_TYPE)
>
>         def assignment = new AssignmentType()
>         assignment.setTargetRef(roleRef)
>
>         assignments.add(assignment)
>         log.info("Prepared AssignmentType for Role: {}",
> matchingRole.getName())
>     }
> } // Fim do loop .each
>
> log.info("SCRIPT(SeparateFilter) FINISHED - Returning {} AssignmentType
> objects.", assignments.size())
> return assignments</code>
>                         </script>
>                     </expression>
>                     <target>
>                         <path>c:assignment</path>
>                     </target>
>                 </inbound>
>             </attribute>
>             <correlation>
>                 <correlators>
>                     <items id="46">
>                         <name>Correlate by username</name>
>                         <description>Correlaciona usuários pelo
> username</description>
>                         <enabled>true</enabled>
>                         <item id="47">
>                             <ref>c:name</ref>
>                         </item>
>                     </items>
>                 </correlators>
>             </correlation>
>             <synchronization>
>                 <reaction id="23">
>                     <name>Update User on Linked</name>
>                     <situation>linked</situation>
>                     <actions>
>                         <synchronize id="49">
>                             <objectTemplateRef
> oid="00000000-0000-0000-0000-000000000380" relation="org:default"
> type="c:ObjectTemplateType">
>                                 <!-- Person Object Template -->
>                             </objectTemplateRef>
>                         </synchronize>
>                     </actions>
>                 </reaction>
>                 <reaction id="25">
>                     <name>Create User on Unmatched</name>
>                     <situation>unmatched</situation>
>                     <actions>
>                         <addFocus id="26">
>                             <objectTemplateRef
> oid="00000000-0000-0000-0000-000000000380" relation="org:default"
> type="c:ObjectTemplateType">
>                                 <!-- Person Object Template -->
>                             </objectTemplateRef>
>                         </addFocus>
>                     </actions>
>                 </reaction>
>                 <reaction id="50">
>                     <name>Link User on Unlinked</name>
>                     <situation>unlinked</situation>
>                     <actions>
>                         <link id="51">
>                             <objectTemplateRef
> oid="00000000-0000-0000-0000-000000000380" relation="org:default"
> type="c:ObjectTemplateType">
>                                 <!-- Person Object Template -->
>                             </objectTemplateRef>
>                         </link>
>                     </actions>
>                 </reaction>
>             </synchronization>
>         </objectType>
> </schemaHandling>
>
> *Error reconciling a user:*
> [image: image.png]
>
> *Question:*
> Given the limitations encountered with inbound scripts and the
> <association> tag validation error in MidPoint 4.9, what is the recommended
> approach to correctly map an external group membership attribute (like
> icfs:groups containing identifiers) to UserType/assignment referencing the
> corresponding RoleType?
> Should this logic be moved to a Synchronization Reaction (e.g., using
> <script> within an action) or an Object Template? Is there a different way
> to configure an association or mapping that avoids the API limitations
> within the inbound script context?
> Any guidance or examples would be greatly appreciated.
>
> Thank you,
> Rafael Mantellatto
>
> *Important - This message, along with any other attached information, is
> confidential and protected by law, and only its recipients are authorized
> to use it. If you received it in error, please inform the sender and then
> delete the message. Note: there is no authorization to store, forward,
> print, use and/or copy its content.*
>
>
> *Importante - Esta mensagem, juntamente com qualquer outra informação
> anexada, é confidencial e protegida por lei, e somente os seus
> destinatários são autorizados a usá-la. Caso a tenha recebido por engano,
> por favor, informe o remetente e em seguida apague a mensagem. Observação:
> não há autorização para armazenar, encaminhar, imprimir, usar e/ou copiar o
> seu conteúdo.*
>
> _______________________________________________
> midPoint mailing listmidPoint at lists.evolveum.comhttps://lists.evolveum.com/mailman/listinfo/midpoint
>
> _______________________________________________
> midPoint mailing list
> midPoint at lists.evolveum.com
> https://lists.evolveum.com/mailman/listinfo/midpoint
>

-- 


*Important - This message, along with any other attached information, is 
confidential and protected by law, and only its recipients are authorized 
to use it. If you received it in error, please inform the sender and then 
delete the message. Note: there is no authorization to store, forward, 
print, use and/or copy its content.*








*Importante - Esta mensagem, 
juntamente com qualquer outra informação anexada, é confidencial e 
protegida por lei, e somente os seus destinatários são autorizados a 
usá-la. Caso a tenha recebido por engano, por favor, informe o remetente e 
em seguida apague a mensagem. Observação: não há autorização para 
armazenar, encaminhar, imprimir, usar e/ou copiar o seu conteúdo.*
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.evolveum.com/pipermail/midpoint/attachments/20250424/33d628b8/attachment-0001.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image.png
Type: image/png
Size: 125002 bytes
Desc: not available
URL: <https://lists.evolveum.com/pipermail/midpoint/attachments/20250424/33d628b8/attachment-0001.png>


More information about the midPoint mailing list