Skip to main content
Skip table of contents

Rules in Object Lifecycle

Introduction

Memority lets you manipulate an object attributes directly while it is being processed, during a CREATE or PATCH operation, using Object Lifecycle Policies. This page describes how the Groovy Rules for those policies must be designed, as well as best practices and attention points.

Overview

Object Lifecycle Policies let one introduce behavior deep in the processing of IDM objects, in performance and failure sensitive operations. For this reason, special care should be taken in writing the associated Groovy Rules. For performance reasons, those policies are not scoped with a search expression. Instead, they are configured for a specific object kind and, optionally, for a set of specific object types. Also, several rules can be configured on a single Policy, to make the most of script reuse and composition.

All Object Lifecycle Policies configured for a given Object Kind and Type are always executed for this kind and type. This means they should be fast and reliable.

I will stress that: reliable

If a Rule fails, the whole object operation transaction will fail. In a bulk scenario, this could be a batch of modifications.

The Rules give a direct access to modifying the well-known ApiObject that is accessible in the groovy code. There is no patching API and the object's attribute are manipulated directly, in a stage of the object processing that does not perform additional validation or checks on the attributes. This means that, after having been changed, the object must be in a correct, valid state.

Lifecycle Control Stages

Although one do not need to know the details of an object processing pipeline to write an Object Lifecycle Policy rule, it is important to understand where such policies stand in regard to other operations done on the object data, during a create or patch operation.

In the diagram above we consider the "Apply Lifecycle Controls" phase, which is the one during which all automated operations related to an Object lifecycle are performed. This includes handling its enable  state, the authentication methods, computing the Role and Right Assignments on Identities, inheriting the attributes of parent Organizations, and more.

The Lifecycle Controls phase is split in several stages, and the Object Lifecycle Policies can introduce behaviour between those stages. The stages depend on the object kind, and are summarized in the table below:

Stage

Object Kind

Policy Stages (before/after)

Description

COMMON

all

COMMON_PRE

COMMON_POST

Handle the core object lifecycle mechanisms:

  • enable  attribute handling (changes authorization, activation modes, enable from/until)

  • creation/update/deleted dates in metadata

  • sanity checks on current operation

ASSIGNMENTS

Identity

ASSIGNMENTS_PRE

ASSIGNMENTS_POST

Handle the roles and rights hierarchy update, including:

  • computing the dimensions in children roles and rights

  • applying Role Assignment Policies

  • updating Role statuses and the object rights as necessary

AUTH

Identity

AUTH_PRE

AUTH_POST

Handles the mechanisms related to Authentication Methods:

  • activation and deactivation of methods

  • triggering of automated actions if necessary

INHERITANCE

Organization

INHERITANCE_PRE

INHERITANCE_POST

Handles the mechanisms related to inheritance in objects that are organized as a hierarchy:

  • inheritance of attributes

  • setting or hierarchy path

REFRESH_ORDERS

Roles

REFRESH_ORDERS_PRE

REFRESH_ORDERS_POST

Handles the generation of refresh orders for Roles and Role Assignment Policy changes, when necessary.

FINALIZE

all

FINALIZE_PRE

FINALIZE_POST

Handles last minute operations. This is a placeholder stage, that can also be used by Object Lifecycle Policies to apply behavior at the very last moment.

Designing Object Lifecycle Policies

Overview

Object Lifecycle Policies should be designed with the following principles in mind:

  • As few as possible

  • As targeted as possible

In practice, this means that you should consider:

  • If you have several behaviors to implement at the same stage, configure a single policy with a single rule that implements all of the behaviors instead of several policies and/or several rules

  • If the policy applies to only a subset of object types, configure those types on the policy instead of testing for the type in your rule

Also please note that Object Lifecycle Policies are not prioritized, so should several policies exist for the same stage, object kind and object type, they may be executed in any order.

Rules

Basic Principles

Rules in Object Lifecycle Policies are Transform Rules, which means they do not return anything but simply operate on the Rule context (the bound variables).

To manipulate the processed object's attributes, one simply access the OBJECT  bound variable and mutate its content. The OBJECT  properties (attributes) can be directly manipulated using idiomatic Groovy syntax, as in the example below:

CODE
OBJECT.commonName = OBJECT.firstName + " " + OBJECT.lastName; // set the value of the 'commonName' attribute

if (OPERATION.operation == ObjectOperation.CREATE && OBJECT.kind == ObjectKind.IDENTITY) {
  // always add the 'adm.common-right' right to Identities on creation
  OBJECT.rights << RightGrant.of("adm.common-right");
}

Beware, when working with multi-valued attributes: if the object does not have any value for this attribute, the property may be null , in which case it is necessary to test for nullity before manipulating the list:

CODE
if (OBJECT.events == null) {
  OBJECT.events = []
}
OBJECT.events << "AWS Summit, April 21st"

It is very important to note that the object's attribute values are considered immutable and must not be modified in place. Instead, an attribute value must be replaced (or added/removed) where necessary. The API always present attribute values as opaque value objects, with facilities to create new, altered versions, where necessary (obviously, for complex attribute values such as Rights or Roles):

(tick) OBJECT.roles[0] = RoleAssignment.copying(OBJECT.roles[0]) .status(RoleAssignmentStatus.FROZEN) .build()

(error) OBJECT.roles[0] .setStatus(RoleAssignmentStatus.FROZEN)

ApiObject methods

The ApiObject also comes with additional methods meant to simplify the manipulation of the attribute values. You can refer to the Javadoc for a complete overview, however here are the most interesting ones:

Method

Usage

CODE
List<?> 
  values(String name)

Return the values for the attribute name , initializing a list if necessary. If this is a mono-valued property, return a list with a single value.

