Focus Processing

Last modified 16 Oct 2024 08:15 +02:00
Scientists study the world as it is; engineers create the world that has never been.
— Theodore von Kármán

Identity management systems are often seen as integration engines that move data from one system to another system. This is indeed a very important part of the identity management functionality. However, the things identity management systems do internally are crucial to all identity management solutions - especially those that deal with identity governance and regulatory compliance.

MidPoint does quite a lot of things that may not be entirely obvious on the outside. There are rules to apply, processes to drive and policies to enforce. Those things are gaining utter importance at that strange boundary where identity management becomes identity governance. MidPoint has powerful mechanisms to support identity governance. We will get to them later. We need to explain the basic mechanisms of midPoint internal processing first.

This chapter is about processing of focus, which is a midPoint term for object that is at the center of computation. The focus is usually a user, therefore this chapter is about the internal processing about the user object itself. It deals with handling of user properties such as determining username, e-mail address and composing full name. Moreover, it deals with other aspects of internal processing, such as automatic assignment of roles.

Object Templates

Data that flow into midPoint are seldom complete and clean. Quite the contrary. The data coming from the "feeds" are often incomplete, they are not very precise, and sometimes several sources may not even agree on a value for a particular data item. Inbound mappings can be used to sort out some of these problems. However, inbound mappings are designed to work in isolation. They work only for one particular resource and one particular attribute. We often need to gather data from several resources, and then look at all of them at once. Inbound mappings cannot do that very well, as they are tied to the resource. However, inbound mappings can be used to gather all the relevant data in the user object. Then we can have a look at user object, where those data are gathered, and process them all together. That is what object templates do. Object template can be used to do variety of things to all kinds of midPoint objects.

Simply speaking, object template is a set of definitions and mappings that is applied to a particular midPoint object. For example, user template is applied to all user objects. The mappings in the object template can produce new values for the object. For instance, a very typical use of object template is computation of user’s full name:

object-template-user-simple.xml
<objectTemplate oid="22f83022-b76d-11e9-8a30-6ffc11b23016">
    <name>User Template</name>
    <item>
        <ref>fullName</ref>
        <mapping>
            <strength>weak</strength>
            <source>
                <path>givenName</path>
            </source>
            <source>
                <path>familyName</path>
            </source>
            <expression>
                <script>
                    <code>givenName + ' ' + familyName</code>
                </script>
            </expression>
        </mapping>
    </item>
</objectTemplate>

The mapping above computes the value of user’s fullName property from givenName and familyName by using a simple Groovy expression. It is a weak mapping, therefore it computes the full name only in case that it is not present already.

Import of object template definition into midPoint does not do much. The template is not used yet, it just stored in midPoint repository. There can be several object templates for different types of objects, archetypes or organizations at the same time. MidPoint does not know how to use the template, unless it is specified in a configuration. The simplest and most common way to use an object template is to configure use in the archetype.

<archetype oid="00000000-0000-0000-0000-000000000702">
    <name>Person</name>
    <archetypePolicy>
        ...
        <objectTemplateRef oid="22f83022-b76d-11e9-8a30-6ffc11b23016"/>
    </archetypePolicy>
    ...
</archetype>

Alternatively, object template can be configured globally in system configuration object:

<systemConfiguration>
    ...
    <defaultObjectPolicyConfiguration>
        <type>UserType</type>
        <objectTemplateRef oid="22f83022-b76d-11e9-8a30-6ffc11b23016"/>
    </defaultObjectPolicyConfiguration>
    ...
</systemConfiguration>

This configuration activates the object template for use by all objects of UserType type. Therefore, this template is applied to all midPoint users.

Even though object templates can be set globally, it is usually better to apply object template using archetypes. Use of archetypes provides finer control over content of individual objects. E.g. we want to generate full name by concatenating given name and family name for persons (users with Person archetype), yet we do not want to do that for non-person users (such as administrator).

User interface can be used to set up and activate object templates as well. Navigate to menu:Configuration[System > Policies > Object policies], create a new policy using the btn:New[] button. Specify reference to your template, select User in type field and click btn:Save[].

Object template is applied every time an object is created, changed or explicitly recomputed (e.g. on reconciliation). Object template is applied after all the inbound mappings are processed. Inbound mappings often copy important data to the object, therefore the template can work on data that are summarized from all the resources.

Item Definitions

We have already seen an example of object template used to apply a mapping on fullName property of midPoint users. This was a very simple example. Object template can also do other tricks.

The processing of an object template is usually focused on particular items of an object. Therefore, almost all the functionality of object template is located in item element that references a particular item by its path:

<objectTemplate oid="22f83022-b76d-11e9-8a30-6ffc11b23016">
    <name>User Template</name>
    <item>
        <ref>fullName</ref>
        ...
    </item>
    <item>
        <ref>assignment</ref>
        ...
    </item>
</objectTemplate>

The most common use of object template is to evaluate mappings on items, such as the mapping to determine user’s full name above. Therefore, the mappings evaluated by the template are pre-configured with convenient default settings. Target of the mapping is automatically set to the item for which it is specified. Sources of the mapping need to be defined explicitly. The basic idea is, that the user template should take properties of user as inputs. In other words, the template works on the same object both as input and output.

Unlike inbound and outbound mappings, object template mappings often use static (literal) values or a very simple expressions to determine the value. However, in object templates, mapping conditions are often used to control application of the value. The easiest way to explain this is to use an example:

<objectTemplate>
    ...
    <item>
        <ref>description</ref>
        <mapping>
            <source>
                <path>extension/hatSize</path>
            </source>
            <expression>
                <value>WARNING: Big brain!</value>
            </expression>
            <condition>
                <script>
                    <code>hatSize &gt; 60</code>
                </script>
            </condition>
        </mapping>
    </item>
    ...
</objectTemplate>

This mapping is applied to description property of the user. The mapping sets a fixed warning text specified as text literal using value expression evaluator. However, the mapping is not setting that value for all the objects. The mapping is applied only for objects that satisfy the condition. The condition is set to trigger for all the users that have hat size larger than 60.

Mappings are made to be relativistic. This means that mappings react to changes. The same principle applies to mapping conditions - they are relativistic too. Therefore, the mapping reacts to changes in its sources, as well as changes in the condition state. When user’s head grows, and the hat size changes to a value over 60, then the mapping adds the warning. When the user’s head shrinks, then the warning is removed.

It may look that this mechanism is unnecessarily complicated, if you look at single-valued properties only. It all starts to make sense in case of multi-value items, such as the assignments. We will get to that in the next section.

Schema

Mappings are the things that object template does almost all the time. However, the template can also do other interesting things. First of all, object template can tweak the schema. MidPoint comes with quite a rich schema, ready to be used. However, the schema is not a perfect fit for all the deployments. Schema chapter described a method to extend the schema with custom items. However, what we should do if we want to change the built-in schema of midPoint? The schema is hard-coded to midPoint, even compiled into midPoint source code. It is not that easy to change the built-in schema. Yet, object template provides a mechanism to customize the use and presentation of built-in schema. The item specification can be used to modify the way how midPoint applies the schema:

<objectTemplate>
    ...
    <item>
        <ref>givenName</ref>
        <displayName>First Name</displayName>
    </item>
    <item>
        <ref>additionalName</ref>
        <displayName>Middle Name</displayName>
    </item>
    <item>
        <ref>familyName</ref>
        <displayName>Last Name</displayName>
    </item>
    ...
</objectTemplate>

The "additional name" is a nice and generic term that can fit many cultural environment. However, it is not very usual or intuitive in cultures that are not used to such a generic term (which means pretty much all the cultures). Therefore, almost all midPoint deployments that chose to use this property would like to rename it to something that feels more natural. Similarly, "given name" and "family name" do not fit well in all the cultures. We have expected that. Therefore, object template can be used to modify some aspects of built-in schema, such as the display labels.

Object template can also be used to override object multiplicity, especially to change mandatory item into optional. MidPoint insists on having name property set for all the users. However, we may be able to compute value of name property from other properties, such as other names of the user, employee number or other identifiers. Therefore, we do not want to present name item as mandatory in the user interface. Therefore, we would allow midPoint administrator to leave name field blank in the user interface when creating a new user. However, user interface is strictly driven by the schema, as are all the other midPoint components. As name is mandatory in the schema, the user interface insists that the name field must be filled in.

This behavior can be changed in the object template:

<objectTemplate>
    ...
    <item>
        <ref>name</ref>
        <limitations>
            <layer>presentation</layer>
            <minOccurs>0</minOccurs>
        </limitations>
        <mapping>
            ...
        </mapping>
    </item>
    ...
</objectTemplate>

