Making IDM Midpoint work with external requests

IDM by Evolveum Midpoint promises a lot, does only what it considers necessary. An extremely rare and little-described solution, but it is loved in Russia. SberTech built its Platform V IDM on Midpoint and does not hide it, according to some information, it even updates it to the latest version, but there is no demo, so it is impossible to check. According to rumors, there are two more Russian IDMs, which are also built on Midpoint, but on old versions, and they do not advertise it.

Evolvеum in its presentations emphasizes that Midpoint can do everything, and what it can't do is, firstly, not needed, and secondly, it was like that in the old IDMs, and we are new – we are better! For example, the issuance of roles is well-developed, but their taking away is basically not, the official answer is “Don't think about how to take away a role, think about how not to give it away.” Midpoint works with states, takes them into itself, wants to be an intermediary (it's right in its name), does not want to receive some commands, although there is a REST API and you can manage them with commands, but for this you need to write an external service for external requests – an intermediary for an intermediary!

But still, work in Midpoint itself with external requests can be implemented using existing tools.

Evolveum calls Midpoint an Open Source project, in my opinion it is hysterically commercial, although you can take it for free and use it, yes, but there are nuances:

Documentation: docs.evolveum.com

Partially or not. The worst example is the description in the Midpoint\Features\Current Features\Parametric Role section docs.evolveum.com/midpoint/features/current/parametric-role just a title and nothing.

And the only official (and simply the only) guide ends at the most interesting place with the phrase “give me money, otherwise I won’t write any further” docs.evolveum.com/book/practical-identity-management-with-midpoint.html#98-to-be-continued The author has been waiting since 22nd year…

Examples: github.com/Evolveum/midpoint-samples

Partially or not or very old

Community Support:

No community

Developer support: support.evolveum.com/projects/midpoint/

Only development of new functions and bugs, only paid subscribers are answered. Typical answers: referral to documentation, referral to integrators, promise to fix in the next releases.

Education: evolveum.com/services/training-and-certification/?target=training-courses-offer

There are paid courses, maybe they show real documentation there! There are several free videos on some functions on www.youtube.com/@Evolveum/videos more presentation than training.

Demo: demo.evolveum.com/midpoint/

Empty, but it's there!

But Midpoint can be cracked even without normal documentation and support, which I will demonstrate below, with examples and everything so that you can reproduce and check.

Initial data: Midpoint 4.8.4 does not work in version 4.8.3, not the main, but necessary function – filter in Resource in Schema Handler.

A simplified diagram to help you understand what's going on

– Midpoint is connected to systems 1,2,3 via Resource. At the entrance to Resource there is a Connector that decides how to connect to systems and takes data and gives it to Schema Handler, which decides what and how to do with them in Midpoint.

– As a result of synchronization, Resource creates in Midpoint – 4 user profile. 5 role for issuing AD group, 6 role WO application.

– When creating a WO application role, application data is written into its attributes, it is assigned an ArcheType (source of properties, functionality), from which an Object Template (source of attribute processing) is assigned. It is also assigned a WO Core role, in which a Policy Role is registered, which will run the script.

– The script is launched at any event with the WO application role, if it is not blocked. The script searches for a user and a role. If there is, it performs an action with them from the attributes of the WO application role, at the end it writes a comment to the WO application role and puts the attribute that everything is done (Object Template blocks the WO application role on this basis). If there is no role or user, it writes to the WO application role that there is an error, and it remains unimplemented, that is, not blocked, so the script will work again when changing the role or synchronizing the resource.

What it looks like in Midpoint

In the Roles tab we have the Work Order tab (where everything is collected from the ArcheType that we assigned to the WO request roles).

The list shows all the roles of the WO application with an informative Description. The completed applications are blocked, they also no longer participate in synchronization with Resource and the system marks them as completed (the Resource filter takes from the system only records without the “completed” mark). And the roles of the WO application with ERROR are waiting for editing by the admin, or maybe during the next synchronization such a user or group will appear, then they will work.

We can go to the user from the last application in History and see what was done with him

We see that someone WO_administrator has given the assigned user a role! Synchronization is started in the resource under WO_administrator.

Setting

1. The example involves additional attributes, they need to be added to Midopint

Place the some.xsd file with the contents in /opt/midpoint/var/schema

<xsd:schema elementFormDefault="qualified"
            targetNamespace="http://example.com/xml/ns/mySchema"
  xmlns:tns="http://example.com/xml/ns/mySchema"
  xmlns:a="http://prism.evolveum.com/xml/ns/public/annotation-3"
  xmlns:c="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:complexType name="RoleExtensionType">
    <xsd:annotation>
      <xsd:appinfo>
        <a:extension ref="c:RoleType"/>
      </xsd:appinfo>
    </xsd:annotation>
    <xsd:sequence>
      <xsd:element name="DROLER_owner" type="xsd:string" minOccurs="0" maxOccurs="1">
        <xsd:annotation>
          <xsd:appinfo>
            <a:indexed>true</a:indexed>
            <a:displayName>DROLER OWNER</a:displayName>
            <a:displayOrder>136</a:displayOrder>
            <a:help>ToDo</a:help>
          </xsd:appinfo>
        </xsd:annotation>
      </xsd:element>
      <xsd:element name="DROLER_role" type="xsd:string" minOccurs="0" maxOccurs="1">
        <xsd:annotation>
          <xsd:appinfo>
            <a:indexed>true</a:indexed>
            <a:displayName>DROLER ROLE</a:displayName>
            <a:displayOrder>138</a:displayOrder>
            <a:help>ToDo</a:help>
          </xsd:appinfo>
        </xsd:annotation>
      </xsd:element>
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>

To get new Midpoint attributes you need to reload. If you already have such a file, then add only what is in the complexType tags

2. Create an Object Template

Go to CONFIGURATION\Object Templates and create an Object Template called WO Object Template. Go into it. Everything that concerns the Object Template is well-written in the GUI Midpoint and can be made into buttons, but for clarity (and speed) let's go into the RAW Code

Click Edit Raw

Before

</objectTemplate>

We insert

<mapping id="2">
  <name>Block</name>
  <strength>strong</strength>
  <source>
    <path>c:documentation</path>
  </source>
  <expression>
    <script>
      <code>import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType;
return ActivationStatusType.DISABLED</code>
    </script>
  </expression>
  <target>
    <path>c:activation/c:administrativeStatus</path>
  </target>
  <condition>
    <script>
      <code>documentation == 'DONE'</code>
    </script>
  </condition>
  <evaluationPhase>afterAssignments</evaluationPhase>
</mapping>

If the Documentation attribute in the WO application role is filled with the word DONE, the role is deactivated.

This is how it looks in the GUI

3. Create an archetype for the role of WO application

Go to CONFIGURATION\ArcheTypes and create ArcheTypes called WO ArcheType Go into it. Let's try to do everything with buttons. Go to the Archetype Policy tab. Midpoint hides all unfilled attributes by default. Fill as in the picture

And this makes sense in the Object template reference you need to poke at your WO Object Template, since it has a different OID and you won’t be able to transfer it with code from another Midpoint.

Yes, it won't work without getting into the code at all, we also need to say that this archetype belongs to Role, there is no button for this in Assigments, so

Click Edit Raw

Before (and Midpoint itself will then put it where it needs, but it won’t be able to change the id itself if it complains about them or delete them or change the number)

</archetype>

Insert code

<assignment id="1">
  <identifier>holderType</identifier>
  <activation>
    <effectiveStatus>enabled</effectiveStatus>
  </activation>
  <assignmentRelation id="2">
    <holderType>RoleType</holderType>
  </assignmentRelation>
</assignment>

4. Add a view for the WO application roles

Everything is simple here, by poking around I managed to find the place where it is set

CONFIGURATION\System\Admin GUI Configuration\Object collection view

Add a view named WO VIEW

Here we will use the minimum filling. And we can also, for example, write in Column what we want to see, but in Display we can’t write anything, error 500 will appear immediately.

Fill in as shown in the picture

5. The Role of WO Core

In ADMINISTRATION\Role\All Roles we simply create a role (black) called WO Core. Go into it.

Click Edit Raw

We insert before

</role>

next code

<inducement id="2">
  <policyRule>
    <name>WO Core enigine</name>
    <policyConstraints>
      <alwaysTrue id="13">
        <name>yes</name>
      </alwaysTrue>
    </policyConstraints>
    <policyActions>
      <scriptExecution id="5">
        <name>some script</name>
        <object>
          <currentObject>
            <type>c:RoleType</type>
          </currentObject>
        </object>
        <executeScript
          xmlns:s="http://midpoint.evolveum.com/xml/ns/public/model/scripting-3">
          <s:pipeline list="true">
            <s:action>
              <s:type>execute-script</s:type>
              <s:parameter>
                <s:name>script</s:name>
                <s:value>
                  <code>
import com.evolveum.midpoint.xml.ns._public.common.common_3.*
import com.evolveum.midpoint.prism.delta.builder.*
import com.evolveum.midpoint.model.api.*
import static com.evolveum.midpoint.schema.constants.SchemaConstants.C_ORG_TYPE
import javax.xml.namespace.QName

//get host role WO info
role = midpoint.getObject(RoleType.class, input.oid)
actionId = basic.stringify(role.costCenter)
userId = basic.stringify(basic.getExtensionPropertyValue(role, "http://example.com/xml/ns/mySchema", "DROLER_owner"))
roleId = basic.stringify(basic.getExtensionPropertyValue(role, "http://example.com/xml/ns/mySchema", "DROLER_role"))
//find and get user from role WO
query_user = midpoint.queryFor(UserType.class, "personalNumber="$userId"") 
result_USER = midpoint.searchObjects(query_user)
//find and get role from role WO
query_role_ass = midpoint.queryFor(RoleType.class, "identifier="$roleId"") 
result_ROLE_ASS = midpoint.searchObjects(query_role_ass)


//if role and user exists
if (result_USER  &amp;&amp; result_ROLE_ASS &amp;&amp; basic.stringify(role.activation.administrativeStatus) != "DISABLED")
{
user_oid = basic.stringify(result_USER.oid)
user = midpoint.getObject(UserType.class, user_oid)

if (actionId == 'DELETE')
{
def assignmentsToDelete = []
for (a in user.assignment) {
if (a.targetRef?.oid == basic.stringify(result_ROLE_ASS.oid)) {
def removeAssignment = new AssignmentType()
removeAssignment.id = a.id
assignmentsToDelete.add removeAssignment.asPrismContainerValue()
}
}
if (!assignmentsToDelete.empty) {
//log.info "Assignments to delete: " + assignmentsToDelete


delta = prismContext.deltaFor(UserType.class).item(UserType.F_ASSIGNMENT).delete(assignmentsToDelete).asObjectDelta(user.oid)
//log.info "Deleting"
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'OK: Deleted role identfier:' + roleId + ' from user personalNumber:' + userId
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'DONE'
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DOCUMENTATION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, null)
} else {
roleDescription = 'WARNING: User with personalNumber:' + userId + ' did not have assigned role with identfier:' + roleId 
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'DONE'
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DOCUMENTATION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, null)
}
}

