MidPoint in Higher Education: Orgs, Roles and Relations

Disclaimer: This particular blog post contains advanced configuration options for midPoint. It might be difficult to understand all the details for those without experience with midPoint.

The first MidPoint in Higher Education blog post gave us a brief overview of the possible complexity of identity management in the higher education environment. Therefore today we can jump to the first technical demonstration. The main focus will be on organizational units, roles and, of course, users which will be connected by assignments with different relations. Also, this demo is designed in a way that a professional identity engineer is needed only for initialization and designing complex objects. Administrators can execute the rest of the operations without excessive knowledge of IdM and IT in general. In other words, any user can, for example, assign a role if they have rights to do it. We won’t get into the access rights configuration in this blog post. We will follow principle that complex logic should be done during initialization and standard operations should be straightforward to use.

Our model example starts with a university organization tree structure with faculties and departments. In a real-world scenario, this structure, including users, would be populated by the HR and student system. In our case, the structure will be created and updated manually. At this point, we need to deal with the first challenge mentioned in the introductory blog, which is different relations of users within the organizational units. In our case, we will take the employee relation and the student relation as an example, but of course we could go further and distinguish academic and non-academic employees, undergraduate and PhD students and so on.

To do that, we need to extend the system configuration with new relations “student” and “employee”.

<roleManagement xmlns:academia="http://midpoint.evolveum.com/xml/ns/samples/academia">
    <relations>
        <relation>
            <ref>academia:student</ref>
            <description>Student</description>
            <display>
                <label>Student</label>
            </display>
            <category>organization</category>
            <category>administration</category>
            <kind>member</kind>
        </relation>
        <relation>
            <ref>academia:employee</ref>
            <description>Employee (e.g. teacher, researcher)</description>
            <display>
                <label>Employee</label>
            </display>
            <category>organization</category>
            <category>administration</category>
            <kind>member</kind>
        </relation>
    </relations>
</roleManagement>

We will use these relations in the organizational structure of our fictive university. User can be member of an organizational unit either as student, employee, or both at the same time. This relation can be assigned automatically using inbound mapping, but for purposes of this demo the assignment will be manual. The manual assignment just gives us a different way how to interpret these relations. A user can have a student/employee right within the organization or within the role. On this basis, we can meaningfully use the same assignment relation with both roles and services.

This is a crucial aspect of midPoint. MidPoint doesn’t impose a specific interpretation of its objects or properties. It is up to the midPoint administrator to define it and share that definition with other users of midPoint. In this demo we have chosen our own interpretation for the student and employee relation.

Let’s explore how to use relations in practice. We have a simple organizational structure where we will add our users as employees, students or both. We want to assign roles to organizational units which will provision members with the appropriate relation to a resource. In our fictive case, the resource is access to a computer lab. We have a simple Computer Lab Account Construction role which creates a default account for all users who have this role assigned. In a real scenario, we could have more complex configuration options in this role.

<role oid="4fd01a65-e468-424b-b2b9-0659eb2c3ccc">
    <name>Computer Lab Account Construction role</name>
    <inducement>
        <construction>
            <!-- Account on Computer Lab resource -->
            <resourceRef oid="519b0b18-42ae-4b76-b789-bbfe279dba36" relation="org:default" type="c:ResourceType"/>
            <kind>account</kind>
            <intent>default</intent>
        </construction>
    </inducement>
</role>

This role can be assigned to users directly, and it will just create Computer Lab accounts for the users, but we have another usage for it. We will create another role, designed to be assigned to org. units only, and it will take all students from that org. unit and create accounts on the Computer Lab resource for them.

<role oid="4b4dcc80-68ee-4b2d-8165-a23e8b9b6350">
    <name>Computer Lab Access Role for direct org. Students</name>
    <description>This role will take direct students from org and give them access to the Computer Lab</description>

    <inducement>
        <order>2</order>
        <focusType>UserType</focusType>

        <!-- ComputerLabAccountConstructionRole -->
        <targetRef oid="4fd01a65-e468-424b-b2b9-0659eb2c3ccc" relation="org:default" type="c:RoleType"/>

        <condition>
            <expression>
                <script>
                    <code>
                       <![CDATA[
                            return focusAssignment.targetRef.relation.localPart == 'student' && focusAssignment.targetRef.relation.prefix == 'academia'
                        ]]>
                    </code>
                </script>
            </expression>
        </condition>
    </inducement>
</role>

The role might seem a bit complex at first glance, but it really is not. Order = 2 means the inducement won’t be applied to the org. unit, where the role is assigned (that would be Order = 1), but instead to members of the org. unit (Order = 2). The next essential part is focusType = UserType, which makes sure the inducement will be applied only to User and not to other objects, for example, the child org. units. The last part is the condition which will select only objects having our custom student relation. The way conditions on custom relations are defined is a bit complicated now, but it will be improved over time.

Order = 2 can be replaced by anorderConstraint configuration, which enables us to apply the inducement to indirect members of the org. unit as well. Let’s create a role which will do precisely that.