This configuration makes the name property optional for the presentation purposes. This means that the user interface treats name as optional field. However, core midPoint engine still requires name property to have a value. This gives object template a chance to generate the value for name property.

However, the name field is still rendered as read-write item in the user interface. We do not want that, as name is supposed to be immutable identifier in our deployment. We do not want anyone to change the name once it was generated. Therefore, we would like to present name as read-only item in the user interface. This can also be achieved by object template, by using access configuration:

<objectTemplate>
    ...
    <item>
        <ref>name</ref>
        <limitations>
            <layer>presentation</layer>
            <minOccurs>0</minOccurs>
            <access>
                <read>true</read>
                <add>false</add>
                <modify>false</modify>
            </access>
        </limitations>
        <mapping>
            ...
        </mapping>
    </item>
    ...
</objectTemplate>
Even immutable identifiers may need to change occasionally. There may be a bug in the identifier generator, or we may need to manually change identifier to match the reality. Theoretically, every piece of the solution should play by the rules. Yet, we know that rules have exceptions in the practice. Therefore, privileged users such as system administrator should be able to change the identifiers when it is really needed. The proper way how to do this would be to use authorizations, and not object template. Unfortunately, we do not know how to use authorizations yet. Therefore, this solution will have to do, at least for now.

Perhaps the most extreme measure to change presentation of midPoint schema is ability to eliminate certain item entirely. In fact, this happens quite often. MidPoint schema is rich, and many deployments do not use all the items defined in midPoint schema. It makes little sense to present the items that are not used, therefore there is a way to tell midPoint that we want to completely ignore an item:

<objectTemplate>
    ...
    <item>
        <ref>organization</ref>
        <limitations>
            <layer>presentation</layer>
            <processing>ignore</processing>
        </limitations>
    </item>
    ...
</objectTemplate>

Object template can be used to do further tricks. It can be used to associate value enumeration with an item, e.g. to apply lookup table to a particular item. Object templates can be used to set up a validation expression for items - and a couple of other things. More on that later.

Includes

There are many ways to apply an object template to an object. The template can be set globally in system configuration, it can be set by an archetype or even by an organizational unit. However, only one object template can be active for any particular object at one time. Yet, there are often mappings that need to apply universally. For example, we may want to generate full name using the same algorithm for all users, regardless of their archetype. We may also want to automatically assign some roles to all users regardless of their organizational units. For that reason there is mechanism to include one object template in another:

<objectTemplate oid="22f83022-b76d-11e9-8a30-6ffc11b23016">
    <name>Default User Template</name>
    <item>
        <ref>fullName</ref>
        <mapping>
            ...
        </mapping>
    </item>
</objectTemplate>
<objectTemplate oid="60eab6a8-ba87-11e9-b9a3-bbb8418de4d5">
    <name>Special User Template</name>
    <includeRef oid="22f83022-b76d-11e9-8a30-6ffc11b23016"/>
    <item>
        <ref>employeeNumber</ref>
        <mapping>
            ...
        </mapping>
    </item>
</objectTemplate>

In this case, the special user template includes all the mappings from default user template. Therefore, both mapping for employeeNumber and mapping for fullName will be processed.

Automatic Role Assignment

In traditional role-based access control (RBAC) models, roles are supposed to be manually assigned to users. That’s the whole point of traditional RBAC: simplify access control by using roles instead of low-level permissions. However, current access control models are all but traditional - they can be described as flexible, dynamic, adaptive, or by any similar term that a marketing team invented yesterday. Whatever are the access control models called, they all rely on dynamism: ability to control privileges automatically as a reaction to changing environments.

Even though midPoint started with role-based access control mechanism, we have always considered this to be dynamic form of RBAC. MidPoint RBAC is not fixed to static roles. Roles can be assigned and unassigned automatically and dynamically. This is a crucial element in all midPoint deployments, therefore this is the right place to describe the mechanisms.

Automatic Role Assignment in Object Template

Object templates are very flexible, they can be used for a lot of different things. Yet, there is one particular usage of object template that appears in almost every deployment. It is an ability to assign roles automatically.

The basic idea is quite simple. When it comes to midPoint schema, an assignment is just an ordinary item. Therefore, we can use object template mapping to populate that item with appropriate value. When done properly, it gives us automatic assignment of roles. Like this:

<objectTemplate>
    ...
    <item>
        <ref>assignment</ref>
        <mapping>
            ...
        </mapping>
    </item>
    ...
</objectTemplate>

The trick here is to set up the mapping properly. The simplest case is a conditional assignment of a role. Let’s suppose that we want to assign a Hatter role to everybody that has provided a hat size in user profile. We already know how to do that, we can use mapping condition:

<role oid="c38a5e6e-b783-11e9-b82f-ebb94fb5b6ec">
    <name>Hatter</name>
    ...
</role>
object-template-user.xml
<objectTemplate>
    ...
    <item>
        <ref>assignment</ref>
        <mapping>
            <source>
                <path>extension/hatSize</path>
            </source>
            <expression>
                <value>
                    <targetRef oid="c38a5e6e-b783-11e9-b82f-ebb94fb5b6ec" type="RoleType">
                </value>
            </expression>
            <condition>
                <script>
                    <code>hatSize as Boolean</code>
                </script>
            </condition>
        </mapping>
    </item>
    ...
</objectTemplate>

Simple, isn’t it? The value part in the expression is a content of a new assignment that we want to create. The assignment will be created when hatSize property has a non-null, non-empty and non-zero value (that is a built-in evaluation of booleans in Groovy). The real trick here is the relativity of mapping conditions. Assignments are multi-valued. We do not want to ruin all the other assignments that the user has. We just want to add (or remove) one particular value, leaving other values untouched.

When it comes to multi-valued items, it is extremely important to know when to add a value and when to remove one. Therefore, midPoint evaluates the condition twice. The condition is evaluated for an object before a change is applied (old object) first. The condition is evaluated for an object after the change is applied (new object) once again. When the condition changes from false to true, Hatter role is assigned (added). When the condition changes from true to false, Hatter role is unassigned (removed). Other assignment values are not changed by this mapping. Therefore, many assignment mappings can happily coexist.

However, this is a very simple case. Typical midPoint deployments have large number of roles. It is (theoretically) possible to create mappings like this for each and every role that has to be assigned automatically. However, that would be a lot of repetitive work. Even worse, it is likely to become a major maintenance nightmare in the future. We engineers are creative people, we do not really like repetitive work - and we really hate maintenance nightmares. Therefore, it is perhaps no big surprise that there is a better way to do this.

The most common use case for automatic role assignment is to look up the role using some of its properties. For example, let’s suppose that our HR system provides job codes for our employees. Therefore, we have extended midPoint schema with a custom property jobCode:

<xsd:schema targetNamespace="http://example.com/xml/ns/midpoint/schema">
    ...
    <xsd:complexType name="UserTypeExtensionType">
        <xsd:annotation>
            <xsd:appinfo>
                <a:extension ref="c:UserType"/>
            </xsd:appinfo>
        </xsd:annotation>
        <xsd:sequence>
            ...
            <xsd:element name="jobCode" type="xsd:string"
                         minOccurs="0" maxOccurs="1"/>

            ...
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

A user object that is created from an HR record looks like this:

<user>
    <name>jones</name>
    <extension>
        <exmpl:jobCode>S007</exmpl:jobCode>
    </extension>
    <fullName>Jack Jones</fullName>
    ...
</user>

Then we do similar extension for roles. We extend role schema with custom autoassignJobCode property:

<xsd:schema targetNamespace="http://example.com/xml/ns/midpoint/schema">
    ...
    <xsd:complexType name="RoleTypeExtensionType">
        <xsd:annotation>
            <xsd:appinfo>
                <a:extension ref="c:RoleType"/>
            </xsd:appinfo>
        </xsd:annotation>
        <xsd:sequence>
            ...
            <xsd:element name="autoassignJobCode" type="xsd:string"
                         minOccurs="0" maxOccurs="1"/>

            ...
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

Then we set up the roles:

<role oid="a1572de4-b9b9-11e9-af3e-5f68b3207f97">
    <name>Sales Manager</name>
    <extension>
        <exmpl:autoassignJobCode>S006</exmpl:autoassignJobCode>
    </extension>
    ...
</role>
<role oid="b93af850-b9b9-11e9-8c2c-dfb9a89635a0">
    <name>Sales Agent</name>
    <extension>
        <exmpl:autoassignJobCode>S007</exmpl:autoassignJobCode>
    </extension>
    ...
</role>
<role oid="b9d2b604-b9b9-11e9-bbc4-17d8e85623b4">
    <name>Sales Assistant</name>
    <extension>
        <exmpl:autoassignJobCode>S008</exmpl:autoassignJobCode>
    </extension>
    ...