if (actionId == 'ADD')
{
orgUnit = new ObjectReferenceType()
orgUnit.setOid(result_ROLE_ASS.oid)
orgUnit.setType(RoleType.COMPLEX_TYPE)
addAssignment = new AssignmentType()
addAssignment.setTargetRef(orgUnit)
def delta = []
delta = prismContext.deltaFor(UserType.class).item(FocusType.F_ASSIGNMENT).add(addAssignment.asPrismContainerValue()).asObjectDelta(user.oid)
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'OK: Added role identfier:' + roleId + ' to user personalNumber:' + userId
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'DONE'
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DOCUMENTATION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, null)

}
} else {
//if role or user does not exists
if (!result_USER &amp;&amp; result_ROLE_ASS &amp;&amp; basic.stringify(role.activation.administrativeStatus) != "DISABLED")
{

roleDescription = 'ERROR: User personalNumber:' + userId + ' not present in Midpoint!'
owdelta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).add(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(owdelta, ModelExecuteOptions.createRaw())}

if (result_USER &amp;&amp; !result_ROLE_ASS &amp;&amp; basic.stringify(role.activation.administrativeStatus) != "DISABLED")
{roleDescription = 'ERROR: Role identfier:' + roleId + ' not present in Midpoint!'
owdelta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(owdelta, ModelExecuteOptions.createRaw())}

if (!result_USER &amp;&amp; !result_ROLE_ASS &amp;&amp; basic.stringify(role.activation.administrativeStatus) != "DISABLED")
{roleDescription = 'ERROR: Role identfier:' + roleId + ' and User personalNumber:' + userId + ' not present in Midpoint!'
owdelta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(owdelta, ModelExecuteOptions.createRaw())}

}