<role oid="e983bebf-6d13-4b3d-9a7b-38c06ddb7d29">
    <name>Computer Lab Access Role for all org. Students </name>
    <description>This role will take all students from org and give them access to the Computer Lab</description>

    <inducement>
        <focusType>UserType</focusType>
        <orderConstraint>
            <orderMin>2</orderMin>
            <orderMax>unbounded</orderMax>
            <resetOrder>1</resetOrder>
        </orderConstraint>

        <!-- ComputerLabAccountConstructionRole -->
        <targetRef oid="4fd01a65-e468-424b-b2b9-0659eb2c3ccc" relation="org:default" type="c:RoleType"/>

        <condition>
            <expression>
                <script>
                    <code>
                       <![CDATA[
                            return focusAssignment.targetRef.relation.localPart == 'student' && focusAssignment.targetRef.relation.prefix == 'academia'
                        ]]>
                    </code>
                </script>
            </expression>
        </condition>
    </inducement>
</role>

The tricky part is resetOrder = 1. Thanks to it, the Computer Lab Account Construction role will treat our users as if they were at order = 1. Otherwise, it’s the same as the previous role; except that the order is defined by a range instead of a constant number.

We have now three roles which were designed in detail by an identity engineer. Nobody expects midPoint users/managers to have that level of knowledge, but we expect them to use the roles. This was the goal from the beginning. We can appoint users who will have the rights to assign our roles to org. units. Such users don’t need to understand the internals. They only need to know that if they assign the role to org. unit, then all students from that org. unit will gain access to the Computer Lab, which is clearly described in the role itself.

It might be difficult for users to track which roles are made to be assigned to org. units and which roles are for users. Therefore we will create an archetype for roles which are supposed to be assigned to organizations only.

<archetype oid="5a2ed686-be87-4294-b882-05f11a0a30a7">
    <name>Role for organization units</name>

    <inducement>
        <assignmentRelation>
            <holderType>OrgType</holderType>
        </assignmentRelation>
    </inducement>

    <archetypePolicy>
        <display>
            <label>Role for org. units</label>
            <pluralLabel>Roles for org. units</pluralLabel>
            <icon>
                <cssClass>fa fa-sitemap</cssClass>
                <color>teal</color>
            </icon>
        </display>
    </archetypePolicy>

</archetype>

The archetypePolicy only defines how the role will be displayed in midPoint GUI. Since we need to create the archetype anyway, we might as well add visual distinction to it. The important part is the assignmentRelation by which we limit the objects to which the archetyped role can be assigned. In this case, we only allow it to be assigned to org. units. This setting is not sufficient because midPoint in default settings doesn’t enforce holder types in assignment relations, and it shouldn’t be enabled globally until all archetype configuration is finished and cleaned up. In our case, we will enable this feature only for our org. structure.

<archetype oid="df37370d-6d2e-4413-8b27-0e291b3ecbf1">
    <name>University org. unit</name>

    <inducement>
        <assignmentRelation>
            <description>Only University org. units are allowed in the university org. tree</description>
            <holderType>OrgType</holderType>
            <holderArchetypeRef oid="df37370d-6d2e-4413-8b27-0e291b3ecbf1"/> <!-- University org. unit archetype -->
        </assignmentRelation>
        <assignmentRelation>
            <description>Only users with student or employee relation are allowed as members</description>
            <holderType>UserType</holderType>
            <relation>academia:student</relation>
            <relation>academia:employee</relation>
        </assignmentRelation>
    </inducement>

    <archetypePolicy>
        <display>
            <label>University org. unit</label>
            <pluralLabel>University org. units</pluralLabel>
            <icon>
                <cssClass>fa fa-university</cssClass>
                <color>teal</color>
            </icon>
        </display>

        <assignmentHolderRelationApproach>closed</assignmentHolderRelationApproach>
    </archetypePolicy>

</archetype>

This archetype will be assigned to our org. units representing the university structure. We are enabling only orgs and users to become members of these archetyped org units. To make it more interesting, we are not allowing any user or org. unit but only org. units with University org. unit archetype and only users with assignment relation student or employee. Also, we are claiming that assignmentHolderRelationApproach is closed, which means only the object which we explicitly permitted (like our “Role for organization units” archetype) can be assigned to this org. unit. You can see the result on the screenshot from assigning roles to university org. unit.

There is still one technical bit missing. When you have roles assigned to org. units, and you only work with org. units members, the provisioning works as expected. But when you are assigning a role to an org. unit, only the org. unit is recomputed, not the members. A simple solution is to recompute all members of the org. unit manually after you assign a new role to it. But this is not suitable for our case because we want to delegate this operation to users who don’t have such detailed knowledge. We have to automate this step.

We will use a brand new feature from midPoint 4.2. – linked objects. We will create a policy which will recompute all org. unit members when there is a change in its assignments. We could apply this policy to all org. units, but we want a finer grained approach. Let’s suppose our organization tree is very rich and only small number of org. units will have a role assigned. What about applying policy to those that will have a role assigned or even better only those that have the role requiring user recomputation assigned? In other words, the role itself will carry the policy which will be applied to org. units to which the role has been assigned. This is the moment when the power of midPoint reveals itself.

 