</role>

We are almost there. The final part of this puzzle is an object template mapping that automatically assigns the roles according to job code. Naive solution would be to create one mapping for each job code. We do not want that, we want something smarter. We want a single mapping that can work for all these roles. Such mapping needs to dynamically look up the role when it is evaluated. Of course, it is possible to create such mapping in Groovy script. However, that is not entirely straightforward. As this use case is a very common one, there is quite an easy method to do that in midPoint. Role autoassignment is a part of almost every identity management solution in one form or another. Therefore, we have created a special expression evaluator to make this job easy. Enter assignmentTargetSearch:

object-template-user.xml
<objectTemplate>
    ...
    <item>
        <ref>assignment</ref>
        <mapping>
            <source>
                <path>extension/jobCode</path>
            </source>
            <expression>
                <assignmentTargetSearch>
                    <targetType>RoleType</targetType>
                    <filter>
                        <q:text>extension/autoassignJobCode = $jobCode</q:text>
                    </filter>
                </assignmentTargetSearch>
            </expression>
        </mapping>
    </item>
    ...
</objectTemplate>

This may look a bit confusing at the first sight, but do not panic. Not yet, anyway. It all makes perfect sense once it is explained. The code above is a part of user template. It is a mapping that is producing assignments. This mapping has an ordinary source which is user’s jobCode property. This mapping also has an expression, even though that expression is somehow extraordinary. It is not the usual script, path or value expression. The expression evaluator is assignmentTargetSearch in this case. This is a special evaluator that looks for assignment target (e.g. a role), creates a complete assignment from that target, and provides that assignment as an output. The interesting part here is the way how assignmentTargetSearch looks for assignment target. First of all, there is a targetType clause, which tells the expression to look for roles as assignment target. Then there is a search filter. We have already seen midPoint search filters a couple of times, for example as a method to look up connector reference. This is yet another use for search filters. In this case, the filter is used to look up suitable role in midPoint repository. The filter looks up the roles by the autoassignJobCode value. However, what value are we looking for? Static value, such as S007, is not going to help here. We need to make this query dynamic and smarter. We simply use a variable instead of static value. In this case, we use $jobCode variable. Job code is a source for this mapping, therefore it is present as a variable in all the expressions of the mapping, including the assignmentTargetSearch expression. When the expression is processed for Jack Jones, value S007 is substituted. That filter is used to look for a role, which results in the Sales Agent role. This role is used to construct an assignment with the role as its target:

<assignment>
    <targetRef oid="b93af850-b9b9-11e9-8c2c-dfb9a89635a0" type="RoleType"/>
</assignment>

That is it! That assignment is added to the user object. Which means that Jack Jones has that role assigned:

<user>
    <name>jones</name>
    <extension>
        <exmpl:jobCode>S007</exmpl:jobCode>
    </extension>
    <assignment>
        <targetRef oid="b93af850-b9b9-11e9-8c2c-dfb9a89635a0" type="RoleType"/>
    </assignment>
    <fullName>Jack Jones</fullName>
    ...
</user>

This single mapping works for all the cases of all the current job codes, and even for all future job codes. That is how we like it. One simple mapping solves many problems.

Why not Groovy?
Of course, this could all have been done with one smart groovy script instead of assignmentTargetSearch expression. You are free to do it in Groovy, if you prefer it that way. It will work. However, as assignment is a complex data structure, the assignmentTargetSearch expression makes work with assignment much easier. It can set up validity constraints, relation and other assignment details. It can also create the target on demand in case the target is not found. It is quite powerful. Perhaps the most important detail is that assignmentTargetSearch expression implements the use cases that are common in almost every identity management deployment. Functionality of assignmentTargetSearch expression is maintained and tested as a native part of midPoint. Therefore, you can simply reuse it in every midPoint deployment, instead of copying, adapting, testing and bugfixing one big groovy script over and over again.

So far we have been talking about automatic assignment of roles. However, the mapping works both ways. It can automatically assign the roles, and it can also automatically unassign them. This is an effect of relativity of midPoint mappings. When mapping input changes, midPoint knows how it was changes, as it has a delta. Therefore, midPoint can use the mapping to compute both the values that were added as well as values that were removed. When jobCode of Jack Jones changes from S007 to X001, midPoint evaluates the expression for both values, old and new. Evaluation of the expression with old value S007 produces assignment for Sales Agent role. MidPoint knows that value S007 was removed, therefore it also removes assignment of Sales Agent role from the user.

When it comes to security, automatic unassignment of roles is even more important than automatic assignment. Principle of least privilege dictates that a users should have minimal access rights necessary for conducting their duties. MidPoint autoassignment mechanism take care of that, applying a rule to assign a role when it is necessary, while also using the same rule to unassign the role when it is no longer necessary. This mechanism is one of the building block of policy-driven RBAC mechanism, a dynamic role-based access control model which is a native part of midPoint.

MidPoint automatically assigns and unassigns the roles when there is appropriate autoassignment mapping. This works very well, it even works when there are several mappings that manage roles automatically. MidPoint executes them all and merges the results. However, roles that are managed automatically may clash with roles that are managed manually. Therefore, if a mapping specifies that a role should be unassigned, midPoint unassigns that role even if the role was assigned manually. MidPoint 4.8 does not really know the difference between role that was assigned automatically and manually.

There is a mechanism that can be used to make sure that manually-assigned roles are never automatically removed. Clever use of assignment subtype together with configuration of mapping range can be used to separate assignments that are managed manually from those that are managed automatically. However, the specific configuration is not entirely simple. Luckily, future versions of midPoint bring a significant improvement. midPoint 4.9 is using value meta-data to record origin of values. Such meta-data can be used to determine whether assignment was created automatically or manually. Mappings in midPoint 4.9 are set up in such a way that they would remove only the values that they themselves created, making the mechanism seamless. Therefore, if this situation is likely to occur in your deployment, it may be worth consider using midPoint 4.9.

The roles used in this section are in fact business roles, as they correspond to business responsibilities defined by job codes. Even though the roles were not explicitly marked as business roles in the examples above, they are properly marked in the example files by applying Business role archetype.

Autoassignment in Roles

Automatic assignment of roles in the object template is not the only option. This is midPoint, therefore there are usually several ways to do the same thing. The statements that control automatic assignment of roles can be placed in the roles themselves:

role-cook.xml
<role oid="9f6add7c-b9bf-11e9-abf6-2348fcd328f1">
    <name>Cook</name>
    ...
    <autoassign>
        <enabled>true</enabled>
        <focus>
            <mapping>
                <source>
                    <path>locality</path>
                </source>
                <condition>
                    <script>
                        <code>
                            locality?.norm == 'kitchen'
                        </code>
                    </script>
                </condition>
            </mapping>
        </focus>
    </autoassign>
</role>

The mapping in the autoassign part of the role is evaluated approximately at the same time as other object template mappings. The mapping has no expression. There is no need to. MidPoint prepares complete assignment data structure for this mapping as an input. The mapping just has to decide whether to apply that assignment to the user. That is what the condition is for. When the condition evaluates to true, the role is automatically assigned. When it evaluates to false, the role is unassigned.

The expression in this mapping is optional. If an expression is specified, then such expression can be used to further set up the assignment. For example, it can set assignment activation, relation, parameters and so on.

But wait, why is there this strange norm thing in the condition? Remember about polystrings? The locality property is a polystring. Therefore, it has orig part and norm part. In this case we want to compare the norm part, as the organizational unit name may be spelled as Kitchen or KITCHEN. However, in all those cases, the norm part will be kitchen.

If fact, there is little trap for the unwary here. The obvious way to specify the expression would be like this:

locality == 'kitchen'       // This is wrong!

However, such expression always returns false. The reason is that different data types are being compared. The locality property is polystring, while ‘kitchen’ is a string literal. Polystring and string are never equal regardless for their content. Therefore, this form of the expression is wrong. Following forms may be used instead:

locality?.orig == 'Kitchen'
locality?.norm == 'kitchen'
basic.stringify(locality) == 'Kitchen'

The later form is using stringify() method from basic midPoint function library. This method converts everything to string. Whatever data type is passed to this method the result is always a string that can be safely compared.

Let’s get back to role autoassignment. When autoassign mappings are specified in the roles, midPoint is processing the mappings in a way that is very similar to object template mappings. This has benefits, but there are also drawbacks.

The benefit of role autoassignment is manageability. The conditions are stored in roles themselves. Therefore, they are bound to the object that they assign. Autoassignment rule is right there, in front of administrator’s eyes. It may also be a benefit if delegated administration is used. E.g. a role owner manages both role definition and the autoassignment condition in the same object. In that case, this encapsulation of autoassignment rule in role definition is a huge benefit.