</code>
                </s:value>
              </s:parameter>
            </s:action>
          </s:pipeline>
        </executeScript>
      </scriptExecution>
    </policyActions>
  </policyRule>
  <condition>
    <expression>
      <script>
        <code>import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType;
if (ActivationStatusType.ENABLED)
{return true}</code>
      </script>
    </expression>
  </condition>
</inducement>

policyConstraints – it is written that the script is always launched but

in condition – it is written that the assignment of this Policy Rule is active only if the role of the WO application is not blocked

6. Configure Resource

We need a data file to simulate the WO order system

In the folder /opt/midpoint/var/ we put the file wo.csv with the contents

wo_num;wo_user;wo_role;wo_action;wo_satus
1;47128562bad;ROLE2144255;ADD;todo
2;47128562;ROLE2144255;ADD;todo
3;47128562;ROLE2144255;DELETE;todo
4;47128562;ROLE2144255;DELETE;todo

wo_num – unique application number in the WO system

wo_user is the employee number in Midpoint in the personalNumber attribute

wo_role is the role number in Midpoint in the identifier attribute

wo_action is either ADD or DELETE action

wo_satus – this is the status if it is not DONE Midpoint will try to execute this request

Create your users and roles and fill in the appropriate attributes

Create a resource, go to ADMINISTRATION\Resources\New Resorce\From Scratch and select CsvConnector