CODE
boolean 
  hasValue(String name)

Indicates whether or not the object has a value for the attribute name 

CODE
boolean 
  has(String name)

Indicates whether or not the object has the attribute name 

CODE
boolean 
  addValue(String name, Object value, boolean distinct, boolean sort)

Add the given value to the multivalued attribute name , with 2 control options:

  • distinct  when true, the value is added only if the attribute does not already have that value

  • sort  when true, the value is added and the list of attributes is sorted according to its natural order

CODE
boolean 
  removeValue(String name, Object value)

Remove the given value from the attribute name . Works with both mono and multi-valued attributes.

Working with Role Assignments

Role Assignments are complex attribute values, however they must still be considered as immutable value objects that need to be replaced in an Identity's list of roles when they are manipulated.

The RoleAssignment  class provides a builder API that let you quickly create a new Role Assignment from scratch, or from an existing one:

Method

Usage

CODE
static RoleAssignmentBuilder 
  forRole(String roleId)

Returns a builder to create a new Role Assignment from scratch. The following defaults are guaranteed:

  • status is set to APPLIED

  • source is set to MANUAL

  • dimensions are empty

To obtain a minimum valid RoleAssignment  one simply need to provide the role identifier (if the corresponding Role does not require any dimension). The build()  method must be called at the end to obtain a complete RoleAssignment  object.

Example usage:

CODE
RoleAssignment.forRole("admin_role").dimension("level", 3).comment("set from groovy").build()
CODE
static RoleAssignmentBuilder 
  copying(RoleAssignment ra)

Returns a builder that is preconfigured to copy the given RoleAssignment . This is especially useful when one want to change an existing Role, for example its status or one of its dimension.

Example usage:

CODE
// existing role has been obtained from OBJECT in 'role' variable
updatedRole = RoleAssignment.copying(role).status(RoleAssignmentStatus.FROZEN).build()
OBJECT.roles.remove(role) // list remove
OBJECT.roles << updatedRole

Best Practices

When implementing an Object Lifecycle Rule, one should keep in mind the following general rules of thumb. Note that they can also be applied to Attribute Compute and Resolve rules.

Rule of thumb

Rationale

Use the earliest stage you can considering your behavior requirement. 

This ensures that the changes you make can be used in subsequent stages, and the other lifecycle controls can act on them.

Only apply necessary computation and use a quick exit pattern.

The control code is executed for each object, so unnecessary computation will slow down all operations needlessly. Instead, your code should test early on if changes must be applied and which changes, and exit quickly as soon as it has been determined that no more computations are needed.

Do not perform external calls.

Again, external calls (database, web service) are extremely costly and should only be performed as needed. Try to apply strategies to test for it if possible (do not needlessly perform these calls if it is not needed considering the context). For example, an external call that need to be made on creation only should not be performed on PATCH.

Use Cases

Authentication Method

We want to automatically enable the myMF Aauthentication method on an Identity of type "employee" depending on its location, but only if the Identity is enabled. The authentication methods are managed in the AUTH control stage, but the enabled  status is managed in the COMMON control stage. So our policy must be configured to be applied between the two. We choose COMMON_POST because it is the earliest corresponding to our requirements.

Let's configure the policy:

name

grant_myMFA

objectKind

IDENTITY

objectTypes

[ "employee" ]

stages

COMMON_POST

rules

GROOVY
if (OBJECT.enabled && OBJECT.location in ['France', 'Spain', 'Germany', 'Switzerland']) {
  OBJECT.addValue("rights", RightGrant.of("auth.my-mfa"), true, false);
}

The right will only be added if not already granted ( distinct  = true).

Birthright Role

We want to automatically grant a birthright Role to Identities of type "employee" marked 'VIP' whose status is transitioned to "NORMAL". As status checks are performed in the COMMON stage, we decide to do this right after.

Let's configure the policy:

name

assign_birthright

objectKind

IDENTITY

objectTypes

[ "employee" ]

stages

COMMON_POST

rules

CODE
if (! OBJECT.vip || OBJECT.status != ObjectStatus.NORMAL) {
  return // quick exit strategy
}
if (OPERATION.operation == ObjectOperation.CREATE || OPERATION.originalObject.status != ObjectStatus.DRAFT) {
  // Test for transition only on Patches, apply behavior always on CREATE
  RoleAssignment role = RoleAssignment.forRole('adm.vip').comment("Automatically assigned.").build()
  OBJECT.addValue("roles", role, distinct: true, sorted: false) // add only if not already given
}

Here we add as simple Role Assignment with a comment. The default value for the status  is APPLIED. 

Mark for review

We want to mark Identities for review when they have a given set of Roles and have not been reviewed for more than 6 months. To handle this we created two custom attributes:

  • upForReview  a Boolean attribute that marks the Identity object as needing reviewing

  • lastReviewOn  a Date attribute that provides the last review date, which may be null 

Because we base our logic on the Identity's roles, we need the ASSIGNMENTS stage to be run before our rule, so we choose the ASSIGNMENTS_POST stage.

Let's configure the policy:

name

mark_for_review

objectKind

IDENTITY

objectTypes

[ ]

stages

ASSIGNMENTS_POST

rules

CODE
if (OBJECT.upForReview) {
  return // object already up for review, no need to do further processing
}
def lastReviewDate = OBJECT.value("lastReviewOn", LocalDate.class)
if (lastReviewDate != null && lastReviewDate.isBefore(LocalDate.now().minus(6, ChronoUnit.MONTHS))) {
  return // last review was less than 6 months ago
}
def needsReview = false;
for (role as RoleAssignment in OBJECT.roles) {
  // ... detect whether we need a review or not
}
if (needsReview) {
  OBJECT.upForReview = true;
}
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.