Beware of the expressions
Scripting expressions are very powerful. In fact, they are way too powerful for secure delegated administration. Unconstrained scripting expression can do pretty much anything. It can bring down the system, read memory, modify data, it can do whatever it likes to do. There are some safeguards that prohibit against accidental abuse, but a malevolent expression can easily circumvent them. If you allow a user to specify any expression, you are pretty much giving away keys to the kingdom. Therefore, do not do it. At least not now. MidPoint has ability to specify expression profiles. The goal of the profiles is to constraint expression to only allow safe operations. However, as scripting languages such as Groovy and JavaScript are very flexible and powerful, it is quite difficult to constraint them to a set of safe operations. Therefore, use scripting expression with utmost care.

Role autoassignment has another drawback which is performance. All the autoassignment mappings need to be evaluated every time a user is recomputed. This means that all the roles that contain the mappings need to be retrieved from midPoint repository. This may not be a big deal for a small deployment with thousands of users and hundreds of roles. However, the performance hit is likely to be significant as the number of users and roles grows. Therefore, role autoassignment is not enabled by default. It has to be explicitly enabled in system configuration:

<systemConfiguration>
    ...
    <roleManagement>
        <autoassignEnabled>true</autoassignEnabled>
    </roleManagement>
    ...
</systemConfiguration>

There is one more significant drawback of role autoassingment, which is quite obvious. The autoassignment mapping needs to be in every role. There is no way to use this mechanism to handle autoassignment of many roles with just one mapping. However, object template mappings can do that easily. Therefore, many deployments chose to implement automatic assignment of roles by the means of object template or inbound mappings.

Role autoassignment mechanism is also part of midpoint dynamic role-based access control mechanism, the policy-based RBAC. As such, autoassignment is an essential tool in midPoint access control policy toolbox.

Automatic Assignment in Synchronization

Automatic assignment of roles can be based on user properties or other complex factors, as we have seen above. However, it is far more common to assign privileges to users based on their birthright. Birthright is a term used in identity management that refers to privileges that are given to the users based on their very nature. This includes privileges given to all users, just because they are users. This can be further refined to individual user types, such as employees, students and suppliers. However, the overall principle of birthright is simple: privileges are assigned to user because the user has a specific type. It also means that the privileges are automatically removed or changed when user type changes.

When we deal with object types, archetypes immediately come to mind. Archetypes are indeed the right answer for management of birthright permissions. Archetypes are abstract roles, therefore they work as roles. We need to add some inducements into Person archetype, and all the persons automatically get the privileges. It is as simple as that.

<archetype oid="00000000-0000-0000-0000-000000000702">
    <name>Person</name>
    ...
    <inducement>
        ...
    </inducement>
</archetype>

However, how do we make sure that all users get the correct archetype? We have already seen that, back in Synchronization chapter. This can be configured in the schema handling part of resource definition.

resource-csv-hr.xml
<resource>
    ...
    <schemaHandling>
        <objectType>
            <displayName>Default Account</displayName>
            ...
            <focus>
                <type>UserType</type>
                <archetypeRef oid="00000000-0000-0000-0000-000000000702"/>
            </focus>
            ...

In this case, the objects that midPoint creates when synchronizing from HR resource are going to be users. Moreover, the users are going to have Person archetype.

However, this is company HR systems. The users created by synchronization from this systems are no mere persons, they are employees. We would like to assign birthright privileges to all employees, privileges that other users should not have. E.g. we want all the employees to automatically have account in company LDAP server.

The best way to do that in midPoint 4.8 is to create an employee role.

role-employee.xml
<role oid="86d3b462-2334-11ea-bbac-13d84ce0a1df">
    <name>Employee</name>
    <inducement>
        <construction>
            <!-- LDAP resource -->
            <resourceRef oid="8a83b1a4-be18-11e6-ae84-7301fdab1d7c" />
        </construction>
    </inducement>
</role>

This role specifies birthright privileges for employees. In this case there is just a single construction for a default LDAP account. This is usually a good start.

Now, how do we make sure all employees have this role? This is not an archetype, we cannot use the archetypeRef method as we have used above. Even though we cannot use the built-in support for birthright archetypes, we can still use inbound mapping. However, we have to use one little trick here. We want Employee role to be assigned to all users that originate from the HR record. Therefore, the mapping is not associated with any specific HR account attribute. Unfortunately, midPoint inbound mapping always need to be configured for a specific attribute. Therefore, we just choose one of the attributes that all employees have, such as empno attribute.

resource-csv-hr.xml
<resource>
    <name>HR System</name>
    ...
    <schemaHandling>
        <objectType>
            <displayName>Default Account</displayName>
            ...
            <attribute>
                <ref>empno</ref>
                <displayName>Employee number</displayName>
                <inbound>
                    <target>
                        <path>$focus/personalNumber</path>
                    </target>
                </inbound>
                <inbound>
                    <expression>
                        <value>
                            <!-- Employee role -->
                            <targetRef oid="86d3b462-2334-11ea-bbac-13d84ce0a1df" type="RoleType"/>
                        </value>
                    </expression>
                    <target>
                        <path>assignment</path>
                    </target>
                </inbound>
            </attribute>
            ...
</resource>

There are two inbound mappings for empno attribute here. One is the regular mapping copying value of empno HR attribute to personalNumber property of the user. The other is the mapping for Employee role. The mapping is not using the input value of empno at all, it always produces the same assignment for Employee role. Hence, all employees are getting that role.

Auxiliary archetypes
Clever user is uneasy here. We have mentioned auxiliary archetypes before. They look like an ideal concept for managing birthright privileges. Indeed, they are ideal for that purpose - or rather almost ideal, at least in midPoint 4.8. Support for auxiliary archetypes is quite limited in midPoint 4.8, especially support for management of auxiliary archetypes in midPoint user interface. That is one thing that will certainly get improved in future midPoint versions. Adventurous users may try to use auxiliary archetypes instead of birthright roles, especially in cases where user interface is not essential. However, for general-purpose deployment, use of birthright roles is still recommended.

Generating Unique Identifiers

There are some use cases that pop out in identity management solutions all the time. One such case is the problem of finding a unique identifier. This is a concern for almost any type of identifier, but it is particularly painful when it comes to usernames. In midPoint world, this means finding a value for name property. This property must be unique for almost all the data types that midPoint supports.

The rational way would be to base usernames on something that is already unique and immutable such as employee numbers or student identifiers. However, those tend to be long numbers, and people often hate them. Therefore, many deployments chose to base usernames on real names of the user. We can easily generate username for Alice Anderson. Maybe aanderson would be a good fit? It is nice, easy to remember and reasonably unique. We can do that with a simple object template mapping:

<objectTemplate>
    ...
    <item>
        <ref>name</ref>
        <mapping>
            <source>
                <path>givenName</path>
            </source>
            <source>
                <path>familyName</path>
            </source>
            <expression>
                <script>
                   <code>
                       givenName?.norm[0] + familyName?.norm
                   </code>
                </script>
            </expression>
        </mapping>
    </item>
    ...
</objectTemplate>

This can indeed work quite well - until Albert Anderson is hired. Then we need to get creative. Obviously, alanderson will not work here. What about alianderson and albanderson? Oh no, we have this ancient application in our company. That application allows only ten characters in the username, and alianderson is just too long. What is even worse, we would need to change Alice’s username for this to work. She will get really mad about it. Not to mention that if we go on with this scheme, we may need to change usernames for pretty much everybody, eventually. That won’t do. Let’s go the usual way. Let’s have aanderson and aanderson1. It is not entirely elegant, but it will do the job. Even better, Alice will not get mad. You know, she is really scary when she gets mad.

This use case is so common that even very early midPoint versions supported it. This feature is called iteration in midPoint terminology. The name suggests how the mechanism works. First step is an attempt to create a user object in a perfectly normal way. This means that username aanderson is generated for Albert Anderson. Then midPoint checks if that username is unique. In this case the username is not unique, as it is already taken by Alice. That is the point when midPoint starts iterating. MidPoint creates iteration token. Iteration token is a short string that changes in every iteration. In our case, the iteration token is initially set to 1. Then midPoint re-evaluates all the object template mappings. Mappings that are supposed to create unique values need to use that token. They should look like this:

<objectTemplate>
    ...
    <item>
        <ref>name</ref>
        <mapping>
            <source>
                <path>givenName</path>
            </source>
            <source>
                <path>familyName</path>
            </source>
            <expression>
                <script>
                   <code>
                       givenName?.norm[0] + familyName?.norm + iterationToken
                   </code>
                </script>
            </expression>
        </mapping>
    </item>
    ...
</objectTemplate>

