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

Pavol Mederly mederly at evolveum.com
Thu Apr 24 17:19:55 CEST 2025


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 developer
evolveum.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:
>       o Receive the group ID(s).
>       o Find the corresponding RoleType OID using midpoint.searchObjects.
>       o Construct and return an AssignmentType with the targetRef
>         populated.
>   * Errors Encountered in Script:
>       o 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) ....
>       o Using midpoint.searchObjects(RoleType.class, queryFilter)
>         (passing EqualFilterImpl directly): Failed with the same
>         MissingMethodException.
>       o 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) ....
>       o Using QueryBuilder.queryFor(...): Failed compilation with
>         unable to resolve class
>         com.evolveum.midpoint.prism.query.QueryBuilder.
>       o 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 <http://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 <http://log.info/>("No valid group IDs found to process after 
> filtering.")
>     return [] // Retorna lista vazia de Assignments
> }
>
> log.info <http://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 <http://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 <http://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 <http://log.info/>("Prepared AssignmentType for Role: {}", 
> matchingRole.getName())
>     }
> } // Fim do loop .each
>
> log.info <http://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.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 list
> midPoint at lists.evolveum.com
> https://lists.evolveum.com/mailman/listinfo/midpoint
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.evolveum.com/pipermail/midpoint/attachments/20250424/bc1d841f/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/bc1d841f/attachment-0001.png>


More information about the midPoint mailing list