<div dir="ltr">Hello MidPoint Community,<div><br>I am currently working with MidPoint 4.9 and integrating a <a href="https://jumpcloud.com/" target="_blank">JumpCloud</a> resource. I need assistance with mapping group memberships from the JumpCloud resource shadow (__ACCOUNT__) to RoleType assignments on the corresponding MidPoint UserType focus object.</div><div><br><b>Goal:<br></b>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.<br><br><b>Configuration Overview:</b><br><ul><li style="margin-left:15px">Resource: JumpCloud Connector</li><li style="margin-left:15px">MidPoint Version: 4.9</li><li style="margin-left:15px">Account Mapping: __ACCOUNT__ (kind: account) maps icfs:groups (String/Multivalue String).</li><li style="margin-left:15px">Role Mapping: __GROUP__ (kind: entitlement) maps icfs:uid (JumpCloud Group ID) to the RoleType's identifier attribute.</li></ul><b>Attempts and Failures:</b><br>I have tried several approaches within the schemaHandling for the __ACCOUNT__ object type's icfs:groups attribute, but have encountered issues:</div><div><br></div><div>1) Direct Inbound Mapping ("As Is"):<div><ul><li style="margin-left:15px">Configured <inbound> with <target><path>c:assignment</path></target> and no <expression> (implicit "As Is").</li><li style="margin-left:15px">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.</li></ul></div>2) <association> Tag:<br><ul><li style="margin-left:15px">Tried adding an <association> definition within the <attribute ref="icfs:groups"> tag in schemaHandling.</li><li style="margin-left:15px">Error: Failed to save the resource XML with a schema validation error: Item association has no definition (in value CTD ({.../common/common-3}ResourceAttributeDefinitionType)).</li></ul>3) Inbound Script (Returning AssignmentType):<br><ul><li style="margin-left:15px">Wrote a Groovy script within <inbound><expression><script><code>...</code></script></expression> targeting c:assignment. The script aims to:</li><ul><li style="margin-left:15px">Receive the group ID(s).</li><li style="margin-left:15px">Find the corresponding RoleType OID using midpoint.searchObjects.</li><li style="margin-left:15px">Construct and return an AssignmentType with the targetRef populated.</li></ul><li style="margin-left:15px">Errors Encountered in Script:</li><ul><li style="margin-left:15px">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) ....</li><li style="margin-left:15px">Using midpoint.searchObjects(RoleType.class, queryFilter) (passing EqualFilterImpl directly): Failed with the same MissingMethodException.</li><li style="margin-left:15px">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) ....</li><li style="margin-left:15px">Using QueryBuilder.queryFor(...): Failed compilation with unable to resolve class com.evolveum.midpoint.prism.query.QueryBuilder.</li><li style="margin-left:15px">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.</li></ul></ul><b>Relevant XML Snippets:</b><br><div style="color:rgb(123,136,161);background-color:rgb(30,33,39);font-family:Consolas,"Courier New",monospace;font-size:12px;line-height:16px"><div><span style="color:rgb(76,86,106);font-style:italic"><!--</span><span style="color:rgb(97,110,136);font-style:italic"> Within Resource XML </span><span style="color:rgb(76,86,106);font-style:italic">--></span></div></div><div style="color:rgb(123,136,161);background-color:rgb(30,33,39);font-family:Consolas,"Courier New",monospace;font-size:12px;line-height:16px"><span style="color:rgb(129,161,193)"><schemaHandling></span> 
       <objectType id="5"><br>            <kind>account</kind><br>            <displayName>__ACCOUNT__</displayName><br>            <delineation><br>                <objectClass>ri:AccountObjectClass</objectClass><br>            </delineation><br>            <focus><br>                <type>c:UserType</type><br>            </focus><br>            <attribute id="9"><br>                <ref>ri:email</ref><br>                <inbound id="10"><br>                    <name>Map User Email</name><br>                    <strength>strong</strength><br>                    <target><br>                        <path>emailAddress</path><br>                    </target><br>                </inbound><br>            </attribute><br>            <attribute id="11"><br>                <ref>ri:firstname</ref><br>                <inbound id="12"><br>                    <name>Map User Firstname</name><br>                    <strength>strong</strength><br>                    <target><br>                        <path>givenName</path><br>                    </target><br>                </inbound><br>            </attribute><br>            <attribute id="13"><br>                <ref>ri:lastname</ref><br>                <inbound id="14"><br>                    <name>Map User Lastname</name><br>                    <strength>strong</strength><br>                    <target><br>                        <path>familyName</path><br>                    </target><br>                </inbound><br>            </attribute><br>            <attribute id="15"><br>                <ref>icfs:uid</ref><br>                <inbound id="16"><br>                    <name>Map User UID</name><br>                    <strength>strong</strength><br>                    <target><br>                        <path>extension/jumpCloudUid</path><br>                    </target><br>                </inbound><br>            </attribute><br>            <attribute id="17"><br>                <ref>icfs:name</ref><br>                <inbound id="18"><br>                    <name>Map User Username</name><br>                    <strength>strong</strength><br>                    <target><br>                        <path>name</path><br>                    </target><br>                </inbound><br>            </attribute><br>            <attribute id="19"><br>                <ref>ri:suspended</ref><br>                <inbound id="20"><br>                    <name>Map User Status</name><br>                    <strength>strong</strength><br>                    <expression><br>                        <script><br>                            <code>if (input == null) {<br>    return 'enabled'<br>}<br><br>// No JumpCloud, suspended=true significa usuário desabilitado<br>// No MidPoint, precisamos retornar 'disabled' quando suspended=true<br>return input ? 'disabled' : 'enabled'</code><br>                        </script><br>                    </expression><br>                    <target><br>                        <path>activation/administrativeStatus</path><br>                    </target><br>                </inbound><br>            </attribute><br>            <attribute id="170"><br>                <ref>icfs:groups</ref><br>                <inbound id="196"><br>                    <name>Map User Membership to Role Assignment (Script)</name><br>                    <strength>strong</strength><br>                    <expression><br>                        <script><br>                            <code>import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType<br>import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType<br>import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType<br>import com.evolveum.midpoint.prism.path.ItemPath<br>import com.evolveum.midpoint.prism.PrismContext<br><br><a href="http://log.info/" target="_blank">log.info</a>("SCRIPT(SeparateFilter) STARTED - Input: {}, User: {}", input, focus?.getName())<br><br>PrismContext prismCtx = midpoint.getPrismContext()<br>if (prismCtx == null) {<br>    log.error("PrismContext is null! Cannot proceed.")<br>    return null // Ou [] se esperar lista<br>}<br><br>// Lista temporária para normalizar a entrada<br>def normalizedInput = []<br>if (input != null) {<br>    if (input instanceof Collection) {<br>        normalizedInput.addAll((Collection)input)<br>    } else {<br>        normalizedInput.add(input)<br>    }<br>}<br><br>// Filtrar valores nulos e depois strings vazias/em branco<br>def groupIdsToProcess = normalizedInput<br>    .findAll { it != null } // Primeiro, remove nulos<br>    .collect { it.toString().trim() } // Converte para String e remove espaços extras<br>    .findAll { !it.isEmpty() } // Remove strings vazias resultantes<br><br>if (groupIdsToProcess.isEmpty()) {<br>    <a href="http://log.info/" target="_blank">log.info</a>("No valid group IDs found to process after filtering.")<br>    return [] // Retorna lista vazia de Assignments<br>}<br><br><a href="http://log.info/" target="_blank">log.info</a>("Processing filtered group IDs: {}", groupIdsToProcess)<br>def assignments = [] // Lista para guardar AssignmentType<br>def roleIdentifierPath = ItemPath.create(RoleType.F_IDENTIFIER)<br><br>groupIdsToProcess.each { groupId -&gt;<br>    <a href="http://log.info/" target="_blank">log.info</a>("Searching Role with identifier '{}' = {}", roleIdentifierPath, groupId)<br><br>    def queryFilter<br>    try {<br>        queryFilter = prismCtx.queryFor(RoleType.class)<br>            .item(roleIdentifierPath).eq(groupId)<br>            .buildFilter()<br>    } catch (Exception e) {<br>        log.error("Error building query filter for Role ID {}: {}", groupId, e.getMessage())<br>        return // continue para o próximo ID<br>    }<br><br>    def results<br>    try {<br>        // Tentando passar o ObjectFilter diretamente - FALHA AQUI!<br>        results = midpoint.searchObjects(RoleType.class, queryFilter)<br>    } catch (Exception e) {<br>        log.error("!!! FAILED HERE: Failed midpoint.searchObjects (direct filter) for Role ID {}: {} ({})",<br>                  groupId, e.getMessage(), e.getClass().getName())<br>        return // continue<br>    }<br><br>    // Código abaixo não é alcançado devido ao erro acima<br>    if (results.isEmpty()) {<br>        log.warn("No Role found for JumpCloud Group ID: {}", groupId)<br>    } else if (results.size() &gt; 1) {<br>        log.warn("Multiple Roles found for JumpCloud Group ID: {}. OIDs: {}. Ambiguous.",<br>                 groupId, results.collect { it.getOid() })<br>    } else {<br>        def matchingRole = results.get(0)<br>        <a href="http://log.info/" target="_blank">log.info</a>("Found Role: {} (OID: {})", matchingRole.getName(), matchingRole.getOid())<br><br>        def roleRef = new ObjectReferenceType()<br>        roleRef.setOid(matchingRole.getOid())<br>        roleRef.setType(RoleType.COMPLEX_TYPE)<br><br>        def assignment = new AssignmentType()<br>        assignment.setTargetRef(roleRef)<br><br>        assignments.add(assignment)<br>        <a href="http://log.info/" target="_blank">log.info</a>("Prepared AssignmentType for Role: {}", matchingRole.getName())<br>    }<br>} // Fim do loop .each<br><br><a href="http://log.info/" target="_blank">log.info</a>("SCRIPT(SeparateFilter) FINISHED - Returning {} AssignmentType objects.", assignments.size())<br>return assignments</code><br>                        </script><br>                    </expression><br>                    <target><br>                        <path>c:assignment</path><br>                    </target><br>                </inbound><br>            </attribute><br>            <correlation><br>                <correlators><br>                    <items id="46"><br>                        <name>Correlate by username</name><br>                        <description>Correlaciona usuários pelo username</description><br>                        <enabled>true</enabled><br>                        <item id="47"><br>                            <ref>c:name</ref><br>                        </item><br>                    </items><br>                </correlators><br>            </correlation><br>            <synchronization><br>                <reaction id="23"><br>                    <name>Update User on Linked</name><br>                    <situation>linked</situation><br>                    <actions><br>                        <synchronize id="49"><br>                            <objectTemplateRef oid="00000000-0000-0000-0000-000000000380" relation="org:default" type="c:ObjectTemplateType"><br>                                <!-- Person Object Template --><br>                            </objectTemplateRef><br>                        </synchronize><br>                    </actions><br>                </reaction><br>                <reaction id="25"><br>                    <name>Create User on Unmatched</name><br>                    <situation>unmatched</situation><br>                    <actions><br>                        <addFocus id="26"><br>                            <objectTemplateRef oid="00000000-0000-0000-0000-000000000380" relation="org:default" type="c:ObjectTemplateType"><br>                                <!-- Person Object Template --><br>                            </objectTemplateRef><br>                        </addFocus><br>                    </actions><br>                </reaction><br>                <reaction id="50"><br>                    <name>Link User on Unlinked</name><br>                    <situation>unlinked</situation><br>                    <actions><br>                        <link id="51"><br>                            <objectTemplateRef oid="00000000-0000-0000-0000-000000000380" relation="org:default" type="c:ObjectTemplateType"><br>                                <!-- Person Object Template --><br>                            </objectTemplateRef><br>                        </link><br>                    </actions><br>                </reaction><br>            </synchronization><br>        </objectType>
<div style="line-height:16px"><div><span style="color:rgb(129,161,193)"></schemaHandling></span></div></div></div></div><div><div><br></div><div><b>Error reconciling a user:</b><br><img src="cid:ii_m9ugocm30" alt="image.png" width="543" height="113" class="gmail-CToWUd gmail-a6T" tabindex="0" style="cursor: pointer; outline: 0px;"><br><br><b>Question:</b></div>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?<br>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?<br>Any guidance or examples would be greatly appreciated.<br><br>Thank you,<br>Rafael Mantellatto</div></div>

<br>
<p></p><div style="text-align:justify"><b>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.</b></div><br><p></p><p><br></p><p><b>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.</b><br></p>