When this mapping is evaluated for the first time, the iteration token is empty. Therefore, it will make no difference for the normal processing. When the mappings are re-iterated, the token is set to 1. Result of this mapping is aanderson1, which is unique username. Therefore, iteration stops there and normal processing continues. In case that even aanderson1 is not unique, the iteration continues. Usernames aanderson2, aanderson3 and other variants are tried. The iteration continues until a unique username is found, or until iteration limit is reached.

The expression to generate name as provided above is nice and simple. However, reality is not that simple. There are going to be users without given names or family names. Using the script above would produce some ugly null strings in that case. Some systems allow only certain number of characters in username. Real-world script has to account for that, generating more sensible usernames. Also, there is administrator user, which we usually do not want to rename. More sophisticated version of the script is already part of Person Object Template in default midPoint installation.

Iteration functionality is disabled by default. Therefore, any conflict in username will result in hard error. This makes sense, as no amount of iterations will make any difference until the iteration token is used in the expressions. We also want to set maximum number of iterations. E.g. there may be a bug in the mappings that may cause endless iterations. The iteration functionality can be enabled by specifying iterationSpecification element and setting iteration limit:

object-template-user.xml
<objectTemplate>
    ...
    <iterationSpecification>
        <maxIterations>5</maxIterations>
    </iterationSpecification>
    ...
</objectTemplate>

Iteration tokens are strings that are created from iteration number. It is the iteration number that really matters for midPoint. Iteration token can take variety of forms, it can be numeric, it may be alphanumeric, fixed length, variable length or anything else. Some mappings will not use the token at all. E.g. mappings that subsequently add letters from given name to the username. Therefore, both iteration number and iteration token are exposed to the mappings. There are two variables:

  • iteration variable contains iteration number in an integer form. It is always numeric, starting with zero (0). Iteration zero means normal processing. Iteration one happens after the first conflict.

  • iterationToken variable contains a string that is derived from the iteration number.

There is default algorithm that derives iteration tokens from iteration number. The algorithm is illustrated in following table.

Iteration The value of iteration variable The value of iterationToken variable

Normal processing

0

"" (empty string)

First iteration

1

"1"

Second iteration

2

"2"

The algorithm is designed to put empty value in the iterationToken during normal processing. The idea is that iterationToken variable can be safely used both for the normal processing and for the iterations. This is just a default algorithm, and it is certainly not going to fit all the deployments. Therefore, a custom mechanism to derive iteration token can be specified. For example, we may not like to have aanderson and aanderson1. Which one of these is number one and which is number two anyway? Let’s skip aanderson1 and let’s use aanderson2 for the first iteration. The iteration number cannot be changed as the iteration sequence is fixed. However, there is no problem for iteration 1 to produce iteration token "2". This can be achieved by specifying a custom algorithm for the token:

object-template-user.xml
<objectTemplate>
    ...
    <iterationSpecification>
        <maxIterations>5</maxIterations>
        <tokenExpression>
            <script>
                <code>
                    if (iteration == 0) {
                        return ''
                    } else {
                        return iteration + 1
                    }
                </code>
            </script>
        </tokenExpression>
    </iterationSpecification>
    ...
</objectTemplate>

This algorithm will produce sequence of aanderson, aanderson2, aanderson3 and so on.

Iteration number and iteration token is the same for the entire object template. All the mappings see the same value and all the mappings are recomputed when there is a need to re-iterate. Therefore, iteration token is not limited to username, it can be used in other mappings too. For example, use of the token in e-mail address is a very common case:

object-template-user.xml
<objectTemplate>
    ...
    <item>
        <ref>emailAddress</ref>
        <mapping>
            <source>
                <path>givenName</path>
            </source>
            <source>
                <path>familyName</path>
            </source>
            <expression>
                <script>
                   <code>
                       givenName?.norm + '.' + familyName?.norm
                           + iterationToken + '@example.com'
                   </code>
                </script>
            </expression>
        </mapping>
    </item>
    ...
</objectTemplate>

This mapping produces a sequence of albert.anderson@example.com, albert.anderson2@example.com, albert.anderson3@example.com and so on (assuming that customized token expression is also applied). Shared value of iterationToken means that the values of e-mail address are consistent with the values of username. If username of aanderson2 is generated, then the e-mail address is albert.anderson2@example.com. The same iteration token is used.

However, it all becomes interesting when it comes to e-mail addresses and other identifiers that are publicly exposed. It is one thing to have username aanderson2. That username is used to log into the system, but is it not very visible outside the system. However, an e-mail address is exposed to a lot of people. It may be strange to have e-mail address of albert.anderson2@example.com, while there is no other albert.anderson@example.com in the company. This can be solved by making the mapping for e-mail address smarter. It can ignore the iteration token, and it may try to create an e-mail address on its own. However, in that case it needs to explicitly check for uniqueness. There are two ways to do that. First method is to check for e-mail address uniqueness inside the e-mail mapping. There is a isUniquePropertyValue(…​) method in midPoint function library that is designed for this purpose:

<objectTemplate>
    ...
    <item>
        <ref>name</ref>
        <mapping>
            <source>
                <path>givenName</path>
            </source>
            <source>
                <path>familyName</path>
            </source>
            <expression>
                <script>
                   <code>
                       def plainAddress = givenName?.norm + '.' + familyName?.norm
                                         + '@example.com'
                       if (midpoint.isUniquePropertyValue(focus, 'emailAddress',
                                         plainAddress)) {
                           // Bingo! We have unique address
                       } else {
                           // Address not unique.
                           // We have to use iteration token here.
                       }
                   </code>
                </script>
            </expression>
        </mapping>
    </item>
    ...
</objectTemplate>

The problem with this approach is that there may be corner cases. We might need to force another iteration even if the username is unique. MidPoint checks only for uniqueness of username by default. However, it is possible that even if aanderson2 username is available, the albert.anderson2@example.com address is already taken. This may be an error in the data, administrator’s mistake, or it may be an artefact of retired Albert Anderson senior who worked in the company years ago, yet his e-mail address was never deprovisioned. The e-mail address mapping can detect this situation. However, what is the mapping supposed to do when it detects a problem? It makes no sense to have username aanderson2, and e-mail address albert.anderson3@example.com or albert.anderson.X@example.com or anything similar. What would make sense is to re-iterate and produce username aanderson3 and e-mail address albert.anderson3@example.com. That would be nice and consistent. However, the mapping cannot do that by itself. Therefore, there is another iteration expression for this purpose: post-iteration condition. It is a condition that gets executed after the iteration is completed. If the condition returns true, then the iteration is accepted as valid, and the generated values are used. If the condition returns false, then midPoint re-iterates and yet another iteration is tried.

<objectTemplate>
    ...
    <iterationSpecification>
        ...
        <postIterationCondition>
            <script>
                <code>
                    def email = ... // Code to generate or retrieve e-mail
                    return midpoint.isUniquePropertyValue(focus,
                            'emailAddress', email)
                </code>
            </script>
    </iterationSpecification>
    ...
</objectTemplate>

The code above does not do much in case the e-mail address is unique. It returns true, the iteration is accepted, and everything goes as usual. In case that the e-mail address is not unique, the code returns false. In that case, midPoint discards all the results of the iteration, increments iteration counter and re-tries the iteration.

There is yet another mechanism that can be used here: pre-iteration condition. It is a condition that is executed prior to iteration. If it returns true, then the iteration continues. If it returns false, then midPoint immediately re-iterates. The difference here is that this condition is executed before all the other mappings are evaluated. Therefore, it may be used to avoid evaluation of expensive mappings just to discard the values that they produce.

Identifier uniqueness and consistency
Finding identifier values and uniqueness checks are messy stuff. They are not entirely reliable. There is a delay between the time when uniqueness is checked and the time when the record is actually written into database. Therefore, strange things can happen. Duplicate identifiers may be generated or attempt to create a user may end up with an error, especially under high loads. The delay between check and write cannot be entirely avoided. We could lock the data during that time, but that would have significant impact on system performance. What we can do to improve the situation is to check the uniqueness on database level and gracefully handle the errors. This is currently implemented only for usernames and even for that the implementation is not perfect. Implementation of strict uniqueness constraints for other properties is possible, but it is no easy endeavor. The values need to be normalized, this can influence database schema and so on. Nevertheless, it is still feasible. In case you are interested, midPoint platform subscription is the best approach for you.

