Policy Constraints

Last modified 13 Oct 2022 18:03 +02:00

This is a work-in-progress document. It reflects some ideas on policy constraints evolution in midPoint 3.7.

Policy constraints

A policy constraint is a statement (predicate) that may be true or false, depending on circumstances. Such constraints are used as part of policy rules, where they serve as conditions determining whether the rule should be triggered or not.

Different kinds of constraints

Constraints can be divided along two dimensions:

  1. assignment- and object-oriented,

  2. state- and transition-related.

The first criterion tells whether the constraint applies to a given assignment (or its target) or to the whole object. It is obvious that object-attached policy rules could not contain assignment-oriented constraints, because there is no assignment to evaluate them against. But on the other hand, assignment-attached policy rules can contain both assignment- and object-oriented constraints.

The second criterion tells whether the constraint triggers only when a change is being executed (related to given assignment or whole object), or they can be evaluated at any time, so to say.

As of midPoint 3.7 there are the following constraints divided into above categories:

State-related Transition-related Special

Object-oriented

objectState, hasAssignment, hasNoAssignment, minAssignees (objectMinAssigneesViolation), maxAssignees (objectMaxAssigneesViolation), timeValidity (objectTimeValidity?)

modification (objectModification?)

transition, situation, and, or, not

Assignment-oriented

assignmentState, exclusion, minAssignees, maxAssignees, timeValidity (assignmentTimeValidity?)

assignment (assignmentModification?)

Naming notes:

  1. Min/maxAssignees should be named in such a way that it would be obvious that the constraint triggers when the limit is violated. So, minAssigneesViolation and maxAssigneesViolation would be a better name.

  2. We try to make obvious (from the constraint name) whether it is related to an object or an assignment. So, timeValidity should be perhaps replaced by objectTimeValidity and assignmentTimeValidity. Another question is minAssigneesViolation. Originally it was related to assignments only. But it can be also object-related, if the object is an abstract role. So we introduced objectMinAssigneesViolation and objectMaxAssigneesViolation in order to highlight that these constraints are to be evaluated in the context of the object.

  3. Modification and assignment should be renamed to objectModification and assignmentModification, respectively. They are quite symmetrical in that the former deals with modifications (add, delete, modify) of objects, whereas the latter does the same for assignments.

Implementation notes:

  1. TimeValidity is not quite implemented yet.

  2. AssignmentModification does not support assignment modifications yet.

  3. Min/max target assignees are listed under state-related constraints, although they are currently checked only when an assignment is being added or deleted. They should be made really state-related, checked for all assignment targets. (At least it should be configurable.

  4. Transition constraint is currently compatible with objectState, assignmentState, hasAssignment, hasNoAssignment constraints. It should be applicable also to other state-related ones, namely min/maxTargetAssignees, time validity, and exclusion constraints.

Currently implemented constraints

Constraint Evaluated on Meaning Parameters Meaning

objectState

object

Triggers if the focal object meets given criteria: filter, expression, midPoint script. Exactly one of these must be provided.

filter

Filter to apply to determine object state.

expression

Expression to apply to determine object state. It can expect the variable "object" holding selected version of the focal object to be checked (see checkInState parameter).

executeScript

Starts a given midPoint script (a.k.a. bulk action) to determine object state. The constraint is considered triggered if the script returns non-empty list of items.

assignmentState

assignment

Triggers if the assignment meets given criteria. Does not supports filter nor executeScript; supports expression only.

expression

Expression to apply to determine object/assignment state. It can expect the variable "object" holding selected version of the focal object (driven by checkInState setting), variable "assignment" holding EvaluatedAssignment structure and variable "target" holding the assignment target. (The latter two variables are not influenced by checkInState setting.)

hasAssignment

object

Triggers if the focal object contains a given assignment. Analyzes evaluatedAssignmentsTriple to deliver the information needed.

targetRef

Containing OID of the target to look for or a filter to apply to existing assignments' targets. If contains also a relation, this relation is used for filtering instead of explicit "relation" parameter.

relation (multi)

Which relations should the target have. Not used if targetRef contains its own relation. If more relations are specified, any of them is to match. If no relations are specified, org:default is assumed. (If you want to specify "any" relation, use that value in relation attribute.)

direct

If true the assignment to given target must be direct. If false, it must be indirect (induced). If not specified, it might be either direct or indirect. But in all cases it must be of the order one, i.e. metaroles are not considered.

enabled

If true the assignment to given target must be effectively enabled. If false, it must be present but effectively disabled (beware, disabled indirect assignments are not present at all: so setting enabled=false is relevant only for direct assignments). If not specified, the effective status is not considered.

hasNoAssignment

object

Exact opposite of the above: triggers if the focal object does not contain a given assignment.

as above

as above

timeValidity*(NOT IMPLEMENTED YET)*

object or assignment (see "assignment" parameter)

Triggers if the focal object or assignment meets given time-related criteria. (Code for this constraint is not implemented yet, but it should be easy.)

item

Item relevant for time validity determination. Defaults to activation/validTo.

assignment

If set to true, validity of assignments of the relevant object are to be checked, not items of the object itself. So, for example, if you want to send notifications before validity of assignments to roles A, B, C expire, you’d need to attach a policy rule with this constraint having assignment=true to roles A, B, and C. (TODO reconsider this - probably by creating objectTimeValidity and assignmentTimeValidity instead)

activateOn

When will this policy constraint activate (trigger)? If not specified, activation will occur on the moment of validity change. Specify negative durations if you need to activate the trigger before that moment; and positive ones if the trigger should be activated after that.

deactivateOn

When will be this policy constraint deactivated? If not specified, it will be active forever. Specify negative durations if you need to activate the trigger before the moment of validity change; and positive ones is the trigger should be deactivated after that.

exclusion

assignment

Triggers if the object defining this "exclusion" and the object defined as target are assigned at the same time.

targetRef

Target of exclusion. Filter in the reference may be used to dynamically exclude broader range of roles - assuming that runtime resolution is used.

min/maxAssignees

assignment or object (see naming note 2 above)

Constraint on multiplicity of assigned objects. Triggers if the specified limits are violated. (This makes this constraint a bit different from the other constraints.)

multiplicity

Numeric value or string "unbounded".

relation (multi)

Relation(s) to which this constraint applies. All of these relations must match the defined multiplicity. If no relation is present, org:default (i.e. null) is assumed.

assignment (should be called assignmentModification)

assignment

Constraint that triggers the rule when the object is assigned.

operation (multi)

Specifies the operation(s) for which this constraints should be triggered. If not specified then it will be triggered for all operations. This defines the object modification operation (add/replace/delete of the specific assignment). In case that new object is added then all assignments in the object are considered to be added. The case of object deletion does not make sense here. Currently supported are ADD and DELETE operations. REPLACE (meaning MODIFY?) is not supported yet.

relation (multi)

This constraint only applies to relations of the specified type. The value of this element is compared to the relation of the targetRef relation in the assignment/inducement. If not specified then this policy only applies to the null (default) relation. If all relations need to be affected by this policy then the special value of "any" should be specified in this element.

modification (should be called objectModification)

object

Constraint that triggers on focal object modification, addition or deletion.

operation (multi)

Specifies the operation(s) for which this constraints should be triggered. If not specified then it will be triggered for all operations. This defines the object operation (add/modify/delete of the entire object).

item (multi)

Specification of items that must be modified (all of them) in order for this constraint be triggered. If no items are specified then any modification will trigger this constraint.

exactPathMatch

If true, item paths to be matched must match exactly. E.g. if inducement is specified as an item to be matched, then only object modifications having inducement in the path (i.e. whole inducement being added/deleted/replaced) would match.This is applicable only for modification operations. For add and delete operations the value of this flag is ignored.

expression

Expression that is used to determine the result. It is evaluated in addition to all the other conditions, and must have a value of true in order for constraint to be triggered.

situation

assignment (currently) or object (in the future)

Constraint that triggers the rule when the object or assignment is in a given policy situation(s). Currently is implemented only for assignments.

situation (multi)

Specifies the policy situation URI(s) to look for.

and

object or assignment

Triggers if all enclosed constraints trigger. The evaluation stops on the first non-triggering constraint.

+

+

or

object or assignment

Triggers if at least one of enclosed constraints trigger. Currently, all constraints are evaluated, even if only one suffices to triggering the enclosing constraint. This might change in the future.

+

+

not

object or assignment

Triggers if none of the enclose constraints trigger. (This means that enclosed constraints clause is taken as "and" clause.)

+

+

transition

object or assignment

Evaluates enclosed state-related constraints by taking current operation into account. They are evaluated in the state before and/or after the operation and the result is compared with the expected one. Enclosing constraint triggers when these results match.

stateBefore

Expected state before the operation: true means enclosed constraints triggers, false means they do not. Missing value (null) means that the state before operation is not checked.

stateAfter

Expected state after the operation: true means enclosed constraints triggers, false means they do not. Missing value (null) means that the state after operation is not checked.

Some examples

Mutual exclusion of 3 roles implemented using "or" constraint.
<policyRule>
    <name>criminal exclusion</name>
    <policyConstraints>
    <!-- triggers if Judge, Pirate, and/or Thief is assigned in addition to the current assignment -->
        <or>
            <exclusion>
                <targetRef oid="12345111-1111-2222-1111-121212111111" type="RoleType"/> <!-- Judge -->
            </exclusion>
            <exclusion>
                <targetRef oid="12345678-d34d-b33f-f00d-555555556666" type="RoleType"/> <!-- Pirate -->
            </exclusion>
            <exclusion>
                <targetRef oid="b189fcb8-1ff9-11e5-8912-001e8c717e5b" type="RoleType"/> <!-- Thief -->
            </exclusion>
        </or>
    </policyConstraints>
    <policyActions>
        <enforcement> ... </enforcement>
    </policyActions>
</policyRule>

The situation is quite simple here. The rule will trigger if any of the elementary exclusion constraints (excluding Judge, Pirate, and Thief) triggers. So, if this rule is attached to a role X, then X cannot be assigned with Judge, Pirate, and/or Thief. Note that if "or" would not be used there, the rule would say that X cannot be assigned if a user has already assigned Judge, Pirate, and Thief.

Active roles must have description and at least one owner and approver
<policyRule>
    <!-- here we simply state that it's not possible to have active role with no description or no owner or no approver -->
    <name>disallow-incomplete-role-activation</name>
    <policyConstraints>
        <objectState>
            <name>active lifecycleState</name>
            <filter>
                <q:equal>
                    <q:path>lifecycleState</q:path>
                    <q:value>active</q:value>
                </q:equal>
            </filter>
        </objectState>
        <or>
            <objectMinAssigneesViolation>
                <multiplicity>1</multiplicity>
                <relation>owner</relation>
                <relation>approver</relation>
            </objectMinAssigneesViolation>
            <objectState>
                <name>no description</name>
                <filter>
                    <q:equal>
                        <q:path>description</q:path>
                    </q:equal>
                </filter>
            </objectState>
        </or>
    </policyConstraints>
    <policyActions>
        <enforcement/>
    </policyActions>
</policyRule>

The rule simply says that it is not acceptable to have active role that violates any of the specified constraints: either has no owner or approver, or has no description.

Switching to active state must be approved
<policyRule>
    <name>approve-role-activation</name>
    <policyConstraints>
        <transition>
            <name>role-switched-to-active</name>
            <stateBefore>false</stateBefore>
            <stateAfter>true</stateAfter>
            <constraints>
                <objectState>
                    <name>active lifecycleState</name>
                    <filter>
                        <q:equal>
                            <q:path>lifecycleState</q:path>
                            <q:value>active</q:value>
                        </q:equal>
                    </filter>
                </objectState>
            </constraints>
        </transition>
    </policyConstraints>
    <policyActions>
        <approval> ... </approval>
    </policyActions>
</policyRule>

This rule says that each change that transitions a role from a state other than "active" (e.g. draft, proposed, deprecated, failed, …​) to the state of "active" must be approved. Note that after the role is in the active state, any changes (even touching the lifecycleState attribute) do not need to be approved, at least as per this rule.

Switching to active state must be approved (alternative way)
<policyRule>
    <name>approve-role-activation</name>
    <policyConstraints>
        <modification>
            <item>lifecycleState</item>
        </modification>
        <objectState>
            <name>active lifecycleState</name>
            <filter>
                <q:equal>
                    <q:path>lifecycleState</q:path>
                    <q:value>active</q:value>
                </q:equal>
           </filter>
       </objectState>
    </policyConstraints>
    <policyActions>
        <approval> ... </approval>
    </policyActions>
</policyRule>

This rule is similar, but implemented using different constraints. It basically says: if there’s a modification that involves lifecycleState item, and if the new value of the item is "active", then require an approval. It is basically the same as the previous one, with a very small difference. If there’s a "no-op" change of lifecycleState, e.g. if the value is "active" and we request REPLACE or ADD operation of "active" value, this latter rule would trigger, whereas the former one would not.

Additional approval step when switching high-risk roles to active state
<policyRule>
    <name>approve-high-risk-role-activation</name>
    <policyConstraints>
        <objectState>
            <name>role-is-high-risk</name>
            <filter>
                <q:equal>
                    <q:path>riskLevel</q:path>
                    <q:value>high</q:value>
                </q:equal>
            </filter>
        </objectState>
        <transition>
            <name>role-switched-to-active</name>
            <stateBefore>false</stateBefore>
            <stateAfter>true</stateAfter>
            <constraints>
                <objectState>
                    <name>active lifecycleState</name>
                    <filter>
                        <q:equal>
                            <q:path>lifecycleState</q:path>
                            <q:value>active</q:value>
                        </q:equal>
                    </filter>
                </objectState>
            </constraints>
        </transition>
    </policyConstraints>
    <policyActions>
        <approval>
            <compositionStrategy>
                ...
            </compositionStrategy>
            ...
        </approval>
    </policyActions>
</policyRule>

This rule says that if a role is switched to active state and its riskLevel is high, additional approval step is to be taken. The riskLevel is evaluated on the new role state. I.e. if the operation for a role with riskLevel=normal and lifecycleState=draft is "set riskLevel to high and set lifecycleState to active", then this role would be applied. If we have a role with riskLevel=high and lifecycleState=draft and issue a change of "set riskLevel to normal and set lifecycleState to active" this rule would not trigger.

Creating complex pipeline in executeScript
<policyRule>
	<policyConstraints>
		<objectState>
			<executeScript>
				<s:pipeline>
					<s:expression xsi:type="s:SearchExpressionType">
						<s:type>TaskType</s:type>
						<s:query>
							<q:filter>
								<q:and>
									<q:ref>
										<q:path>objectRef</q:path>
										<expression>
											<script>
												<code>
													import com.evolveum.midpoint.xml.ns._public.common.common_3.*
													new ObjectReferenceType().oid(object.oid)
												</code>
											</script>
										</expression>
									</q:ref>
									<q:equal>
										<q:path>executionStatus</q:path>
										<q:value>waiting</q:value>
									</q:equal>
									<q:equal>
										<q:path>category</q:path>
										<q:value>Workflow</q:value>
									</q:equal>
								</q:and>
							</q:filter>
						</s:query>
					</s:expression>
					<s:expression xsi:type="s:ActionExpressionType">
						<s:type>execute-script</s:type>
						<s:parameter>
							<s:name>script</s:name>
							<value>
								<code>
									//...put your code here, you can inspect the found task detail for e.g. changes in workflow context item delta.
									//return input; return input TaskType to trigger the rule
									return null; // return null to skip it
								</code>
							</value>
						</s:parameter>
						<s:parameter>
							<s:name>outputItem</s:name>
							<value>TaskType</value>
						</s:parameter>
					</s:expression>
				</s:pipeline>
			</executeScript>
		</objectState>
	</policyConstraints>
	<policyActions>
		<enforcement />
	</policyActions>
</policyRule>

Example above is quite complex fragment that uses pipeline in the object state to execute custom script. The search expression searches midPoint for any waiting workflow task referencing the object. If found, task is inspected in subsequent action with groovy code. To trigger the constraint just return the object as defined in outputItem or return null to skip it.

Constraints references

Quite often a constraint has to be reused. For example if it is to be used in the context of approval policy rule (if everything goes well) but also in the context of an enforcement policy rule (if there’s something wrong).

To avoid duplication of configuration parts or even scripting code there is a concept of constraints references.

Each named constraint can be referenced using its name. For example, the following configuration disallows activation of incompletely defined roles while it allows (under approval) activation of roles that are well defined. Note that "active lifecycleState" is a policy constraint that tells whether the role is to be considered active. It is used in both policy rules: defined in the first one and referenced in the second one.

Forbidding activation of incomplete roles, allowing (under approval) activation of complete ones
<inducement>
    <policyRule>
        <!-- here we simply state that it's not possible to have active role with no description or no owner or no approver -->
        <name>disallow-incomplete-role-activation</name>
        <policyConstraints>
            <objectState>
                <name>active lifecycleState</name>
                <filter>
                    <q:equal>
                        <q:path>lifecycleState</q:path>
                        <q:value>active</q:value>
                    </q:equal>
                </filter>
            </objectState>
            <or>
                <name>incomplete-role</name>
                <minAssignees>
                    <multiplicity>1</multiplicity>
                    <relation>owner</relation>
                    <relation>approver</relation>
                </minAssignees>
                <objectState>
                    <name>no description</name>
                    <filter>
                        <q:equal>
                            <q:path>description</q:path>
                        </q:equal>
                    </filter>
                </objectState>
            </or>
        </policyConstraints>
        <policyActions>
            <enforcement/>
        </policyActions>
        <evaluationTarget>focus</evaluationTarget>
    </policyRule>
</inducement>
<inducement>
    <policyRule>
        <name>approve-role-activation</name>
        <policyConstraints>
            <transition>
                <name>role-switched-to-active</name>
                <stateBefore>false</stateBefore>
                <stateAfter>true</stateAfter>
                <constraints>
                    <ref>active lifecycleState</ref>
                </constraints>
            </transition>
        </policyConstraints>
        <policyActions>
            <approval>
                <compositionStrategy>
                    <order>10</order>
                </compositionStrategy>
                <approverRelation>owner</approverRelation>
            </approval>
        </policyActions>
    </policyRule>
</inducement>

Interesting question is: what is the scope of the policy rule names? I.e. in what places are rules to be resolved being looked for?

The process currently has two steps. The first step is the following:

  1. When collecting rules for a given assignment, all enabled policy rules related to its direct and indirect targets are taken.

  2. When collecting rules for an object, all enabled policy rules related to this object are taken.

Enabled rules means these that are to be really evaluated in that situation. If they are global, then selectors and conditions must match (pass). If they are attached, then validity condition must be true.

If the constraint is not found within these, the second step is executed. It consists of taking all defined global rules, irrespective of their selectors (focus, target), or conditions.

During each step we check whether the constraint name is unique. If there are two different constraints with the same name, an exception is reported.

(Of course, an exception is reported also if the constraint cannot be found or if there is a cyclic dependency among constraints.)

TODO: restricting scope for referenced constraints

It is possible that the current scope is too broad, i.e. that naming conflicts would occur. This is to be thought over again.

Some not yet implemented ideas

Full support for TimeValidity constraint

It is supported only in the context of a notification task.

"Scripting" policy action

Could be named "executeScript" but this term is already used elsewhere.

Certification of policy situations

Experimental support is provided for SoD situations.

Exceptions for actions, rules, and maybe constraints

It would be great to support rules like this:

14 days before user's validity is over, notify his line manager. But send notifications at most each 4 days.

Or

14 days before user's validity is over, start an approval task to extend it by 180 days.

In these cases we would need to record that the notification or extension action was taken, and not repeat it until 4 days pass (in the first case), or never (the second case).

Caching of constraints evaluation

Currently, constraints are evaluated repeatedly even if they were already evaluated for a given context (object, assignment, state). This is to be changed.

Was this page helpful?
YES NO
Thanks for your feedback