[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