<role oid="79acd5e6-3559-423d-9212-67d43ab6ffaa">
    <name>Recompute org. members after assignment change - policy role</name>

    <inducement>
        <order>2</order>
        <policyRule>
            <documentation>
                Recompute all direct members of the org when a new object (e.g. role) is assigned to the org.
            </documentation>
            <policyConstraints>
                <modification>
                    <item>roleMembershipRef</item>
                </modification>
            </policyConstraints>
            <policyActions>
                <scriptExecution>
                    <object>
                        <linkSource/>
                    </object>
                    <executeScript>
                        <s:recompute>
                            <s:triggered>
                                <fireAfter>PT1M</fireAfter>
                            </s:triggered>
                        </s:recompute>
                    </executeScript>
                </scriptExecution>
            </policyActions>
        </policyRule>
    </inducement>

</role>

There is one additional detail which will enhance the performance. It is delayed execution implemented by a trigger. In our case, the recomputation won’t start immediately, but it will be picked up by the trigger task after one minute. This will eliminate duplicate recomputation of the same users, which might happen if there are multiple changes done at the same time.

Unfortunately, previous policy only triggers the recomputation for direct members of an org. unit. Therefore, it is suitable for our role, which works with direct members only but not for the other one, which works with all members including indirect ones. To recompute all members, we need to modify the policy role. Unfortunately, this is the point where we exceed the capabilities of midPoint configuration options, at least in version 4.2. MidPoint gives us the possibility to solve even that, but it will require getting away from friendly XML configuration and providing the missing piece using a script. The following role works exactly like the previous one except it will also recompute indirect members.

<role oid="16e81801-7b17-45c7-b5c4-6b8f50c480a6">
    <name>Recompute all org. members after assignment change - policy role</name>

    <inducement>
        <order>2</order>
        <policyRule>
            <documentation>
                Recompute all members of the org when a new object (e.g. role) is assigned to the org.
            </documentation>
            <policyConstraints>
                <modification>
                    <item>roleMembershipRef</item>
                </modification>
            </policyConstraints>
            <policyActions>
                <scriptExecution>
                    <executeScript>
                        <s:execute>
                            <s:script>
                                <code>
                                    import com.evolveum.midpoint.xml.ns._public.common.common_3.*

                                    TRIGGER_FIRE_AFTER = 60000
                                    TRIGGER_SAFETY_MARGIN = 10000

                                    log.info('Looking for children of {}', input)
                                    query = midpoint.prismContext.queryFor(UserType.class)
                                            .isChildOf(input.oid)
                                            .build()

                                    users = midpoint.searchObjects(UserType.class, query)
                                    log.info('Users found: {}', users)

                                    midpoint.searchObjectsIterative(UserType.class, query, { object, parentResult ->
                                        log.info('Creating trigger for {}', object)
                                        midpoint
                                                .getOptimizingTriggerCreator(TRIGGER_FIRE_AFTER, TRIGGER_SAFETY_MARGIN)
                                                .createForObject(UserType.class, object.oid)
                                    })
                                </code>
                            </s:script>
                        </s:execute>
                    </executeScript>
                </scriptExecution>
            </policyActions>
        </policyRule>
    </inducement>

</role>

MidPoint is continuously evolving, and new features are coming rapidly. Therefore in the future there will very likely be a way to achieve the same result without scripts. For now, we have this clear demonstration of the extreme flexibility of midPoint, even when the desired feature seems to be missing.

To put our policy roles into effect, we have to assign them to our ComputerLabs access roles, which we will do using the ordinary assignment.

<role oid="e983bebf-6d13-4b3d-9a7b-38c06ddb7d29">
    <name>Computer Lab Access Role for all org. Students </name>
    <description>This role will take all student from org and give them access to the Computer Lab</description>

    <assignment>
        <!-- Recompute all org. members after role assignment change - policy role -->
        <targetRef oid="16e81801-7b17-45c7-b5c4-6b8f50c480a6" type="RoleType" />
    </assignment>

    ...

</role>

To sum all this blog up, we have created two custom assignment relations for students and employees, which we are using in the org. structure. We have created roles for org. units, which takes only students from a given org. unit or, alternatively, from the whole subtree. To enhance user experience, we limit assignment of these roles to org. units only. As a final touch, we have created policies which will automatically recompute affected members when we are assigning our roles to org. units.

 

Technical note:  This blog is based on the upcoming midPoint 4.2. The whole demo environment will be released as a docker image.

We are welcoming feedback to this blog series. Also, we are open to suggestions for the next topics, as long as they fit the overall scope. Feel free to share your ideas in the comments, on midPoint mailing list or any other channels.

Assistance with proofreading and readability improvements provided by Keith Hazelton of Internet2 was greatly appreciated.

1 thought on “MidPoint in Higher Education: Orgs, Roles and Relations

Leave a Reply

Your email address will not be published.