Next, as in the pictures, we write our resource name WO ticket system from CSV

Path to file /opt/midpoint/var/wo.csv

We specify a unique identifier in the wo_num system. Midpoint really needs it.

And that's it, further further

Go to the created resource, click Edit Raw

and add before

<capabilities>

next code

<schemaHandling>
        <objectType id="7">
            <kind>entitlement</kind>
            <intent>intent WO</intent>
            <displayName>WO</displayName>
            <default>true</default>
            <delineation>
                <objectClass>ri:AccountObjectClass</objectClass>
                <filter>
                    <q:text>attributes/wo_satus not contains 'DONE'</q:text>
                </filter>
            </delineation>
            <focus>
                <type>c:RoleType</type>
                <archetypeRef oid="42ed9c9f-693d-4bfe-9cfe-28656b8ee9da" relation="org:default" type="c:ArchetypeType">
                    <!-- WO ArcheType -->
                </archetypeRef>
            </focus>
            <attribute id="9">
                <ref>ri:wo_num</ref>
                <inbound id="10">
                    <name>01 inbound</name>
                    <strength>strong</strength>
                    <target>
                        <path>identifier</path>
                    </target>
                </inbound>
                <inbound id="11">
                    <name>02 inbound</name>
                    <strength>strong</strength>
                    <expression>
                        <script>
                            <code>"WO:" + input.padLeft(14,'0')</code>
                        </script>
                    </expression>
                    <target>
                        <path>name</path>
                    </target>
                </inbound>
                <inbound id="472">
                    <name>03 add POLICY to WO</name>
                    <lifecycleState>active</lifecycleState>
                    <strength>strong</strength>
                    <expression>
                        <assignmentTargetSearch>
                            <targetType>c:RoleType</targetType>
                            <oid>d489d8d8-b0e5-4e9e-a078-46e2be7f6de0</oid>
                        </assignmentTargetSearch>
                    </expression>
                    <target>
                        <path>$focus/assignment</path>
                    </target>
                </inbound>
            </attribute>
            <attribute id="12">
                <ref>ri:wo_role</ref>
                <inbound id="13">
                    <name>04 inbound</name>
                    <strength>weak</strength>
                    <target>
                        <path>extension/DROLER_role</path>
                    </target>
                </inbound>
            </attribute>
            <attribute id="14">
                <ref>ri:wo_user</ref>
                <inbound id="15">
                    <name>05 inbound</name>
                    <strength>weak</strength>
                    <target>
                        <path>extension/DROLER_owner</path>
                    </target>
                </inbound>
            </attribute>
            <attribute id="16">
                <ref>ri:wo_action</ref>
                <inbound id="17">
                    <name>06 inbound</name>
                    <strength>weak</strength>
                    <target>
                        <path>costCenter</path>
                    </target>
                </inbound>
            </attribute>
            <attribute id="21">
                <ref>ri:wo_satus</ref>
                <outbound>
                    <name>01 outbound</name>
                    <strength>strong</strength>
                    <source>
                        <path>$focus/documentation</path>
                    </source>
                    <condition>
                        <script>
                            <code>documentation == 'DONE'</code>
                        </script>
                    </condition>
                </outbound>
            </attribute>
            <correlation>
                <correlators>
                    <items id="30">
                        <item id="31">
                            <ref>c:identifier</ref>
                        </item>
                    </items>
                </correlators>
            </correlation>
            <synchronization>
                <reaction id="23">
                    <situation>unmatched</situation>
                    <actions>
                        <addFocus id="26"/>
                    </actions>
                </reaction>
                <reaction id="24">
                    <situation>linked</situation>
                    <actions>
                        <synchronize id="27"/>
                    </actions>
                </reaction>
                <reaction id="25">
                    <situation>unlinked</situation>
                    <actions>
                        <synchronize id="28"/>
                    </actions>
                </reaction>
            </synchronization>
        </objectType>
    </schemaHandling>

