<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Hello Rafael,</p>
<p>the <association> tag is the way to go. Please see <a
href="https://docs.evolveum.com/midpoint/reference/support-4.9/resources/entitlements/"
class="moz-txt-link-freetext">https://docs.evolveum.com/midpoint/reference/support-4.9/resources/entitlements/</a>,
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.</p>
<p>Regards,<br>
</p>
<pre class="moz-signature" cols="72">--
Pavol Mederly
Software developer
evolveum.com</pre>
<div class="moz-cite-prefix">On 24/04/2025 14:52, Rafael Mantellatto
via midPoint wrote:<br>
</div>
<blockquote type="cite"
cite="mid:CANw7vzv==vUnyHzY22ppnpysjGqzy_XvhQ_evuYHV6QErg6nzg@mail.gmail.com">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<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"
moz-do-not-send="true">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"
moz-do-not-send="true">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"
moz-do-not-send="true">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"
moz-do-not-send="true">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 -><br>
<a href="http://log.info/" target="_blank"
moz-do-not-send="true">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() > 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"
moz-do-not-send="true">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"
moz-do-not-send="true">log.info</a>("Prepared
AssignmentType for Role: {}", matchingRole.getName())<br>
}<br>
} // Fim do loop .each<br>
<br>
<a href="http://log.info/" target="_blank"
moz-do-not-send="true">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:part1.DFsA7Rxn.C9IGqVvM@evolveum.com"
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>
<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><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>
<br>
<fieldset class="moz-mime-attachment-header"></fieldset>
<pre wrap="" class="moz-quote-pre">_______________________________________________
midPoint mailing list
<a class="moz-txt-link-abbreviated" href="mailto:midPoint@lists.evolveum.com">midPoint@lists.evolveum.com</a>
<a class="moz-txt-link-freetext" href="https://lists.evolveum.com/mailman/listinfo/midpoint">https://lists.evolveum.com/mailman/listinfo/midpoint</a>
</pre>
</blockquote>
</body>
</html>