When it comes to human-friendly identifiers, there is yet another trouble. People tend to change their minds. They also like to have all kinds of crazy ideas, such as the urge to get married. The result is that the names of people change. In fact, they change surprisingly often. When user-friendly identifiers are used, change in user’s name usually means a change in the identifiers. This is known as the rename problem, and you can observe a glimpse of fear in the eyes of all experienced identity management engineers every time it is mentioned. Overall, midPoint handles renames very well. Primary identifier of any midPoint object is an OID, not a name. OIDs do not change. Therefore, as long as midPoint is concerned, nothing special happens when user’s name is changed. The change is picked up by mappings, recomputed and stored. However, iterations and uniqueness checks may complicate the things here. MidPoint remembers the iteration number for all objects that went through an iteration process. This is necessary to get the same results from the mappings every time that they are recomputed. Otherwise the identifiers may get re-generated on every recompute. However, there is a drawback to this approach. Let’s suppose that Carol Cooper had username ccooper2. She got married and now her name is Carol Cunningham. Even though there is no ccunningham in the system, her generated username will be ccunningham2. The iteration token is remembered and re-used during the rename process. The rename scenarios can be very treacherous. We always recommend to test them thoroughly in any project where user renames are possible.

Another drawback of those iterating algorithms is scalability and performance. Every time there is a conflict, the algorithm need to go through all the iterations that correspond to all the taken usernames. How many people named John Smith can be in a large user population? We can easily get to jsmith42. This means that the next John Smith needs to go through 42 iterations before the system figures out that the next available username is jsmith43. This gets worse with every John Smith added to the system. Therefore, this iterative approach is not suitable for generating identifiers that are likely to require a lot of iterations. Generating UNIX user and group numbers is a good example for identifiers that would surely cause a disaster if an iterative approach is used. Fortunately, there is another mechanism in midPoint that can support generation of such identifiers: sequences. More on that later.

Overall, the best strategy is to avoid using those generated human-friendly identifiers altogether. The best choice would be something that is already unique, immutable and reasonably short. Something like employee number, student identifier or partner ID are usually suitable. If that is not acceptable, then the second-best approach is to keep the algorithms simple. The simpler it is the less likely it is to fail.

Combining the Ingredients

It is time to put all the bits and pieces together. So far we have been talking about provisioning, inbound synchronization, schema, RBAC, archetypes and object templates. Let’s see how all the parts fit together:

Big picture

Everything starts with a synchronization process, whether it is reconciliation or live synchronization. Synchronization process invokes the connector for the source resource (Resource A). The connector retrieves the data from the source system. Shadow objects are created for all the source accounts as soon as the data set foot in midPoint. Correlation is evaluated for all new accounts to find their owners. Once we have owner of the account, we can execute inbound mappings. This is the way how account data are reflected to midPoint user object, which is a focus of the computation.

Next couple of steps is all about the focus. This is the part where object templates are executed, assignments and roles are evaluated. Assignments and roles may contain construction statements. Those statements are just collected at this stage. They are not evaluated yet. This focus policy phase of computation is all about the focus. Which means that user object is both the input and output of this computation.

Outbound phase takes place next. In this phase, the focus of the computation (user) is projected to accounts. This is the time when constructions are processed and the mappings inside them are evaluated. Those constructions were collected from the assignments in the previous phase. They are combined with outbound mappings specified in resource schema handling. All of that is mixed together, sorted to resource accounts, all the values are computed. This is also the time when attribute-level reconciliation takes place. We know what attributes the account should have, therefore we can compare that with the values that the attributes have in reality. When all of that is computed and processed, then a connector is used to update the target resource (Resource B).

This picture is still not entirely complete. It does not show policy rules, existence mappings, approval processes, hooks and good deal of other advanced features. Yet, this picture is good enough for now. It is good enough to create a simple, yet complete solution.

Complete Deployment Example

We have all that we need to create a simple but mostly complete identity management solution. Our environment and solution outline:

  • HR system is a data input. It exports employee data into a CSV file. Employee number is a primary key, there are first and last names and job code. There is no username or password.

  • We need to feed employee data into midPoint. Which means that we need to configure synchronization.

  • We need to generate unique and user-friendly username, compute full name and generate a random initial password.

  • We need to automatically assign roles based on job code from the HR system.

  • We need to automatically provision account to LDAP server and CRM database table.

We can do that if we put together all that we have learned so far. Even though this is still quite a simple example, the complete configuration is too large to put all of it into this book. It will take too much space. Moreover, after all the detailed explanation in the previous chapters, it would also get a bit boring. Therefore, we will show only the interesting pieces of the configuration here. Complete configuration can be found in the usual place. Please see Additional Information chapter for details. These files represent the final configuration, the expected state at the end of this chapter. Therefore, if you want to follow instructions in this chapter step-by-step, you have to choose appropriate parts of the files to import. Alternatively, you can just import everything, and use the following text as an explanation of the effects that you see.

Let us start with an HR resource. This is mostly the same resource definition as we have seen in the Synchronization chapter. However, there are few differences. First of all, the data feed is a bit different. We have a new jobcode column there. It looks like this:

hr.csv
"empno","firstname","lastname","jobcode"
"001","Alice","Anderson","S006"
"002","Bob","Brown","S007"
...

Of course, the HR resource definition has to reflect those changes. We have defined a new custom user property jobCode in our extension schema:

extension-example.xsd
<xsd:schema ...>
    <xsd:complexType name="UserExtensionType">
        <xsd:annotation>
            <xsd:appinfo>
                <a:extension ref="c:UserType"/>
            </xsd:appinfo>
        </xsd:annotation>
        <xsd:sequence>
            ...
            <xsd:element name="jobCode" type="xsd:string"
                                        minOccurs="0" maxOccurs="1">
                ...
            </xsd:element>
        </xsd:sequence>
    </xsd:complexType>
    ...
</xsd:schema>

The jobcode column is mapped to jobCode extension property in HR resource inbound mapping:

resource-csv-hr.xml
<resource oid="03c3ceea-78e2-11e6-954d-dfdfa9ace0cf">
    ...
    <schemaHandling>
        <objectType>
            ...
            <attribute>
                <ref>jobcode</ref>
                <displayName>Job code</displayName>
                <inbound>
                    <target>
                        <path>$focus/extension/jobCode</path>
                    </target>
                </inbound>
            </attribute>
            ...
        </objectType>
    </schemaHandling>
    ...
</resource>

There is one more interesting inbound mapping, a mapping that automatically assigns Employee role. All the records that come from the HR resource are employee records. Therefore, we want to assign Employee role to all of them. The easiest way to do that is to use inbound mapping of HR resource:

resource-csv-hr.xml
<resource oid="03c3ceea-78e2-11e6-954d-dfdfa9ace0cf">
    ...
    <schemaHandling>
        <objectType>
            ...
            <attribute>
                <ref>rempno</ref>
                ...
                <inbound>
                    <expression>
                        <value>
                            <!-- Employee role -->
                            <targetRef oid="86d3b462-2334-11ea-bbac-13d84ce0a1df"
                                       type="RoleType"/>
                        </value>
                    </expression>
                    <target>
                        <path>assignment</path>
                    </target>
                </inbound>
            </attribute>
            ...
        </objectType>
    </schemaHandling>
    ...
</resource>

This mapping is quite straightforward. Its expression produces a static targetRef value that is placed in user’s assignment. The strange thing here is the placement of this mapping. It is placed in the section that corresponds to empno attribute. This is inbound mapping, and it just has to be placed somewhere. Any reasonable attribute would do. It does not really matter into which attribute it is placed as it ignores attribute value anyway.

The rest of the mappings that are defined in the HR resource is a bit boring. The interesting thing is the mapping that is not there at all. The mapping for username (property name of the user object) is missing. We are not generating username in the inbound phase. We just do not have enough data to responsibly generate username just yet. Inbound phase is still running, user object is not fully populated yet. Let’s postpone the decision about username for later.

Synchronization part of the HR resource definition is also a pretty standard one. This resource is an authoritative source. Accounts are correlated by the empno column matching the personalNumber user property. Linked accounts are updated and new users are created for unmatched accounts. It is all the same routine as we have already described in Synchronization chapter.

Perhaps the most interesting part of this setup is object template. The template has several responsibilities:

  • Compute full name from first name and last name.

  • Generate unique username.

  • Generate e-mail address.

  • Automatically assign the roles based on job code.

All our users originating from the HR system have Person archetype. This is quite a standard configuration, and it is a very suitable one. We want to set up object template for users that represent persons. We do not want the template to apply to system users, such as administrator. Therefore, Person archetype is an ideal place to specify the template. This configuration is so common, that this is already pre-configured in midPoint. There is Person Object Template which is present in midPoint by default and it is already applied to Person archetype. All we need to do is to modify that template.

Let’s start with the simple thing: generating full name. At this point this almost a no-brainer:

object-template-person.xml
<objectTemplate oid="00000000-0000-0000-0000-000000000380">
    <name>Person Object Template</name>
    ...
    <item>
        <ref>fullName</ref>
        <mapping>
            <name>generate-fullname</name>
            <description>Generate fullName (enforcing on renames because of strong mapping)</description>
            <strength>strong</strength>
            <source>
                <path>givenName</path>
            </source>
            <source>
                <path>familyName</path>
            </source>
            <expression>
                <script>
                    <code>
                        basic.concatName(givenName, familyName)
                    </code>
                </script>
            </expression>
            <target>
                <path>fullName</path>
            </target>
        </mapping>
    </item>
    ...
</objectTemplate>

This is almost the same mapping as we have seen before. However, there is one difference: concatName function is used to concatenate the names. The concatName is quite smart, it converts all inputs to strings, it trims them, handles cases where some parts of name are null or empty - overall, it saves a lot of trouble. Perhaps the most interesting thing about this mapping is the fact, that it is already present in Person Object Template by default. We do not need to change anything.

Full name mapping is really simple, but it is a bit harder for username. We want to generate a user-friendly username. We could simply use user’s last name, but this is very likely to create conflicts. Therefore, let’s combine last name with the first letter of first name. That gives us nice usernames such as aanderson, bbrown and so on. However, there is still a chance of username conflict. So let’s add iteration tokens into the mix. Like this:

object-template-person.xml
<objectTemplate oid="00000000-0000-0000-0000-000000000380">
    <name>Person Object Template</name>
    ...
    <item>
        <ref>name</ref>
        <mapping>
            <name>generate-name-jsmith</name>
            <lifecycleState>active</lifecycleState>
            <strength>weak</strength>
            <source>
                <path>givenName</path>
            </source>
            <source>
                <path>familyName</path>
            </source>
            <expression>
                <script>
                    <code>
                        tmpGivenName = basic.trim(basic.norm(basic.stringify(givenName)))
                        tmpFamilyName  = basic.trim(basic.norm(basic.stringify(familyName)))
                        tmpGivenNameInitial = tmpGivenName?.take(1)
                        return (tmpGivenNameInitial + tmpFamilyName?.replaceAll(" ", ""))?.take(8)
                                    + iterationToken
                    </code>
                </script>
            </expression>
        </mapping>
    </item>
    ...
</objectTemplate>

This looks a bit more complicated that your have expected, doesn’t it? The basic idea is simple, so why won’t equally simple expression work? Maybe something like this?

givenName[0] + familyName + iterationToken

The devil is, as usual, in the details.

Firstly, good part of any programming is robustness and error handling. All the stringify and trim functions deal with inputs that are null, formatted in a wrong way (e.g. extra spaces) or wrong data types (e.g. polystring instead of string). We are also making sure there are no spaces in username (hence the replaceAll function).

Secondly, midPoint is built with multinational environment in mind. It is 21st century already and Unicode is everywhere. Almost everywhere, that is. It is expected that the HR system stores names with full national characters, such as Radovan Semančík. Yet, it is still not a common practice to use national characters in usernames, e-mail addresses and so on. Therefore, we usually want to normalize the national characters to their ASCII-7 equivalents. That is what PolyString is for and that is what the norm methods are doing. The result is that the generated username will be rsemanci instead of RSemančí.

Then there is slightly annoying issue of legacy in information technologies. Even though it is 21st century already, some systems are still quite behind. For example, sAMAccountName identifier in Active Directory is limited to 10 characters in length. Therefore, we are limiting the length of username to 8 characters, leaving last two characters for the iteration token. This is what the take(8) function does.

The bad news is that reality always finds a way to bring surprises, therefore the expressions dealing with real-wold data need to be more complicated than it is perhaps expected. However, there is also good news - at least when it comes to username generator mapping. The mapping above is already pre-configured in default Person Object Template in midPoint. It is not enabled by default. Lifecycle state of the mapping is set to draft, which effectively inactivates the mapping. Changing the lifecycle state to active enables it, and makes it ready to be used.

However, there is still one piece missing. We want to enable iteration to resolve naming conflicts. Otherwise poor Albert Anderson won’t have his accounts created because aanderso username is already taken by Alice. We can enable iterations like this:

object-template-person.xml
<objectTemplate oid="00000000-0000-0000-0000-000000000380">
    <name>Person Object Template</name>
    ...
    <iterationSpecification>
        <maxIterations>99</maxIterations>
        <tokenExpression>
            <script>
                <code>
                    if (iteration == 0) {
                        return ""
                    } else {
                        return iteration + 1
                    }
                </code>
            </script>
        </tokenExpression>
    </iterationSpecification>
    ...
</objectTemplate>

The maxIteration part up there is quite straightforward. We want to have some limit on the number of iterations as we do not want to iterate forever. Most iteration sequences are short in practice. If the iterative approach cannot find a match in several steps, then perhaps the iteration is not a good method anyway. Therefore, the limit is usually not a problem. However, having a limit makes a huge difference for troubleshooting. Most infinite iteration loops are caused by configuration errors. It is way much better to get an error after a couple of seconds than to wait forever.

The second part of the iteration configuration is also quite clear for people that read this chapter carefully. The default iteration token sequence is "", "1", "2", "3" and so on. That would give us aanderso, aanderso1, aanderso2 and so on. We do not want to have aanderso and aanderso1 as that would be confusing. Therefore, we chose to skip the "1" token and start with "2". The custom iteration token expression does just that.

Similarly to the mappings, iteration specification is already part of default Person Object Template.

As soon as the above configuration is in place, we can start testing our little deployment. Go ahead and import the HR resource, import object template, set the object template in the configuration and do not forget to replace the HR CSV file. If you did any experiments with previous configuration, it can be helpful to clean up midPoint by using the "delete all identities" process (that little dropdown button in menu:Repository objects[] page). When everything is set up, you can try to manually import a single account from the HR resource by using the btn:import[] button, located on the page where you can list resource accounts. Once the basic configuration works, you can test username generator by adding Albert Anderson to the HR CSV file and importing the account. Do not forget to explicitly fetch the accounts from the resource by clicking on the btn:Reload[] button on the bottom of the page. There is no synchronization task running, therefore MidPoint have not seen Albert’s account yet. You have to instruct midPoint to explicitly look at the resource. Once Albert’s account is imported, a non-conflicting username should be generated for him:

Users

We need to generate e-mail address next. In our case, the mapping for e-mail address is a bit similar to username mapping:

object-template-person.xml
<objectTemplate oid="00000000-0000-0000-0000-000000000380">
    <name>Person Object Template</name>
    ...
    <item>
        <ref>emailAddress</ref>
        <mapping>
            <name>generate-email</name>
            <strength>weak</strength>
            <source>
                <path>givenName</path>
            </source>
            <source>
                <path>familyName</path>
            </source>
            <expression>
                <script>
                    <code>
                        if ( givenName == null &amp;&amp; familyName == null ) { return null }
                        if ( familyName == null ) { return givenName?.norm + iterationToken + '@example.com' }
                        if ( givenName == null ) { return familyName?.norm + iterationToken + '@example.com' }
                        givenName?.norm + '.' + familyName?.norm + iterationToken + '@example.com'
                    </code>
                </script>
            </expression>
        </mapping>
    </item>
    ...
</objectTemplate>

This mapping should be quite understandable by now. There are the same checks for special cases. Then the main expression at the end combines given name and family name to get an e-mail address. This is just a simple example for e-mail address that is a good fit for a book or for a simple deployment. However, dealing with e-mail address is a bit more difficult in practice. A clever reader can surely discover a couple of obvious issues. Firstly, the expression is using the same iteration token than the username mapping is using. Therefore, the e-mail address for Albert Anderson will be albert.anderson2@example.com.

This is what we get when we re-import or reconcile both Andersons:

Users

This is not exactly what we want. Ideally, we would like to use much simpler versions albert.anderson@example.com. In this case there is no conflict with alice.anderson@example.com. However, midPoint does not consider e-mail address to be an identifier, therefore it does not check for its uniqueness. Also, there is only one iteration token that is reused for all the expressions in all object template mappings. There are also primary e-mail accounts and account aliases, dealing with account renames and temporary assignment of e-mail aliases and so on. Overall, dealing with e-mail addresses is far from easy. Some of those issues can be solved with pre-iteration or post-iteration conditions. However, it is quite likely that a completely custom code will be needed for a more complex cases.

That is also one of the reasons to set strength of this mapping to weak. We want to set an e-mail address automatically, but only in case that an address was not already specified manually. Weak mapping does not overwrite existing value. Sometimes it is best not to automate everything, leave the complex cases to system administrators to deal with.

Similarly to the full name and username mappings, the mapping for e-mail address is also part of Person Object Template by default.

Role autoassignment is the next step. We are going to handle role autoassignment based on job code using the object template method described above. Our object template works with user object both as input and output. It cannot (or rather should not) reach out to the HR account to get the value of jobcode attribute. We have to do that the other way around. We have to map HR account attribute jobcode to midPoint user property jobCode by using an inbound mapping:

resource-csv-hr.xml
<resource oid="03c3ceea-78e2-11e6-954d-dfdfa9ace0cf">
    <name>HR System</name>
    ...
    <schemaHandling>
        <objectType>
            ...
            <attribute>
                <ref>jobcode</ref>
                <inbound>
                    <target>
                        <path>$focus/extension/jobCode</path>
                    </target>
                </inbound>
            </attribute>
            ...
        </objectType>
    </schemaHandling>
    ...
</resource>

As the job code is synchronized to user, we can easily use it in object template mapping now:

object-template-person.xml
<objectTemplate oid="00000000-0000-0000-0000-000000000380">
    <name>Person Object Template</name>
    ...
    <item>
        <ref>assignment</ref>
        <mapping>
            <name>autoassign-jobcode</name>
            <strength>strong</strength>
            <source>
                <path>extension/jobCode</path>
            </source>
            <expression>
                <assignmentTargetSearch>
                    <targetType>RoleType</targetType>
                    <filter>
                        <q:text>extension/autoassignJobCode = $jobCode</q:text>
                    </filter>
                </assignmentTargetSearch>
            </expression>
        </mapping>
    </item>
    ...
</objectTemplate>

This is the same principle as we have used earlier in this chapter. The mapping is using assignmentTargetSearch expression to look for roles where user’s jobCode and role’s autoassignJobCode match. This mapping is strong, as we want to recompute the mapping and set the value all the times. If the mapping would be normal-strength, then the values are recomputed only when jobCode changes. Which actually might be enough during normal operation of the system. However, making this mapping strong makes things much easier during testing. That is all that we need to do for the mapping. Now we need to prepare the roles for this mapping to work with. We need to extend role schema first:

extension-example.xsd
<xsd:schema ...>
    ...
    <xsd:complexType name="RoleExtensionType">
        <xsd:annotation>
            <xsd:appinfo>
                <a:extension ref="c:RoleType"/>
            </xsd:appinfo>
        </xsd:annotation>
        <xsd:sequence>
            <xsd:element name="autoassignJobCode" type="xsd:string"
                                                  minOccurs="0" maxOccurs="1">
                ...
            </xsd:element>
        </xsd:sequence>
    </xsd:complexType>
    ...
</xsd:schema>

Then we need a couple of roles with job codes in their extension:

role-sales-manager.xml
<role oid="a1572de4-b9b9-11e9-af3e-5f68b3207f97">
    <name>Sales Manager</name>
    <extension>
        <exmpl:autoassignJobCode>S006</exmpl:autoassignJobCode>
    </extension>
    ...
</role>
role-sales-agent.xml
<role oid="b93af850-b9b9-11e9-8c2c-dfb9a89635a0">
    <name>Sales Agent</name>
    <extension>
        <exmpl:autoassignJobCode>S007</exmpl:autoassignJobCode>
    </extension>
    ...
</role>
role-sales-assistant.xml
<role oid="b9d2b604-b9b9-11e9-bbc4-17d8e85623b4">
    <name>Sales Assistant</name>
    <extension>
        <exmpl:autoassignJobCode>S008</exmpl:autoassignJobCode>
    </extension>
    ...
</role>

That is all the configuration needed for autoassignment to work. The roles should be automatically assigned to users when the users are recomputed. Just make sure that the users have their jobCode properly set in the user object. If they do not have it, then re-import them from the HR resource or run a reconciliation task. Then go ahead and create some more roles for the missing job codes. The mappings do not need to be changed at all to support more job codes. Just create new roles and recompute. That is the beauty of this solution - it is so easy to maintain.

So far we have tackled the inbound phase and focus policy phase. However, we have not talked about the outbound (provisioning) phase much. Now it is the right time to have a look at that.

We are going to reuse the LDAP and CRM resources from previous chapters. Those resources are used here in a pretty much unchanged form. There is no need to change them. Outbound mappings in the resource definitions specify the basic framework of the account. The key to provisioning flexibility is usually not in the resource definition, it is in the roles. Let’s start in the simplest way possible - with the Employee role. ExAmPLE company policy states that every employee should have a very basic LDAP account. Therefore, all we need is a very simple LDAP account construction that we place into an inducement in the Employee role:

role-employee.xml
<role oid="86d3b462-2334-11ea-bbac-13d84ce0a1df">
    <name>Employee</name>
    <inducement>
        <construction>
            <!-- OpenLDAP -->
            <resourceRef oid="8a83b1a4-be18-11e6-ae84-7301fdab1d7c" />
            <!-- just basic account. Nothing special here. -->
        </construction>
    </inducement>
</role>

All employees get this role, by the means of inbound mapping defined for HR resource. Therefore, all employees automatically get basic LDAP account. It is as simple as that. Put the construction in the role, reconcile the HR resource or just recompute the users, then LDAP accounts are created for all employees.

We want something a bit more fancy next. Salespeople tend to be a bit sensitive when it comes to their professional image. Therefore, they insist on having proper titles set up in company directory. Not a problem. We can do that easily in their "job" roles. This is how it looks like for a sales manager:

role-sales-manager.xml
<role oid="a1572de4-b9b9-11e9-af3e-5f68b3207f97">
    <name>Sales Manager</name>
    ...
    <inducement>
        <construction>
            <!-- OpenLDAP -->
            <resourceRef oid="8a83b1a4-be18-11e6-ae84-7301fdab1d7c" />
            <attribute>
                <ref>title</ref>
                <outbound>
                    <expression>
                        <value>Sales Manager</value>
                    </expression>
                </outbound>
            </attribute>
        </construction>
    </inducement>
</role>

This construction refers to the same account as the Employee role. MidPoint knows that, therefore it does not attempt to create a new account. It just updates existing account with appropriate title. However, we are not done yet. We need to provide access to CRM system for the salespeople. Should we create a new role for that? Absolutely not. We do not want to have too many roles as every role is a maintenance burden. Let’s just add new construction to an existing job role:

role-sales-manager.xml
<role oid="a1572de4-b9b9-11e9-af3e-5f68b3207f97">
    <name>Sales Manager</name>
    ...
    <inducement>
        <construction>
            <!-- OpenLDAP -->
            ...
        </construction>
    </inducement>
    <inducement>
        <construction>
            <!-- CRM -->
            <resourceRef oid="04afeda6-394b-11e6-8cbe-abf7ff430056" />
            <attribute>
                <ref>accesslevel</ref>
                <outbound>
                    <expression>
                        <value>MANAGER</value>
                    </expression>
                </outbound>
            </attribute>
        </construction>
    </inducement>
</role>

This is a role that gives access to the CRM system for a sales manager. MidPoint knows that, and it automatically creates a new CRM account when the role is assigned. Outbound mappings from the CRM resource definition are used to set basic properties of CRM account, such as account identifiers and password. In addition to that, the Sales Manager role sets appropriate access level to the CRM system.

Our setup is almost complete now. We have inbound synchronization, object template, roles and outbound mappings. This is the right time to test everything. Select few representative HR accounts and try to import them. Check that everything is provisioned correctly. If it works, then it is the time for roll-out. Set up a synchronization task for the HR resource, and we are done. We have running system:

Users

Users are imported from the HR system. Roles are assigned, which can be checked by navigating to user details page and opening the menu:Assignements[] tab. Accounts are provisioned according to the roles, which is the reason for variations in number of accounts for individual users. The basic stuff works now. Go ahead and try it out, add more roles and mappings, modify the configuration. Have some fun.

Even this simple identity management deployment is a huge improvement for many organizations already. However, there is still a lot of things to improve here. Maybe we want to set up a formalized organizational structure. Maybe we need delegated administration. We almost certainly want to manage groups, privileges and other entitlements. This is still just a beginning.

Conclusion

This chapter concludes one whole part of the book. If you have followed the book so far, you should be able to set up a simple working identity management deployment at this point. We have covered all the basic mechanisms: resources, mappings, roles, schema and object templates. This is a good time to stop reading and get your hands dirty. Take the examples from this book and play a bit with them. Explore the examples that come with midPoint distribution. Watch videos on Evolveum YouTube channel. Now it is time for experiments. You will surely do a lot of things that are suboptimal or even outright wrong. That does not really matter now. This is part of the learning process. If you get to dead end, just scrap everything and start over, or maybe rework everything from the ground up. MidPoint is designed for this. Evolutionary approach is deeply embedded in midPoint philosophy and design. Just go ahead, have fun, conduct experiments and explore. Such experience will help a lot when you get back and read through following chapters.

Was this page helpful?
YES NO
Thanks for your feedback