This is our schemaHandling

<delineation> – a filter is registered, by which we take records for which wo_status is not DONE

<focus> – it is written that we will create roles and assign them an archetype created earlier

<attribute id="*"> – it is written what Midpoint takes and where it puts it, it groups mappings by attribute, here there can be both inbound and outbound

<correlation> – by which attribute the binding occurs

<synchronization> – and it describes what to do during synchronization, if we don’t have an application, we create it:

                <reaction id="23">
                    <situation>unmatched</situation>
                    <actions>
                        <addFocus id="26"/>
                    </actions>
                </reaction>

All this is in the GUI, let's look at Mappings, here the GUI doesn't understand everything

03 add POLICY to WO – here the assignment to all WO roles is hardcoded, applications have a role with WO core, GUI does not understand this. The WO Core OID is registered for you will be different, it can be found in CONFIGURATION\Repository Object\All object\Select type Role or dig into RAW in WO Core

<inbound id="472">
                    <name>03 add POLICY to WO</name>
                    <lifecycleState>active</lifecycleState>
                    <strength>strong</strength>
                    <expression>
                        <assignmentTargetSearch>
                            <targetType>c:RoleType</targetType>
                            <oid>d489d8d8-b0e5-4e9e-a078-46e2be7f6de0</oid>
                        </assignmentTargetSearch>
                    </expression>
                    <target>
                        <path>$focus/assignment</path>
                    </target>
                </inbound>

02 inbound – takes a number from the wo_num system and puts it in name in Midpoint. The main thing for him is that this name is unique in Midpoint, so we add WO: and zeros to it via Script

"WO:" + input.padLeft(14,'0')

04 inbound, 05 inbound, 06 inbound – for these mappings if you click on them on the right, near the trash can, pencil with paper, a window with more settings will open. I don't want Resource to always overwrite these attributes for me as in itself, I plan to edit them in Midpoint therefore Strength is set to Weak, if they are already filled in the role of WO application the resource will not change them.

Outbound mappings that are written to the WO system via the resource are hidden right there in the right tab

I write what is in Midpoint in the documentation in the attribute in the resource wo_status, but not always, and when the documentation has the word DONE. If you click on the pencil, it is written there in Condition

Almost everything, all that remains is to configure the synchronization launch, it is not written in the RAW code of the resource, so let's go by the pictures.

In the resource, go to Defined Task and click + and select Reconcilation task

Fill in as shown in the picture

Here, note that I have set the WO_administrator user as Owner, all requests created during synchronization will be from him. You can leave it empty here, or you can also create a WO_administrator and give him the Superuser role (authorization in Midpoint is a separate amazing story).

Here you can set an interval that will run every ten seconds or a Cron-like like “5 5 21 * * ?” at 5 seconds 5 minutes at 21:00.

Next, at the end, click Save & Run

And that's it, that's how easy it is to configure Midpoint for your legacy application systems!

In Roles\Work Orders there is now a picture like this – based on test data!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *