IDM 7.4.1

Object modeling

Guide to creating and managing objects in ForgeRock® Identity Management.

IDM provides a default schema for typical managed object types, such as users, roles, and groups, but does not control the structure of objects that you store in the repository. This section shows you how to change and add to the managed object schema, how to establish relationships between objects, and how to use policies to validate objects. You will also learn how to access IDM objects using queries.

ForgeRock Identity Platform™ serves as the basis for our simple and comprehensive Identity and Access Management solution. We help our customers deepen their relationships with their customers, and improve the productivity and connectivity of their employees and partners. For more information about ForgeRock and about the platform, refer to https://www.forgerock.com.

The ForgeRock Common REST API works across the platform to provide common ways to access web resources and collections of resources.

Managed objects

IDM provides a default schema for typical managed object types, such as users and roles, but does not control the structure of objects that you store in the repository. You can modify or extend the schema for the default object types. You can also create new managed object types for any item that can be collected in a data set. For example, with the right schema, you can set up any device associated with the Internet of Things (IoT).

These topics describe how to work with managed object types. For more information about the IDM object model, refer to Data models and objects reference.

Define the schema

Managed objects and their properties are defined in the managed object configuration.

The default managed object configuration is not a comprehensive list of all the properties that can be stored in IDM. If you use a generic object mapping, you can create a managed object with any arbitrary property, and that property will be stored in IDM. However, if you create an object with properties that are not defined in the managed object configuration, those properties are not visible in the UI. In addition, you won’t be able to configure the "sub-properties" that are described in the following section.

For explicit object mappings, the schema must be mapped to tables and columns in the JDBC database or to organizational units in DS. For more information about explicit and generic object mappings, refer to Object mappings.

  • The admin UI depends on the presence of specific core schema elements, such as users, roles, and assignments (and the default properties nested within them). If you remove such schema elements, and you use the admin UI to configure IDM, you must modify the admin UI code accordingly. For example, if you remove the entire assignment object from the managed object configuration, the UI will throw exceptions wherever it queries this schema element.

  • Managed object properties that contain an underscore (_) are reserved for internal use. Do not create new properties that contain underscores, and do not include these properties in update requests.

Create and modify object types

If the managed object types provided in the default configuration are not sufficient for your deployment, you can create new managed object types. The easiest way to create a new managed object type is to use the admin UI, as follows:

  1. Select Configure > Managed Objects > New Managed Object.

  2. On the New Managed Object page, enter a name and readable title for the object, make optional changes, as necessary, and click Save. The readable title specifies what the object will be called in the UI.

  3. On the Properties tab, specify the schema for the object type (the properties that make up the object).

  4. On the Scripts tab, specify any scripts that will be applied on events associated with that object type. For example, scripts that will be run when an object of that type is created, updated, or deleted.

You can also create a new managed object type by editing your managed object configuration.

Example: Phone object created using the admin UI
{
    "name": "Phone",
    "schema": {
        "$schema": "http://forgerock.org/json-schema#",
        "type": "object",
        "properties": {
            "brand": {
                "description": "The supplier of the mobile phone",
                "title": "Brand",
                "viewable": true,
                "searchable": true,
                "userEditable": false,
                "policies": [],
                "returnByDefault": false,
                "pattern": "",
                "isVirtual": false,
                "type": [
                    "string",
                    "null"
                ]
            },
            "assetNumber": {
                "description": "The asset tag number of the mobile device",
                "title": "Asset Number",
                "viewable": true,
                "searchable": true,
                "userEditable": false,
                "policies": [],
                "returnByDefault": false,
                "pattern": "",
                "isVirtual": false,
                "type": "string"
            },
            "model": {
                "description": "The model number of the mobile device, such as 6 plus, Galaxy S4",
                "title": "Model",
                "viewable": true,
                "searchable": false,
                "userEditable": false,
                "policies": [],
                "returnByDefault": false,
                "pattern": "",
                "isVirtual": false,
                "type": "string"
            }
        },
        "required": [],
        "order": [
            "brand",
            "assetNumber",
            "model"
        ]
    }
}

Every managed object type has a name and a schema that describes the properties associated with that object. The name can only include the characters a-z, A-Z, 0-9, and _ (underscore). You can add any arbitrary properties to the schema.

Avoid using the dash character in property names (like last-name) because dashes in names make JavaScript syntax more complex. Rather use "camel case" (lastName). If you cannot avoid dash characters, write source['last-name'] instead of source.last-name in your JavaScript.

A property definition typically includes the following fields:

title

The name of the property, in human-readable language, used to display the property in the UI.

description

A brief description of the property.

viewable

Specifies whether this property is viewable in the object’s profile in the UI. Boolean, true or false (true by default).

searchable

Specifies whether this property can be searched in the UI. A searchable property is visible within the Managed Object data grid in the End User UI.

For a property to be searchable in the UI, it must be indexed in the repository configuration. For information on indexing properties in a repository, refer to Object mappings.

Boolean, true or false (false by default).

userEditable

Specifies whether users can edit the property value in the UI. This property applies in the context of the End User UI, where users are able to edit certain properties of their own accounts. Boolean, true or false (false by default).

isProtected

Specifies whether reauthentication is required if the value of this property changes.

For certain properties, such as passwords, changing the value of the property should force an end user to reauthenticate. These properties are referred to as protected properties. Depending on how the user authenticates (which authentication module is used), the list of protected properties is added to the user’s security context. For example, if a user logs in with the login and password of their managed user entry (MANAGED_USER authentication module), their security context will include this list of protected properties. The list of protected properties is not included in the security context if the user logs in with a module that does not support reauthentication (such as through a social identity provider).

pattern

Any specific pattern to which the value of the property must adhere. For example, a property whose value is a date might require a specific date format.

policies

Any policy validation that must be applied to the property. For more information on managed object policies, refer to Default policy for managed objects.

required

Specifies whether the property must be supplied when an object of this type is created. Boolean, true or false.

The required policy is assessed only during object creation, not when an object is updated. You can effectively bypass the policy by updating the object and supplying an empty value for that property. To prevent this inconsistency, set both required and notEmpty to true for required properties. This configuration indicates that the property must exist, and must have a value.
type

The data type for the property value; can be string, array, boolean, integer, number, object, Resource Collection, or null.

If any user might not have a value for a specific property (such as a telephoneNumber), you must include null as one of the property types. You can set a null property type in the admin UI (Configure > Managed Objects > User, select the property, and under the Details tab, Advanced Options, set Nullable to true).

You can also set a null property type in your managed object configuration by setting "type" : '[ "string","null" ]' for that property (where string can be any other valid property type. This information is validated by the policy service, as described in Validate Managed Object Data Types.

If you’re configuring a data type of array through the admin UI, you’re limited to two values.

isVirtual

Specifies whether the property takes a static value, or whether its value is calculated "on the fly" as the result of a script. Boolean, true or false.

returnByDefault

For non-core attributes (virtual attributes and relationship fields), specifies whether the property will be returned in the results of a query on an object of this type if it is not explicitly requested. Virtual attributes and relationship fields are not returned by default. Boolean, true or false. When the property is in an array within a relationship, always set to false.

relationshipGrantTemporalConstraintsEnforced

For attributes with relationship fields. Specifies whether this relationship should have temporal constraints enforced. Boolean, true or false. For more information about temporal constraints, refer to Use Temporal Constraints to Restrict Effective Roles.

default

Specifies a default value if the object is created without passing a value. Default values are available for the following data types, and arrays of those types:

  • boolean

  • number

  • object

  • string

IDM assumes all default values are valid for the schema.

Default values

You can specify default values in the managed object configuration. If you omit a default value when creating an object, the default value is automatically applied to the object. You can have default values for the following data types, and arrays of those types:

  • boolean

  • number

  • object

  • string

For example, the default managed object configuration includes a default value that makes accountStatus:active, which effectively replaces the onCreate script that was previously used to achieve the same result. The following excerpt from the managed object configuration displays the default value for accountStatus:

"accountStatus" : {
    "title" : "Status",
    "description" : "Status",
    "viewable" : true,
    "type" : "string",
    "searchable" : true,
    "userEditable" : false,
    "usageDescription" : "",
    "isPersonal" : false,
    "policies" : [
        {
            "policyId": "regexpMatches",
            "params": {
                "regexp": "^(active|inactive)$"
            }
        }
    ],
    "default" : "active"
}

IDM assumes all default values are valid for the schema. Although IDM skips policy validation for objects with default values, you can force validation on property values.

Virtual properties

Properties can be derived from other properties within an object. This lets computed and composite values be created in the object. Such derived properties are named virtual properties. The value of a virtual property can be calculated in two ways:

  • Using a script called by the onRetrieve script hook. This script then calculates the current value of the virtual property based on the related properties.

  • Using a query to identify the relationship fields to traverse to reach the managed objects whose state is included in the virtual property, and the fields in these managed objects to include in the value of the virtual property.

    These properties are called relationship-derived virtual properties.

Virtual properties using onRetrieve scripts

The onRetrieve script hook lets you run a script when the object is retrieved. In the case of virtual properties, this script gets the data from related properties and uses it to calculate a value for the virtual property. For more information about running scripts on managed objects, refer to Run scripts on managed objects.

Relationship-derived virtual properties

Virtual properties can be calculated by IDM based on relationships and relationship notifications. This means that, rather than calculating the current state when retrieved, the managed object that contains the virtual property is notified of changes in a related object, and the virtual property is recalculated when this notification is received. To configure virtual properties to use relationship notifications, there are two areas that need to be configured:

  • The related managed objects must be configured to use relationship notifications. This lets IDM know where to send notifications of changes in related objects.

  • To calculate the value of a virtual property, you must configure which relationships to check, and in which order, a notification of a change in a related object is received. You configure this using the queryConfig property.

The queryConfig property tells IDM the sequence of relationship fields it should traverse in order to calculate (or recalculate) a virtual property, and which fields it should return from that related object. This is done using the following fields:

  • referencedRelationshipFields is an array listing a sequence of relationship fields connecting the current object with the related objects you want to calculate the value of the virtual property from. The first field in the array is a relationship field belonging to the same managed object as the virtual property. The second field is a relationship in the managed object referenced by the first field, and so on.

    For example, the referencedRelationshipFields for effectiveAssignments is ["roles","assignments"]. The first field refers to the roles relationship field in managed/user, which references the managed/role object. It then refers to the assignments relationship in managed/role, which references the managed/assignment object. Changes to either related object (managed/role or managed/assignment) will cause the virtual property value to be recalculated, due to the notify, notifySelf, and notifyRelationships configurations on managed user, role, and assignment. These configurations ensure that any changes in the relationships between a user and their roles, or their roles, and their assignments, as well as any relevant changes to the roles or assignments themselves, such as the modification of temporal constraints on roles, or attributes on assignments, will be propagated to connected users, so their effectiveRoles and effectiveAssignments can be recalculated and potentially synced.

  • referencedObjectFields is an array of object fields that should be returned as part of the virtual property. If this property is not included, the returned properties will be a reference for the related object. To return the entire related object, use *.

  • flattenProperties is a boolean that specifies whether relationship-derived virtual properties should be returned as plain fields rather than as JSON objects with an _id and a _rev. This property is false by default.

    With flattenProperties set to false, and referencedObjectFields set to name, the response to a query on a user’s effectiveAssignments might look something like this:

    "effectiveAssignments": [
        {
            "name": "MyFirstAssignment",
            "_id": "02b166cc-d7ed-46b7-813f-5ed103145e76",
            "_rev": "2"
        },
        {
            "name": "MySecondAssignment",
            "_id": "7162ddd4-591a-413e-a30b-3a5864bee5ec",
            "_rev": "0"
        }
    ]

    With flattenProperties set to true, and referencedObjectFields set to name, the response to the same query looks like this:

    "effectiveAssignments": [
        "MyFirstAssignment",
        "MySecondAssignment"
    ]

    Setting flattenProperties to true also lets singleton relationship-derived virtual properties be initialized to null.

Using queryConfig, the virtual property is recalculated when it receives a notice that changes occurred in the related objects. This can be significantly more efficient than recalculating whenever an object is retrieved, while still ensuring the state of the virtual property is correct.

When you change which fields to return using referencedObjectFields, changes are not reflected until there is a change in the related object that would trigger the virtual property to be recalculated (as specified by the notify, notifySelf, and notifyRelationships configurations). The calculated state of the virtual property is still correct, but since a change is necessary for the state to be updated, the returned fields will still be based on the previous configuration.

The effectiveAssignments property in managed.json is an example of a relationship-derived virtual property:

"effectiveAssignments" : {
    "type" : "array",
    "title" : "Effective Assignments",
    "description" : "Effective Assignments",
    "viewable" : false,
    "returnByDefault" : true,
    "isVirtual" : true,
    "queryConfig" : {
        "referencedRelationshipFields" : [ "roles", "assignments" ],
        "referencedObjectFields" : [ "*" ]
    },
    "usageDescription" : "",
    "isPersonal" : false,
    "items" : {
        "type" : "object",
        "title" : "Effective Assignments Items"
    }
}

Run scripts on managed objects

Before implementing a script, it’s highly recommended that you validate the script over REST. Use scripts in a test environment before deploying them to a production environment.

A number of script hooks let you manipulate managed objects using scripts. Scripts can be triggered during various stages of the lifecycle of the managed object, and are defined in the managed object schema.

You can trigger scripts when a managed object is created (onCreate), updated (onUpdate), retrieved (onRetrieve), deleted (onDelete), validated (onValidate), or stored in the repository (onStore). You can also trigger a script when a change to a managed object triggers an implicit synchronization operation (onSync).

Post-action scripts let you manipulate objects after they are created (postCreate), updated (postUpdate), and deleted (postDelete).

For more information, refer to:

Track user metadata

Some self-service features, such as progressive profile completion, privacy and consent, and terms and conditions acceptance, rely on user metadata that tracks information related to a managed object state. Such data might include when the object was created, or the date of the most recent change, for example. This metadata is not stored within the object itself, but in a separate resource location.

Because object metadata is stored outside the managed object, state change situations (such as the time of an update) are separate from object changes (the update itself). This separation reduces unnecessary synchronization to targets when the only data that has changed is metadata. Metadata is not returned in a query unless it is specifically requested. Therefore, the volume of data that is retrieved when metadata is not required, is reduced.

To specify which metadata you want to track for an object, add a meta stanza to the object definition in your managed object configuration. The following default configuration tracks the createDate and lastChanged date for managed user objects:

{
  "objects" : [
    {
      "name" : "user",
      ...
      "schema" : {
        ...
      },
      "meta" : {
        "property" : "_meta",
        "resourceCollection" : "internal/usermeta",
        "trackedProperties" : [
          "createDate",
          "lastChanged"
        ]
      },
      ...
    },
    ...
  ]
}

If you are not using the self-service features that require metadata, you can remove the meta stanza from the user object in your managed object configuration. Preventing the creation and tracking of metadata where it is not required will improve performance in that scenario.

The metadata configuration includes the following properties:

property

The property that will be dynamically added to the managed object schema for this object.

resourceCollection

The resource location in which the metadata will be stored.

Adjust your repository to match the location you specify here. It’s recommended that you use an internal object path and define the storage in your repo.jdbc.json or repo.ds.json file.

For a JDBC repository, metadata is stored in the metaobjects table by default. The metaobjectproperties table is used for indexing.

For a DS repository, metadata is stored under ou=usermeta,ou=internal,dc=openidm,dc=forgerock,dc=com by default.

User objects stored in a DS repository must include the ou specified in the preceding dnTemplate attribute. For example:

dn: ou=usermeta,ou=internal,dc=openidm,dc=forgerock,dc=com
objectclass: organizationalunit
objectclass: top
ou: usermeta
trackedProperties

The properties that will be tracked as metadata for this object. In the previous example, the createDate (when the object was created) and the lastChanged date (when the object was last modified) are tracked.

You cannot search on metadata and it is not returned in the results of a query unless it is specifically requested. To return all metadata for an object, include _fields=,_meta/* in your request. The following example returns a user entry without requesting the metadata:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/bjensen"
{
  "_id": "bjensen",
  "_rev": "000000000444dd1a",
  "mail": "bjensen@example.com",
  "givenName": "Barbara",
  "sn": "Jensen",
  "description": "Created By CSV",
  "userName": "bjensen",
  "telephoneNumber": "1234567",
  "accountStatus": "active",
  "effectiveRoles": [],
  "effectiveAssignments": []
}

The following example returns the same user entry, with their metadata:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/bjensen?_fields=,_meta/*"
{
  "_id": "bjensen",
  "_rev": "000000000444dd1a",
  "mail": "bjensen@example.com",
  "givenName": "Barbara",
  "sn": "Jensen",
  "description": "Created By CSV",
  "userName": "bjensen",
  "telephoneNumber": "1234567",
  "accountStatus": "active",
  "effectiveRoles": [],
  "effectiveAssignments": []
  "_meta": {
    "_ref": "internal/usermeta/284273ff-5e50-4fa4-9d30-4a3cf4a5f642",
    "_refResourceCollection": "internal/usermeta",
    "_refResourceId": "284273ff-5e50-4fa4-9d30-4a3cf4a5f642",
    "_refProperties": {
      "_id": "30076e2e-8db5-4b4d-ab91-5351d2da4620",
      "_rev": "000000001ad09f00"
    },
    "createDate": "2018-04-12T19:53:19.004Z",
    "lastChanged": {
      "date": "2018-04-12T19:53:19.004Z"
    },
    "loginCount": 0,
    "_rev": "0000000094605ed9",
    "_id": "284273ff-5e50-4fa4-9d30-4a3cf4a5f642"
  }
}

Apart from the createDate and lastChanged shown previously, the request also returns the loginCount. This property is stored by default for all objects, and increments with each login request based on password or social authentication. If the object for which metadata is tracked is not an object that "logs in," this field will remain 0.

The request also returns a _meta property that includes relationship information. IDM uses the relationship model to store the metadata. When the meta stanza is added to the user object definition, the attribute specified by the property ("property" : "_meta", in this case) is added to the schema as a uni-directional relationship to the resource collection specified by resourceCollection. In this example, the user object’s _meta field is stored as an internal/usermeta object. The _meta/_ref property shows the full resource path to the internal object where the metadata for this user is stored.

Users

User objects that are managed by IDM are called managed users.

For a JDBC repository, IDM stores managed users in the managedobjects table. A second table, managedobjectproperties, serves as the index table.

IDM provides RESTful access to managed users, at the context path /openidm/managed/user. You can add, change, and delete managed users using the admin UI or over the REST interface. To use the admin UI, select Manage > User.

If you are viewing users through the admin UI, the User List page supports specialized filtering with the Advanced Filter option. This lets you build many of the queries shown in Define and call data queries.

Managed users examples

The following examples show how to add, change, and delete users over the REST interface. For a reference of all managed user endpoints and actions, refer to the Managed users endpoint.

You can also use the REST API Explorer as a reference to the managed object REST API.

Some examples in this documentation use client-assigned IDs (such as bjensen and scarter) when creating objects because it makes the examples easier to read. If you create objects using the admin UI, they are created with server-assigned IDs (such as 55ef0a75-f261-47e9-a72b-f5c61c32d339). Generally, immutable server-assigned UUIDs are used in production environments.

Retrieve the IDs of all managed users in the repository

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user?_queryFilter=true&_fields=_id"
{
  "result": [
    {
      "_id": "bjensen",
      "_rev": "0000000079b78ace"
    },
    {
      "_id": "scarter",
      "_rev": "0000000070e587a7"
    },
    ...
  ],
  ...
}

Query managed users for a specific user

The _queryFilter requires double quotes, or the URL-encoded equivalent (%22), around the search term. This example uses the URL-encoded equivalent:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user?_queryFilter=userName+eq+%22scarter%22"
{
  "result": [
    {
      "_id": "scarter",
      "_rev": "0000000070e587a7",
      "userName": "scarter",
      "givenName": "Sam",
      "sn": "Carter",
      "telephoneNumber": "12345678",
      "active": "true",
      "mail": "scarter@example.com",
      "accountStatus": "active",
      "effectiveAssignments": [],
      "effectiveRoles": []
    }
  ],
  ...
}

This example uses single quotes around the URL to avoid conflicts with the double quotes around the search term:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=userName+eq+"scarter"'
{
  "result": [
    {
      "_id": "scarter",
      "_rev": "0000000070e587a7",
      "userName": "scarter",
      "givenName": "Sam",
      "sn": "Carter",
      "telephoneNumber": "12345678",
      "active": "true",
      "mail": "scarter@example.com",
      "accountStatus": "active",
      "effectiveAssignments": [],
      "effectiveRoles": []
    }
  ],
  ...
}

Retrieve a managed user by their ID

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/scarter"
{
  "_id": "scarter",
  "_rev": "0000000070e587a7",
  "userName": "scarter",
  "givenName": "Sam",
  "sn": "Carter",
  "telephoneNumber": "12345678",
  "active": "true",
  "mail": "scarter@example.com",
  "accountStatus": "active",
  "effectiveAssignments": [],
  "effectiveRoles": []
}

Add a user with a specific user ID

curl \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "If-None-Match: *" \
--request PUT \
--data '{
  "userName": "bjackson",
  "sn": "Jackson",
  "givenName": "Barbara",
  "mail": "bjackson@example.com",
  "telephoneNumber": "082082082",
  "password": "Passw0rd"
}' \
"http://localhost:8080/openidm/managed/user/bjackson"
{
  "_id": "bjackson",
  "_rev": "0000000055c185c5",
  "userName": "bjackson",
  "sn": "Jackson",
  "givenName": "Barbara",
  "mail": "bjackson@example.com",
  "telephoneNumber": "082082082",
  "accountStatus": "active",
  "effectiveAssignments": [],
  "effectiveRoles": []
}

Add a user with a system-generated ID

curl \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request POST \
--data '{
  "userName": "pjensen",
  "sn": "Jensen",
  "givenName": "Pam",
  "mail": "pjensen@example.com",
  "telephoneNumber": "082082082",
  "password": "Passw0rd"
}' \
"http://localhost:8080/openidm/managed/user?_action=create"
{
  "_id": "9d92cdc8-8b22-4037-a344-df960ea66194",
  "_rev": "00000000a4bf9006",
  "userName": "pjensen",
  "sn": "Jensen",
  "givenName": "Pam",
  "mail": "pjensen@example.com",
  "telephoneNumber": "082082082",
  "accountStatus": "active",
  "effectiveAssignments": [],
  "effectiveRoles": []
}

Update a user

This example checks whether user bjensen exists, then replaces her telephone number with the new data provided in the request body:

curl \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request POST \
--data '[
  {
    "operation": "replace",
    "field": "/telephoneNumber",
    "value": "0763483726"
  }
]' \
"http://localhost:8080/openidm/managed/user?_action=patch&_queryFilter=userName+eq+'bjackson'"
{
  "userName": "bjackson",
  "sn": "Jackson",
  "givenName": "Barbara",
  "mail": "bjackson@example.com",
  "telephoneNumber": "0763483726",
  "accountStatus": "active",
  "effectiveAssignments": [],
  "effectiveRoles": [],
  "_rev": "000000008c0f8617",
  "_id": "bjackson"
}

Delete a user

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request DELETE \
"http://localhost:8080/openidm/managed/user/bjackson"
{
  "_id": "bjackson",
  "_rev": "000000008c0f8617",
  "userName": "bjackson",
  "sn": "Jackson",
  "givenName": "Barbara",
  "mail": "bjackson@example.com",
  "telephoneNumber": "0763483726",
  "accountStatus": "active",
  "effectiveAssignments": [],
  "effectiveRoles": []
}

Relationships between objects

Relationships are references between managed objects. Roles and Organizations are implemented using relationships, but you can create relationships between any managed object type.

Define a relationship type

Relationships are defined in your managed object configuration. The default configuration includes a relationship named manager that lets you configure a management relationship between two managed users. The manager relationship is a good example from which to understand how relationships work.

The default manager relationship is configured as follows:

"manager" : {
    "type" : "relationship",
    "validate" : true,
    "reverseRelationship" : true,
    "reversePropertyName" : "reports",
    "description" : "Manager",
    "title" : "Manager",
    "viewable" : true,
    "searchable" : false,
    "usageDescription" : "",
    "isPersonal" : false,
    "properties" : {
        "_ref" : {
            "description" : "References a relationship from a managed object",
            "type" : "string"
        },
        "_refProperties" : {
            "description" : "Supports metadata within the relationship",
            "type" : "object",
            "title" : "Manager _refProperties",
            "properties" : {
                "_id" : {
                    "description" : "_refProperties object ID",
                    "type" : "string"
                }
            }
        }
    },
    "resourceCollection" : [
        {
            "path" : "managed/user",
            "label" : "User",
            "query" : {
                "queryFilter" : "true",
                "fields" : [
                    "userName",
                    "givenName",
                    "sn"
                ]
            }
        }
    ],
    "userEditable" : false
},

Most of these properties apply to any managed object type. Relationships have the following specific configurable properties:

type (string)

The object type. Must be relationship for a relationship object.

returnByDefault (boolean true, false)

Specifies whether the relationship should be returned as part of the response. The returnByDefault property is not specific to relationships. This flag applies to all managed object types. However, relationship properties are not returned by default, unless explicitly requested.

reverseRelationship (boolean true, false)

Specifies whether this is a bidirectional relationship.

reversePropertyName (string)

The corresponding property name, in the case of a bidirectional relationship. For example, the manager property has a reversePropertyName of reports.

_ref (JSON object)

Specifies how the relationship between two managed objects is referenced.

In the relationship definition, the value of this property is { "type" : "string" }. In a managed user entry, the value of the _ref property is the reference to the other resource. The _ref property is described in more detail in Create a relationship between two objects.

_refProperties (JSON object)

Any required properties from the relationship that should be included in the managed object. The _refProperties field includes a unique ID (_id) and the revision (_rev) of the object. _refProperties can also contain arbitrary fields to support metadata within the relationship.

resourceCollection (JSON object)

The collection of resources (objects) on which this relationship is based (for example, managed/user objects).

Create a relationship between two objects

When you have defined a relationship type, (such as the manager relationship, described in the previous section), you can reference one managed user from another, using the _ref* relationship properties. Three properties make up a relationship reference:

  • _refResourceCollection specifies the container of the referenced object (for example, managed/user).

  • _refResourceId specifies the ID of the referenced object. This is generally a system-generated UUID, such as 9dce06d4-2fc1-4830-a92b-bd35c2f6bcbb. For clarity, this section uses client-assigned IDs such as bjensen and psmith.

  • _ref is a derived path that is a combination of _refResourceCollection and a URL-encoded _refResourceId.

For example, imagine that you are creating a new user, psmith, and that psmith’s manager will be bjensen. You would add psmith’s user entry, and reference bjensen’s entry with the _ref property, as follows:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "If-None-Match: *" \
--header "Content-Type: application/json" \
--request PUT \
--data '{
  "sn":"Smith",
  "userName":"psmith",
  "givenName":"Patricia",
  "displayName":"Patti Smith",
  "description" : "psmith - new user",
  "mail" : "psmith@example.com",
  "phoneNumber" : "0831245986",
  "password" : "Passw0rd",
  "manager" : {"_ref" : "managed/user/bjensen"}
}' \
"http://localhost:8080/openidm/managed/user/psmith"
{
  "_id": "psmith",
  "_rev": "00000000ec41097c",
  "sn": "Smith",
  "userName": "psmith",
  "givenName": "Patricia",
  "displayName": "Patti Smith",
  "description": "psmith - new user",
  "mail": "psmith@example.com",
  "phoneNumber": "0831245986",
  "accountStatus": "active",
  "effectiveRoles": [],
  "effectiveAssignments": []
}

Note that relationship information is not returned by default. To show the relationship in psmith’s entry, you must explicitly request her manager entry, as follows:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/psmith?_fields=manager"
{
  "_id": "psmith",
  "_rev": "00000000ec41097c",
  "manager": {
    "_ref": "managed/user/bjensen",
    "_refResourceCollection": "managed/user",
    "_refResourceId": "bjensen",
    "_refProperties": {
      "_id": "ffc6f0f3-93db-4939-b9eb-1f8389a59a52",
      "_rev": "0000000081aa991a"
    }
  }
}

If a relationship changes, you can query the updated relationship state when any referenced managed objects are queried. So, after creating user psmith with manager bjensen, a query on bjensen’s user entry will show a reference to psmith’s entry in her reports property (because the reports property is configured as the reversePropertyName of the manager property). The following query shows the updated relationship state for bjensen:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/bjensen?_fields=reports"
{
  "_id": "bjensen",
  "_rev": "0000000057b5fe9d",
  "reports": [
    {
      "_ref": "managed/user/psmith",
      "_refResourceCollection": "managed/user",
      "_refResourceId": "psmith",
      "_refProperties": {
        "_id": "ffc6f0f3-93db-4939-b9eb-1f8389a59a52",
        "_rev": "0000000081aa991a"
      }
    }
  ]
}

IDM maintains referential integrity by deleting the relationship reference, if the object referred to by that relationship is deleted. In our example, if bjensen’s user entry is deleted, the corresponding reference in psmith’s manager property is removed.

Configure relationship change notification

A relationship exists between two managed objects. By default, when a relationship changes (when it is created, updated, or deleted), the managed objects on either side of the relationship are not notified of that change. This means that the state of each object with respect to that relationship field is not recalculated until the object is read. This default behavior improves performance, especially in the case where many objects are affected by a single relationship change.

For roles, a special kind of relationship, change notification is configured by default. The purpose of this default configuration is to notify managed users when any of the relationships that link users, roles, and assignments are manipulated. For more information about relationship change notification in the specific case of managed roles, refer to Roles and relationship change notification.

To change the default configuration, or to set up notification for other relationship changes, use the notify* properties in the relationship definition, as described in this section.

A relationship exists between an origin object and a referenced object. These terms reflect which managed object is specified in the URL (for example managed/user/psmith), and which object is referenced by the relationship (_ref*) properties. For more information about the relationship properties, refer to Create a relationship between two objects.

In the previous example, a PUT on managed/user/psmith with "manager" : {_ref : "managed/user/bjensen"}, causes managed/user/psmith to be the origin object, and managed/user/bjensen to be the referenced object for that relationship, as shown in the following illustration:

Illustration shows the origin and referenced objects in a relationship
Figure 1. Relationship Objects

Note that for the reverse relationship (a PUT on managed/user/bjensen with "reports" : [{_ref = "managed/user/psmith"}]) managed/user/bjensen would be the origin object, and managed/user/psmith would be the referenced object.

By default, when a relationship changes, neither the origin object nor the referenced object is notified of the change. So, with the PUT on managed/user/psmith with "manager" : {_ref : "managed/user/bjensen"}, neither psmith’s object nor bjensen’s object is notified.

Auditing is not tied to relationship change notification and is always triggered when a relationship changes. Therefore, relationship changes are audited, regardless of the notify and notifySelf properties.

To configure relationship change notification, set the notify and notifySelf properties in your managed object schema. These properties specify whether objects that reference relationships are notified of a relationship change:

notifySelf

Notifies the origin object of the relationship change.

In our example, if the manager definition includes "notifySelf" : true, and if the relationship is changed through a URL that references psmith, then psmith’s object would be notified of the change. For example, for a CREATE, UPDATE or DELETE request on the psmith/manager, psmith would be notified, but the managed object referenced by this relationship (bjensen) would not be notified.

If the relationship were manipulated through a request to bjensen/reports, then bjensen would only be notified if the reports relationship specified "notifySelf" : true.

notify

Notifies the referenced object of the relationship change. Set this property on the resourceCollection of the relationship property.

In our example, assume that the manager definition has a resourceCollection with a path of managed/user, and that this object specifies "notify" : true. If the relationship changes through a CREATE, UPDATE, or DELETE on the URL psmith/manager, then the reference object (managed/user/bjensen) would be notified of the change to the relationship.

notifyRelationships

This property controls the propagation of notifications out of a managed object when one of its properties changes through an update or patch, or when that object receives a notification through one of these fields.

The notifyRelationships property takes an array of relationships as a value; for example, "notifyRelationships" : ["relationship1", "relationship2"]. The relationships specified here are fields defined on the managed object type (which might itself be a relationship).

Notifications are propagated according to the recipient’snotifyRelationships configuration. If a managed object type is notified of a change through one if its relationship fields, the notification is done according to the configuration of the recipient object. To illustrate, look at the attributes property in the default managed/assignment object:

{
    "name" : "assignment",
    "schema" : {
        ...
        "properties" : {
            ...
            "attributes" : {
                "description" : "The attributes operated on by this assignment.",
                "title" : "Assignment Attributes",
                ...
                "notifyRelationships" : ["roles"]
            },
...

This configuration means that if an assignment is updated or patched, and the assignment’s attributes change in some way, all the roles connected to that assignment are notified. Because the role managed object has "notifyRelationships" : ["members"] defined on its assignments field, the notification that originated from the change to the assignment attribute is propagated to the connected roles, and then out to the members of those roles.

So, the role is notified through its assignments field because an attribute in the assignment changed. This notification is propagated out of the members field because the role definition has "notifyRelationships" : ["members"] on its assignments field.

By default, roles, assignments, and members use relationship change notification to ensure that relationship changes are accurately provisioned.

For example, the default user object includes a roles property with notifySelf set to true:

{
   "name" : "user",
   ...
   "schema" : {
       ...
       "properties" : {
           ...
           "roles" : {
               "description" : "Provisioning Roles",
               ...
               "items" : {
                   "type" : "relationship",
                   ...
                   "reverseRelationship" : true,
                   "reversePropertyName" : "members",
                   "notifySelf" : true,
                   ...
               }
...

In this case, notifySelf indicates the origin or user object. If any changes are made to a relationship referencing a role through a URL that includes a user, the user will be notified of the change. For example, if there is a CREATE on managed/user/psmith/roles which specifies a set of references to existing roles, user psmith will be notified of the change.

Similarly, the role object includes a members property. That property includes the following schema definition:

{
    "name" : "role",
    ...
    "schema" : {
        ...
        "properties" : {
            ...
            "members" : {
                ...
                "items" : {
                    "type" : "relationship",
                    ...
                    "properties" : {
                        ...
                        "resourceCollection" : [
                            {
                                "notify" : true,
                                "path" : "managed/user",
                                "label" : "User",
                                ...
                            }
                        ]
                    }
...

Notice the "notify" : true setting on the resourceCollection. This setting indicates that if the relationship is created, updated, or deleted through a URL that references that role, all objects in that resource collection (in this case, managed/user objects) that are identified as members of that role must be notified of the change.

  • To notify an object at the end of a relationship that the relationship has changed (using the notify property), the relationship must be bidirectional ("reverseRelationship" : true).

    When an object is notified of a relationship state change (create, delete, or update), part of that notification process involves calculating the changed object state with respect to the changed relationship field. For example, if a managed user is notified that a role has been created, the user object calculates its base state, and the state of its roles field, before and after the new role was created. This before and after state is then reconciled. An object that is referenced by a forward (unidirectional) relationship does not have a field that references that relationship; the object is "pointed-to", but does not "point-back". Because this object cannot calculate its before and after state with respect to the relationship field, it cannot be notified.

    Similarly, relationships that are notified of changes to the objects that reference them must be bidirectional relationships.

    If you configure relationship change notification on a unidirectional relationship, IDM throws an exception.

  • You cannot configure relationship change notification in the admin UI; you must update the managed object configuration directly.

Validate relationships between objects

Optionally, you can specify that a relationship between two objects must be validated when the relationship is created. For example, you can indicate that a user cannot reference a role, if that role does not exist.

When you create a new relationship type, validation is disabled by default, because it involves an expensive query to the relationship that is not always required.

To configure validation of a referenced relationship, set "validate": true in the managed object configuration. The default schema enables validation for the following relationships:

  • For user objects—roles, managers, and reports

  • For role objects—members and assignments

  • For assignment objects—roles

The following configuration of the manager relationship enables validation, and prevents a user from referencing a manager that has not already been created:

"manager" : {
   "type" : "relationship",
   ...
   "validate" : true,

Create bidirectional relationships

In most cases, you define a relationship between two objects in both directions. For example, a relationship between a user and his manager might indicate a reverse relationship between the manager and her direct report. Reverse relationships are particularly useful for queries. You might want to query jdoe’s user entry to discover who his manager is, or query bjensen’s user entry to discover all the users who report to bjensen.

You declare a reverse relationship as part of the relationship definition. Consider the following sample excerpt of the default managed object configuration:

"reports" : {
    "description" : "Direct Reports",
    "title" : "Direct Reports",
    ...
    "type" : "array",
    "returnByDefault" : false,
    "items" : {
        "type" : "relationship",
        "reverseRelationship" : true,
        "reversePropertyName" : "manager",
        "validate" : true,
        ...
    }
...

The reports property is a relationship between users and managers. So, you can refer to a managed user’s reports by referencing the reports. However, the reports property is also a reverse relationship ("reverseRelationship" : true) which means that you can list all users that reference that report.

You can list all users whose manager property is set to the currently queried user.

The reverse relationship includes an optional resourceCollection that lets you query a set of objects, based on specific fields:

"resourceCollection" : [
    {
        "path" : "managed/user",
        "label" : "User",
        "query" : {
            "queryFilter" : "true",
            "fields" : [
                "userName",
                "givenName",
                "sn"
            ]
        }
    }
]

The path property of the resourceCollection points to the set of objects to be queried. If this path is not in the local repository, the link expansion can incur a significant performance cost. Although the resourceCollection is optional, the same performance cost is incurred if the property is absent.

The query property indicates how you will query this resource collection to configure the relationship. In this case, "queryFilter" : "true", indicates that you can search on any of the properties listed in the fields array when you are assigning a manager to a user or a new report to a manager.

To configure these relationships from the admin UI, refer to Manage relationships using the admin UI.

Grant relationships conditionally

Relationships can be granted dynamically, based on a specified condition. In order to conditionally grant a relationship, the schemas for the resources you are creating a relationship between need to be configured to support conditional association. To do this, three fields in the schema are used:

conditionalAssociation

Boolean. This property is applied to the resourceCollection for the grantor of the relationship. For example, the members relationship on managed/role specifies that there is a conditional association with the managed/user resource:

"resourceCollection" : [
    {
        "notify" : true,
        "conditionalAssociation" : true,
        "path" : "managed/user",
        "label" : "User",
        "query" : {
            "queryFilter" : "true",
            "fields" : [
                "userName",
                "givenName",
                "sn"
            ]
        }
    }
]
conditionalAssociationField

This property is a string, specifying the field used to determine whether a conditional relationship is granted. The field is applied to the resourceCollection of the grantee of the relationship. For example, the roles relationship on managed/user specifies that the conditional association with managed/role is defined by the condition field in managed/role:

"resourceCollection" : [
    {
        "path" : "managed/role",
        "label" : "Role",
        "conditionalAssociationField" : "condition",
        "query" : {
            "queryFilter" : "true",
            "fields" : [
                "name"
            ]
        }
    }
]

The field name specified will usually be condition if you are using default schema, but can be any field that evaluates a condition and has been flagged as isConditional.

isConditional

Boolean. This is applied to the field you wish to check to determine whether membership in a relationship is granted. Only one field on a resource can be marked as isConditional. For example, in the relationship between managed/user and managed/role, conditional membership in the relationship is determined by the query filter specified in the managed/role condition field:

"condition" : {
    "description" : "A conditional filter for this role",
    "title" : "Condition",
    "viewable" : false,
    "searchable" : false,
    "isConditional" : true,
    "type" : "string"
}

Conditions support both properties and virtual properties derived from other relationships, if the query property has been configured. Conditions are a powerful tool for dynamically creating relationships between two objects. An example of conditional relationships in use is covered in Grant a Role Based on a Condition.

View relationships over REST

By default, information about relationships is not returned as the result of a GET request on a managed object. You must explicitly include the relationship property in the request, for example:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/psmith?_fields=manager"
{
  "_id": "psmith",
  "_rev": "0000000014c0b68d",
  "manager": {
    "_ref": "managed/user/bjensen",
    "_refResourceCollection": "managed/user",
    "_refResourceId": "bjensen",
    "_refProperties": {
      "_id": "42418f09-ad6c-4b77-bf80-2a12d0c44678",
      "_rev": "00000000288b921e"
    }
  }
}

To obtain more information about the referenced object (psmith’s manager, in this case), you can include additional fields from the referenced object in the query, using the syntax object/property (for a simple string value) or object/*/property (for an array of values).

The following example returns the email address and contact number for psmith’s manager:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/psmith?_fields=manager/mail,manager/telephoneNumber"
{
  "_id": "psmith",
  "_rev": "0000000014c0b68d",
  "manager": {
    "_rev": "000000005bac8c10",
    "_id": "bjensen",
    "telephoneNumber": "12345678",
    "mail": "bjensen@example.com",
    "_ref": "managed/user/bjensen",
    "_refResourceCollection": "managed/user",
    "_refResourceId": "bjensen",
    "_refProperties": {
      "_id": "42418f09-ad6c-4b77-bf80-2a12d0c44678",
      "_rev": "00000000288b921e"
    }
  }
}

To query all the relationships associated with a managed object, query the reference (*_ref) property of that object. For example, the following query shows all the objects that are referenced by psmith’s entry:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/psmith?_fields=*_ref"
{
  "_id": "psmith",
  "_rev": "0000000014c0b68d",
  "reports": [],
  "manager": {
    "_ref": "managed/user/bjensen",
    "_refResourceCollection": "managed/user",
    "_refResourceId": "bjensen",
    "_refProperties": {
      "_id": "42418f09-ad6c-4b77-bf80-2a12d0c44678",
      "_rev": "00000000288b921e"
    }
  },
  "roles": [],
  "_meta": {
    "_ref": "internal/usermeta/601a3086-8c64-4966-b33c-7a213b13d859",
    "_refResourceCollection": "internal/usermeta",
    "_refResourceId": "601a3086-8c64-4966-b33c-7a213b13d859",
    "_refProperties": {
      "_id": "9de71bd7-1e1b-462e-b565-ac0a7d2f9269",
      "_rev": "0000000037f79a00"
    }
  },
  "authzRoles": [],
  "_notifications": [
    {
      "_ref": "internal/notification/3000bb64-4619-490a-8c4b-50ae7ca6b20c",
      "_refResourceCollection": "internal/notification",
      "_refResourceId": "3000bb64-4619-490a-8c4b-50ae7ca6b20c",
      "_refProperties": {
        "_id": "f54b6f84-7d3f-4486-a7c1-676fca03eeab",
        "_rev": "00000000748da107"
      }
    }
  ]
}

To expand that query to show all fields within each relationship, add a wildcard as follows:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/psmith?_fields=*_ref/*"

Which outputs the following:

{
  "_id": "psmith",
  "_rev": "0000000014c0b68d",
  "reports": [],
  "manager": {
    "_rev": "000000005bac8c10",
    "_id": "bjensen",
    "userName": "bjensen",
    "givenName": "Babs",
    "sn": "Jensen",
    "telephoneNumber": "12345678",
    "active": "true",
    "mail": "bjensen@example.com",
    "accountStatus": "active",
    "effectiveAssignments": [],
    "effectiveRoles": [],
    "_ref": "managed/user/bjensen",
    "_refResourceCollection": "managed/user",
    "_refResourceId": "bjensen",
    "_refProperties": {
      "_id": "42418f09-ad6c-4b77-bf80-2a12d0c44678",
      "_rev": "00000000288b921e"
    }
  },
  "roles": [],
  "_meta": {
    "_rev": "0000000079e86d8d",
    "_id": "601a3086-8c64-4966-b33c-7a213b13d859",
    "createDate": "2020-07-29T08:52:20.061794Z",
    "lastChanged": {
      "date": "2020-07-29T11:52:16.424167Z"
    },
    "loginCount": 0,
    "_ref": "internal/usermeta/601a3086-8c64-4966-b33c-7a213b13d859",
    "_refResourceCollection": "internal/usermeta",
    "_refResourceId": "601a3086-8c64-4966-b33c-7a213b13d859",
    "_refProperties": {
      "_id": "9de71bd7-1e1b-462e-b565-ac0a7d2f9269",
      "_rev": "0000000037f79a00"
    }
  },
  "authzRoles": [],
  "_notifications": [
    {
      "_rev": "00000000d93a6598",
      "_id": "3000bb64-4619-490a-8c4b-50ae7ca6b20c",
      "notificationType": "info",
      "message": "Your profile has been updated.",
      "createDate": "2020-07-29T11:52:16.517200Z",
      "_ref": "internal/notification/3000bb64-4619-490a-8c4b-50ae7ca6b20c",
      "_refResourceCollection": "internal/notification",
      "_refResourceId": "3000bb64-4619-490a-8c4b-50ae7ca6b20c",
      "_refProperties": {
        "_id": "f54b6f84-7d3f-4486-a7c1-676fca03eeab",
        "_rev": "00000000748da107"
      }
    }
  ]
}

Metadata is implemented using the relationships mechanism so when you request all relationships for a user (with _ref/), you will also get all the metadata for that user, if metadata is being tracked. For more information, refer to Track user metadata.

View relationships in graph form

The Identity Relationships widget gives a visual display of the relationships between objects.

Add the Identity Relationships widget to a dashboard

This widget is not displayed on any dashboard by default. You can add it as follows:

  1. Log in to the admin UI.

  2. From the navigation bar, click Dashboards, and select a dashboard. Alternatively, create a dashboard.

  3. On the applicable dashboard page, click Add Widget.

  4. In the Add Widget window, click the drop-down menu, scroll down to the Utilities item, and select Identity Relationships.

  5. Click Settings, and make selections from the following drop-down menus:

    • Widget Size—Select from Small, Medium, or Large.

    • Chart Type:

      • Collapsible Tree Layout:

        relationships-collapsible

      • Radial Layout:

        relationships-radial

    • Default Object—Select the object for which to display relationships; for example, User.

    • Display/Search Property—Select one or more properties to search on the default object that will be displayed in the widget; for example, userName and city.

  6. Optionally, click Preview to view a widget preview.

  7. Click Add to add the widget to the dashboard.

Use the Identity Relationships widget

To use the Identity Relationships widget:

  1. Select a user within the widget.

    The following example shows relationships for imartinez, including Manager and Direct Reports:

    relationships-graph

  2. You can interact with the graph in multiple ways:

    • Select or clear Data Types to filter information.

    • Click and drag the graph for a different view.

    • Double-click a user to view their profile.

Manage relationships using the admin UI

This section describes how to set up relationships between managed objects by using the admin UI. You can set up a relationship between any object types. The examples in this section demonstrate how to set up a relationship between users and devices, such as IoT devices.

For illustration purposes, these examples assume that you have started IDM and already have some managed users. If this is not the case, start the server with the sample configuration described in Synchronize data from a CSV file to IDM, and run a reconciliation to populate the managed user repository.

In the following procedures, you will:

Create a new Device object type

This procedure illustrates how to set up a new Device managed object type, and add properties to collect information such as model, manufacturer, and serial number for each device.

  1. From the navigation bar, click Configure > Managed Objects.

  2. On the Managed Objects page, click New Managed Object.

  3. On the New Managed Object page, enter information in the following fields, and click Save:

    Field Value

    Managed Object Name

    Device

    Readable Title

    Device

    Managed Object Icon

    fa-mobile-phone

    Material Design Icon

    phone

    Description

    Devices

    The Managed Objects > Device page displays.

  4. Click the Properties tab.

  5. For each following property, click Add a Property, enter the information, and click Save:

    Property Name Label Type Required

    model

    Model

    String

    ui-initial-mo

    serialNumber

    Serial Number

    String

    ui-initial-mo

    manufacturer

    Manufacturer

    String

    ui-initial-mo

    description

    Description

    String

    ui-initial-mo

    category

    Category

    String

    ui-initial-mo

    After you finish, the properties list should look like this:

    ui-initial-mo
  6. From the navigation bar, click Manage > Device.

  7. For each of the following devices, on the Device List page, click New Device, enter the applicable information, and click Save:

    • Device 1

    • Device 2

    • Device 3

    • Device 4

    Field Value

    Model

    Generic Phone

    Serial Number

    Phone-1

    Manufacturer

    PhoneCo

    Description

    Entry level phone

    Category

    Smart Phone

    Field Value

    Model

    Generic Watch

    Serial Number

    Watch-1

    Manufacturer

    WatchCo

    Description

    Entry level watch

    Category

    Smart Watch

    Field Value

    Model

    Special Phone

    Serial Number

    Phone-2

    Manufacturer

    PhoneCo

    Description

    Intermediate level phone

    Category

    Smart Phone

    Field Value

    Model

    Special Watch

    Serial Number

    Watch-2

    Manufacturer

    WatchCo

    Description

    Intermediate level watch

    Category

    Smart Watch

  8. From the navigation bar, click Manage > Device.

    The Device List page should look similar to the following:

    ui-mo-iot
    The other procedures in this topic assume that you have added these devices.

Configure the relationship between a device and a user

To set up a relationship between the Device object type and the User object type, you must identify the specific property on each object that will form the basis of the relationship. For example, a device must have an owner and a user can own one or more devices. The property type for each of these must be relationship.

In this procedure, you will update the managed Device object type to add a new Relationship type property named owner. You will then link that property to a new property on the managed User object, named device. At the end of the procedure, the updated object types will look as follows:

relationships
Figure 2. Relationship Properties on User and Device Objects
  1. Create a new relationship property on the Device object:

    • From the navigation bar, click Configure > Managed Objects, and select the Device object.

    • On the Managed Objects > Device page, click the Properties tab.

  2. Click Add a Property, enter the information, and click Save

    Property Name Label Type Required

    owner

    Owner

    Relationship

    ui-initial-mo

    You cannot change the property Type after creation. If you create a property with an incorrect Type, you must delete the property and recreate it.
  3. Click the owner property row.

    The Details tab displays the current Relationship Configuration:

    ui-device-relation

  4. Click the + Related Resource area.

  5. In the Add Resource window, select user from the Resource drop-down list.

    This sets up a relationship between the Device object and the managed user object.

  6. From the Display Properties drop-down list, select the user object properties to display when viewing a user’s devices in the UI. For example, you may want to access a user’s userName, mail, and telephoneNumber.

  7. Click Show advanced options. Notice that the Query Filter field is set to true. This setting lets you search on any selected Display Properties when assigning a device to a user.

  8. Click Save.

    Add Resource Window

    The Managed > Device > owner page now displays the one-way relationship between a device and a user.

    Owner to Device one-way relationship

  9. Click Save.

  10. To configure the reverse relationship, click + Two-way Relationship:

    1. In the Reverse Relationship pop-up, select Has Many. This indicates a single user can have more than one device.

      The Configure Reverse Relationship window displays.

      Configure Reverse Relationship Window

    2. In the Reverse property name field, enter the new property name that will be created in the managed user object type, device for this example.

    3. From the Display Properties drop-down list, select the properties of the device object to display when viewing a user in the UI. For example, you might want to access the model and serialNumber of each device.

    4. Click Show advanced options. Notice that the Query Filter field is set to true. This setting allows you to search on any of the selected Display Properties when assigning a device to a user.

    5. Enable Validate relationship.

      This setting ensures the relationship is valid when a device is assigned to a user. IDM verifies the user and device objects exist, and that the device has not already been assigned to another user.

    6. Click Save.

      The Managed > Device > owner page now displays the two-way relationship showing that a user objects can have many devices.

      Devices to owner two-way relationship

    7. Click Save.

  11. From the navigation bar, click Configure > Managed Objects.

  12. On the Managed Objects page, click User.

  13. On The Managed Objects > user page, click the Properties tab.

    Notice the device property was created automatically when you configured the relationship.

Demonstrate the relationship

This procedure demonstrates how devices can be assigned to users, based on the relationship configuration that you set up in the previous procedures.

  1. From the navigation bar, click Manage > User.

  2. On the User List page, click a user entry.

  3. On the User > userName page, click the Device tab, and then click Add Device.

  4. In the Add Device window, click the Device field to display the list of devices that you added in the previous procedure.

    ui-user-add-device

  5. Select two devices, and click Add.

    The Device tab displays the added devices.

    Device tab with two added devices

  6. Click the Show Chart button.

    A graphical representation of the relationship between the user and her devices is displayed:

    ui-device-chart

  7. You can also assign an owner to a device.

    From the navigation bar, click Manage > Device, and click a device that you did not assign in the previous step.

  8. On the Device > model page, click Add Owner.

  9. In the Add Owner window, select a user, and click Add.

  10. Click Save.

To demonstrate the relationship validation, try to assign a device that has already been assigned to a different user.

The UI displays the error message Conflict with Existing Relationship.

View the relationship configuration in the UI

The Managed Objects Relationship Diagram provides a visual display of the relationship configuration between managed objects. Unlike the Identity Relationships widget, described in View relationships in graph form, this widget does not show the actual relationship data, but rather shows the configured relationship types.

This widget is not displayed on any dashboard by default. You can add it as follows:

  1. Log in to the admin UI.

  2. From the navigation bar, click Dashboards, and select a dashboard. Alternatively, create a dashboard.

  3. On the applicable dashboard page, click Add Widget.

  4. In the Add Widget window, click the drop-down menu, scroll down to the Utilities item, and select Managed Objects Relationship Diagram.

    There are no configurable settings for this widget.
  5. The Preview button shows the current relationship configuration. The following image shows the relationship configuration for a basic IDM installation with no specific configuration:

    managed-objects-relationships

    The legend indicates which relationships are required, optional, one-to-one, and one-to-many.

Roles

The managed role object is a default managed object type that uses the relationships mechanism. You should understand how relationships work before you read about IDM roles.

IDM role types

IDM supports two types of roles:

  • Provisioning roles : used to specify how objects are provisioned to an external system.

    Provisioning roles are created as managed roles, at the context path openidm/managed/role/role-name, and are granted to managed users as values of the user’s roles property.

  • Authorization roles : used to specify the authorization rights of a managed object internally, within IDM.

    Authorization roles are created as internal roles, at the context path openidm/internal/role/role-name, and are granted to managed users as values of the user’s authzRoles property.

Provisioning roles and authorization roles use relationships to link the role to the managed object to which it applies. Authorization roles can also be granted statically, during authentication, with the defaultUserRoles property.

For more information, refer to Authentication and roles.

Managed roles

For information about internal authorization roles, and how IDM controls authorization to its own endpoints, refer to Authorization and roles.

Managed roles are defined like any other managed object, and are granted to users through the relationships mechanism. A managed role can be granted manually, as a static value of the user’s roles attribute, or dynamically, as a result of a condition or script. For example, a user might be granted a role such as sales-role dynamically, if that user is in the sales organization.

A user’s roles attribute takes an array of references as a value, where the references point to the managed roles. For example, if user bjensen has been granted two roles (employee and supervisor), the value of bjensen’s roles attribute would look something like the following:

"roles": [
  {
    "_ref": "managed/role/employee",
    "_refResourceCollection": "managed/role",
    "_refResourceId": "employee",
    "_refProperties": {
      "_grantType": "",
      "_id": "bb399428-21a9-4b01-8b74-46a7ac43e0be",
      "_rev": "00000000e43e9ba7"
    }
  },
  {
    "_ref": "managed/role/supervisor",
    "_refResourceCollection": "managed/role",
    "_refResourceId": "supervisor",
    "_refProperties": {
      "_grantType": "",
      "_id": "9f7d124b-c7b1-4bcf-9ece-db4900e37c31",
      "_rev": "00000000e9c19d26"
    }
  }
]

The _refResourceCollection is the container that holds the role. The _refResourceId is the ID of the role. The _ref property is a resource path that is derived from the _refResourceCollection and the URL-encoded _refResourceId. _refProperties provides more information about the relationship.

Some of the examples in this documentation set use client-assigned IDs (such as bjensen and scarter) for the user objects because it makes the examples easier to read. If you create objects using the admin UI, they are created with server-assigned IDs (such as 55ef0a75-f261-47e9-a72b-f5c61c32d339). This particular example uses a client-assigned role ID that is the same as the role name. All other examples in this chapter use server-assigned IDs. Generally, immutable server-assigned UUIDs are used for all managed objects in production environments.

Manipulate roles

These sections show the REST calls to create, read, update, and delete managed roles, and to grant roles to users. For information about using roles to provision users to external systems, refer to Use assignments to provision users.

Create a role

Use the admin UI

  1. From the navigation bar, click Manage > Role.

  2. On the Roles page, click New Role.

  3. On the New Role page, enter a name and description, and click Save.

  4. Optionally, do any of the following, and click Save:

    • To restrict the role grant to a set time period, enable Temporal Constraint, and set the Timezone Offset, Start Date, and End Date.

    • To define a query filter that dynamically grants the role to members, enable Condition, and define the query.

Use REST

To create a role, send a PUT or POST request to the /openidm/managed/role context path. The following example creates a managed role named employee:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request POST \
--data '{
  "name": "employee",
  "description": "Role granted to workers on the company payroll"
}' \
"http://localhost:8080/openidm/managed/role?_action=create"
{
  "_id": "5790220a-719b-49ad-96a6-6571e63cbaf1",
  "_rev": "0000000079c6644f",
  "name": "employee",
  "description": "Role granted to workers on the company payroll"
}

By default, the role name must be unique. To change this behavior, adjust the policy validation on the role property in your managed object configuration.

This employee role has no corresponding assignments. Assignments are what enables the provisioning logic to the external system. Assignments are created and maintained as separate managed objects, and are referred to within role definitions. For more information about assignments, refer to Use assignments to provision users.

List roles

To list all managed roles over REST, query the openidm/managed/role endpoint. The following example shows the employee role that you created in the previous example:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/role?_queryFilter=true"
{
  "result": [
    {
      "_id": "5790220a-719b-49ad-96a6-6571e63cbaf1",
      "_rev": "0000000079c6644f",
      "name": "employee",
      "description": "Role granted to workers on the company payroll"
    }
  ],
  ...
}

To display all configured managed roles in the admin UI, select Manage > Role.

If you have a large number of roles, select Advanced Filter to build a more complex query filter to display only the roles you want.

Grant roles to a user

You grant roles to users through the relationship mechanism. Relationships are essentially references from one managed object to another; in this case, from a user object to a role object. For more information about relationships, refer to Relationships between objects.

You can grant roles statically or dynamically.

To grant a role statically, you must do one of the following:

  • Update the value of the user’s roles property to reference the role.

  • Update the value of the role’s members property to reference the user.

Dynamic role grants use the result of a condition or script to update a user’s list of roles.

Grant roles statically

Grant a role to a user statically using the REST interface or the admin UI as follows:

Use REST

Use one of the following methods to grant a role to a user over REST:

  • Add the user as a role member. The following example adds user scarter as a member of the role (5790220a-719b-49ad-96a6-6571e63cbaf1):

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --header "Content-Type: application/json" \
    --request POST \
    --data '{
      "_ref":"managed/user/scarter",
      "_refProperties":{}
    }' \
    "http://localhost:8080/openidm/managed/role/5790220a-719b-49ad-96a6-6571e63cbaf1/members?_action=create"
    {
      "_id": "4c32ae53-abed-45f8-bc84-c367e2b0e194",
      "_rev": "00000000c67a99ce",
      "_ref": "managed/user/scarter",
      "_refResourceCollection": "managed/user",
      "_refResourceId": "scarter",
      "_refProperties": {
        "_id": "4c32ae53-abed-45f8-bc84-c367e2b0e194",
        "_rev": "00000000c67a99ce"
      }
    }
    This preferred method does not incur an unnecessary performance cost when working with a role that contains many members.
  • Update the user’s roles property to refer to the role.

    The following example grants the employee role (5790220a-719b-49ad-96a6-6571e63cbaf1) to user scarter:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --header "Content-Type: application/json" \
    --request PATCH \
    --data '[
      {
        "operation": "add",
        "field": "/roles/-",
        "value": {"_ref" : "managed/role/5790220a-719b-49ad-96a6-6571e63cbaf1"}
      }
    ]' \
    "http://localhost:8080/openidm/managed/user/scarter"
    {
      "_id": "scarter",
      "_rev": "000000003be825ce",
      "mail": "scarter@example.com",
      "givenName": "Steven",
      "sn": "Carter",
      "description": "Created By CSV",
      "userName": "scarter",
      "telephoneNumber": "1234567",
      "accountStatus": "active",
      "effectiveRoles": [
        {
          "_ref": "managed/role/5790220a-719b-49ad-96a6-6571e63cbaf1"
        }
      ],
      "effectiveAssignments": []
    }

    Note that scarter’s effectiveRoles attribute has been updated with a reference to the new role. For more information about effective roles and effective assignments, refer to Effective roles and effective assignments.

    When you update a user’s existing roles array, use the - special index to add the new value to the set. For more information, refer to Set semantic arrays in Patch Operation: Add.

  • Update the role’s members property to refer to the user.

    The following sample command makes scarter a member of the employee role:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --header "Content-Type: application/json" \
    --request PATCH \
    --data '[
      {
        "operation": "add",
        "field": "/members/-",
        "value": {"_ref" : "managed/user/scarter"}
      }
    ]' \
    "http://localhost:8080/openidm/managed/role/5790220a-719b-49ad-96a6-6571e63cbaf1"
    {
      "_id": "5790220a-719b-49ad-96a6-6571e63cbaf1",
      "_rev": "0000000079c6644f",
      "name": "employee",
      "description": "Role granted to workers on the company payroll"
    }

    The members property of a role is not returned by default in the output. To show all members of a role, you must specifically request the relationship properties (*_ref) in your query. The following example lists the members of the employee role (currently only scarter):

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --request GET \
    "http://localhost:8080/openidm/managed/role/5790220a-719b-49ad-96a6-6571e63cbaf1?_fields=*_ref,name"
    {
      "_id": "5790220a-719b-49ad-96a6-6571e63cbaf1",
      "_rev": "0000000079c6644f",
      "name": "employee",
      "assignments": [],
      "members": [
        {
          "_ref": "managed/user/scarter",
          "_refResourceCollection": "managed/user",
          "_refResourceId": "scarter",
          "_refProperties": {
            "_id": "7ad15a7b-6806-487b-900d-db569927f56d",
            "_rev": "0000000075e09cbf"
          }
        }
      ]
    }
  • You can replace an existing role grant with a new one by using the replace operation in your patch request.

    The following command replaces scarter’s entire roles entry (that is, overwrites any existing roles) with a single entry, the reference to the employee role (ID 5790220a-719b-49ad-96a6-6571e63cbaf1):

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --header "Content-Type: application/json" \
    --request PATCH \
    --data '[
      {
        "operation": "replace",
        "field": "/roles",
        "value": [
          {"_ref":"managed/role/5790220a-719b-49ad-96a6-6571e63cbaf1"}
        ]
      }
    ]' \
    "http://localhost:8080/openidm/managed/user/scarter"
    {
      "_id": "scarter",
      "_rev": "00000000da112702",
      "mail": "scarter@example.com",
      "givenName": "Steven",
      "sn": "Carter",
      "description": "Created By CSV",
      "userName": "scarter",
      "telephoneNumber": "1234567",
      "accountStatus": "active",
      "effectiveRoles": [
        {
          "_ref": "managed/role/5790220a-719b-49ad-96a6-6571e63cbaf1"
        }
      ],
      "effectiveAssignments": []
    }
Use the admin UI

Use one of the following UI methods to grant a role to a user:

Update the user entry:

  1. Select Manage > User, and select the user to whom you want to grant the role.

  2. Select the Provisioning Roles tab, and select Add Provisioning Roles.

  3. Select the role from the drop-down list, and select Add.

Update the role entry:

  1. Select Manage > Role, and select the role that you want to grant.

  2. Select the Role Members tab, and select Add Role Members.

  3. Select the user from the drop-down list, and select Add.

Grant roles dynamically

Grant a role dynamically by using one of the following methods:

  • Use a condition, expressed as a query filter, in the role definition. If the condition is true for a particular member, that member is granted the role. Conditions can be used in both managed and internal roles.

  • Use a custom script to define a more complex role-granting strategy.

Grant a role based on a condition

A role that is granted based on a defined condition is called a conditional role. To create a conditional role, include a query filter in the role definition.

Properties that are used as the basis of a conditional role query must be configured as searchable and must be indexed in the repository configuration. To configure a property as searchable, update the property definition in your managed object configuration. For more information, refer to Create and modify object types.

To create a conditional role by using the admin UI, select Condition on the role Details page, then define the query filter that will be used to assess the condition.

To create a conditional role over REST, include the query filter as a value of the condition property in the role definition. The following example creates a role, fr-employee, that will be granted only to those users who live in France (whose country property is set to FR):

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request POST \
--data '{
  "name": "fr-employee",
  "description": "Role granted to employees resident in France",
  "condition": "/country eq \"FR\""
}' \
"http://localhost:8080/openidm/managed/role?_action=create"
{
  "_id": "eb18a2e2-ee1e-4cca-83fb-5708a41db94f",
  "_rev": "000000004085704c",
  "name": "fr-employee",
  "description": "Role granted to employees resident in France",
  "condition": "/country eq \"FR\""
}

When a conditional role is created or updated, IDM automatically assesses all managed users, and recalculates the value of their roles property, if they qualify for that role. When a condition is removed from a role, that is, when the role becomes an unconditional role, all conditional grants are removed. So, users who were granted the role based on the condition, have that role removed from their roles property.

When a conditional role is defined in an existing data set, every user entry (including the mapped entries on remote systems) must be updated with the assignments implied by that conditional role. The time that it takes to create a new conditional role is impacted by the following items:

  • The number of managed users affected by the condition.

  • The number of assignments related to the conditional role.

  • The average time required to provision updates to all remote systems affected by those assignments.

In a data set with a very large number of users, creating a new conditional role can therefore incur a significant performance cost when you create it. Ideally, you should set up your conditional roles at the beginning of your deployment to avoid performance issues later.

Query a user’s roles

To query user roles over REST, query the user’s roles property. The following example shows that scarter has been granted two roles—an employee role, and an fr-employee role:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/scarter/roles?_queryFilter=true&_fields=_ref/*,name"
{
  "result": [
    {
      "_id": "5a023862-654d-4d7f-b9d0-7c151b8dede5",
      "_rev": "00000000baa999c1",
      "_refResourceCollection": "managed/role",
      "_refResourceId": "b8783543-869a-4bd4-907e-9c1d89f826ae",
      "_refResourceRev": "0000000027a959cf",
      "name": "employee",
      "_ref": "managed/role/b8783543-869a-4bd4-907e-9c1d89f826ae",
      "_refProperties": {
        "_id": "5a023862-654d-4d7f-b9d0-7c151b8dede5",
        "_rev": "00000000baa999c1"
      }
    },
    {
      "_id": "b281ffdf-477e-4211-a112-84476435bab2",
      "_rev": "00000000d612a248",
      "_refResourceCollection": "managed/role",
      "_refResourceId": "01ee6191-75d8-4d4b-9291-13a46592c57a",
      "_refResourceRev": "000000000cb0794d",
      "name": "fr-employee",
      "_ref": "managed/role/01ee6191-75d8-4d4b-9291-13a46592c57a",
      "_refProperties": {
        "_grantType": "conditional",
        "_id": "b281ffdf-477e-4211-a112-84476435bab2",
        "_rev": "00000000d612a248"
      }
    }
  ],
  ...
}

Note that the fr-employee role indicates a _grantType of conditional. This property indicates how the role was granted to the user. If no _grantType is listed, the role was granted statically.

Querying a user’s roles in this way does not return any roles that would be in effect as a result of a custom script, or of any temporal constraint applied to the role. To return a complete list of all the roles in effect at a specific time, query the user’s effectiveRoles property, as follows:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/scarter?_fields=effectiveRoles"

Alternatively, to check which roles have been granted to a user, either statically or dynamically, look at the user’s entry in the admin UI:

  1. Select Manage > User, then select the user whose roles you want to see.

  2. Select the Provisioning Roles tab.

  3. If you have a large number of managed roles, use the Advanced Filter option on the Role List page to build a custom query.

Delete a user’s roles

To remove a statically granted role from a user entry, do one of the following:

  • Update the value of the user’s roles property to remove the reference to the role.

  • Update the value of the role’s members property to remove the reference to that user.

A delegated administrator must use PATCH to add or remove relationships.

Roles that have been granted as the result of a condition can only be removed when the condition is changed or removed, or when the role itself is deleted.

Over REST

Use one of the following methods to remove a role grant from a user:

  • DELETE the role from the user’s roles property, including the reference ID (the ID of the relationship between the user and the role) in the delete request.

    The following example removes the employee role from user scarter. The role ID is b8783543-869a-4bd4-907e-9c1d89f826ae, but the ID required in the DELETE request is the reference ID (5a023862-654d-4d7f-b9d0-7c151b8dede5):

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --request DELETE \
    "http://localhost:8080/openidm/managed/user/scarter/roles/5a023862-654d-4d7f-b9d0-7c151b8dede5"
    {
      "_id": "5a023862-654d-4d7f-b9d0-7c151b8dede5",
      "_rev": "00000000baa999c1",
      "_ref": "managed/role/b8783543-869a-4bd4-907e-9c1d89f826ae",
      "_refResourceCollection": "managed/role",
      "_refResourceId": "b8783543-869a-4bd4-907e-9c1d89f826ae",
      "_refProperties": {
        "_id": "5a023862-654d-4d7f-b9d0-7c151b8dede5",
        "_rev": "00000000baa999c1"
      }
    }
  • PATCH the user entry to remove the role from the array of roles, specifying the value of the role object in the JSON payload.

    When you remove a role in this way, you must include the entire object in the value, as shown in the following example:
    curl \
    --header "Content-type: application/json" \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --request PATCH \
    --data '[
      {
        "operation" : "remove",
        "field" : "/roles",
        "value" : {
          "_ref": "managed/role/b8783543-869a-4bd4-907e-9c1d89f826ae",
          "_refResourceCollection": "managed/role",
          "_refResourceId": "b8783543-869a-4bd4-907e-9c1d89f826ae",
          "_refProperties": {
            "_id": "5a023862-654d-4d7f-b9d0-7c151b8dede5",
            "_rev": "00000000baa999c1"
          }
        }
      }
    ]' \
    "http://localhost:8080/openidm/managed/user/scarter"
    {
      "_id": "scarter",
      "_rev": "000000007b78257d",
      "mail": "scarter@example.com",
      "givenName": "Steven",
      "sn": "Carter",
      "description": "Created By CSV",
      "userName": "scarter",
      "telephoneNumber": "1234567",
      "accountStatus": "active",
      "effectiveRoles": [
        {
          "_ref": "managed/role/01ee6191-75d8-4d4b-9291-13a46592c57a"
        }
      ],
      "effectiveAssignments": [],
      "preferences": {
        "updates": false,
        "marketing": false
      },
      "country": "France"
    }
  • DELETE the user from the role’s members property, including the reference ID (the ID of the relationship between the user and the role) in the delete request.

    The following example first queries the members of the employee role, to obtain the ID of the relationship, then removes bjensen’s membership from that role:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --request GET \
    "http://localhost:8080/openidm/managed/role/b8783543-869a-4bd4-907e-9c1d89f826ae/members?_queryFilter=true"
    {
      "result": [
        {
          "_id": "a5a4bf94-6425-4458-aae4-bbd6ad094f72",
          "_rev": "00000000c25d994a",
          "_ref": "managed/user/bjensen",
          "_refResourceCollection": "managed/user",
          "_refResourceId": "bjensen",
          "_refProperties": {
            "_id": "a5a4bf94-6425-4458-aae4-bbd6ad094f72",
            "_rev": "00000000c25d994a"
          }
        }
      ],
      ...
    }
    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --request DELETE \
    "http://localhost:8080/openidm/managed/role/b8783543-869a-4bd4-907e-9c1d89f826ae/members/a5a4bf94-6425-4458-aae4-bbd6ad094f72"
    {
      "_id": "a5a4bf94-6425-4458-aae4-bbd6ad094f72",
      "_rev": "00000000c25d994a",
      "_ref": "managed/user/bjensen",
      "_refResourceCollection": "managed/user",
      "_refResourceId": "bjensen",
      "_refProperties": {
        "_id": "a5a4bf94-6425-4458-aae4-bbd6ad094f72",
        "_rev": "00000000c25d994a"
      }
    }

Use the admin UI

Use one of the following methods to remove a user’s roles:

Method 1:

  1. Select Manage > User, and select the user whose roles you want to remove.

  2. Select the Provisioning Roles tab, select the role that you want to remove, then select Remove Selected Provisioning Roles.

Method 2:

  1. Select Manage > Role, and select the role whose members you want to remove.

  2. Select the Role Members tab, select the members that you want to remove, then select Remove Selected Role Members.

Delete a role definition

To delete a role over the REST interface, simply delete that managed object. The following command deletes the employee role created in the previous section:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request DELETE \
"http://localhost:8080/openidm/managed/role/b8783543-869a-4bd4-907e-9c1d89f826ae"
{
  "_id": "b8783543-869a-4bd4-907e-9c1d89f826ae",
  "_rev": "0000000027a959cf",
  "privileges": [],
  "name": "employee",
  "description": "All employees"
}

You cannot delete a role that is currently granted to users. If you attempt to delete a role that is granted to a user (either over the REST interface, or by using the admin UI), IDM returns an error. The following example attempts to remove a role that is still granted to a user:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request DELETE \
"http://localhost:8080/openidm/managed/role/01ee6191-75d8-4d4b-9291-13a46592c57a"
{
  "code": 409,
  "reason": "Conflict",
  "message": "Cannot delete a role that is currently granted"
}

To delete a role through the admin UI, select Manage > Role, select the role you want to remove, then click Delete Selected.

Use temporal constraints to restrict effective roles

Temporal constraints restrict the period that a role is effective. You can apply temporal constraints to managed and internal roles, and to role grants (for individual users).

For example, you might want a role, contractors-2020, to apply to all contract employees for the year 2020. In this case, you would set the temporal constraint on the role. Alternatively, you might want to assign a contractors role that applies to an individual user only for the period of their contract of employment.

The following examples show how to set temporal constraints on role definitions, and on individual role grants.

Add a temporal constraint to a role

When you create a role, you can include a temporal constraint in the role definition that restricts the validity of the role, regardless of how that role is granted. Temporal constraints are expressed as a time interval in ISO 8601 date and time format. For more information on this format, refer to the ISO 8601 standard.

The following example adds a contractor role over the REST interface. The role is effective from March 1st, 2020 to August 31st, 2020:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request POST \
--data '{
  "name": "contractor",
  "description": "Role granted to contract workers for 2020",
  "temporalConstraints": [
    {
      "duration": "2020-03-01T00:00:00.000Z/2020-08-31T00:00:00.000Z"
    }
  ]
}' \
"http://localhost:8080/openidm/managed/role?_action=create"
{
  "_id": "ed761370-b24f-4e21-8e58-a3230942da67",
  "_rev": "000000007429750e",
  "name": "contractor",
  "description": "Role granted to contract workers for 2020",
  "temporalConstraints": [
    {
      "duration": "2020-03-01T00:00:00.000Z/2020-08-31T00:00:00.000Z"
    }
  ]
}

This example specifies the time zone as Coordinated Universal Time (UTC) by appending Z to the time. If no time zone information is provided, the time zone is assumed to be local time. To specify a different time zone, include an offset (from UTC) in the format ±hh:mm. For example, an interval of 2020-03-01T00:00:00.000-07:00/2020-08-31T00:00:00.000-07:00 specifies a time zone that is seven hours behind UTC.

When the period defined by the constraint has ended, the role object remains in the repository, but the effective roles script will not include the role in the list of effective roles for any user.

The following example assumes that user scarter has been granted a role contractor-march. A temporal constraint has been included in the contractor-march role definition, specifying that the role should be applicable only during the month of March 2020. At the end of this period, a query on scarter’s entry shows that his roles property still includes the contractor-march role (with ID 0face495-772d-4d36-a30d-8594618aba0d), but his effectiveRoles property does not:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/scarter?_fields=_id,userName,roles,effectiveRoles"
{
  "_id": "scarter",
  "_rev": "00000000e5fdeb51",
  "userName": "scarter",
  "effectiveRoles": [],
  "roles": [
    {
      "_ref": "managed/role/0face495-772d-4d36-a30d-8594618aba0d",
      "_refResourceCollection": "managed/role",
      "_refResourceId": "0face495-772d-4d36-a30d-8594618aba0d",
      "_refProperties": {
        "_id": "5f41d5a5-19b4-4524-a4b1-445790ff14da",
        "_rev": "00000000cb339810"
      }
    }
  ]
}

The role is still in place but is no longer effective.

To restrict the period during which a role is valid by using the admin UI, select Temporal Constraint on the role Details tab, then select a timezone offset relative to GMT and the start and end dates for the required period.

Add a temporal constraint to a role grant

To restrict the validity of a role for individual users, apply a temporal constraint at the grant level, rather than as part of the role definition. In this case, the temporal constraint is taken into account per user, when the user’s effective roles are calculated. Temporal constraints that are defined at the grant level can be different for each user who is a member of that role.

To apply a temporal constraint to a grant over the REST interface, include the constraint as one of the _refProperties of the relationship between the user and the role. The following example assumes a contractor role, with ID ed761370-b24f-4e21-8e58-a3230942da67. The command adds user bjensen as a member of that role, with a temporal constraint that specifies that she be a member of the role for one year only, from January 1st, 2020 to January 1st, 2021:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request PATCH \
--data '[
  {
    "operation": "add",
    "field": "/members/-",
    "value": {
      "_ref": "managed/user/bjensen",
      "_refProperties": {
        "temporalConstraints": [{"duration": "2020-01-01T00:00:00.000Z/2021-01-01T00:00:00.000Z"}]
      }
    }
  }
]' \
"http://localhost:8080/openidm/managed/role/ed761370-b24f-4e21-8e58-a3230942da67"
{
  "_id": "ed761370-b24f-4e21-8e58-a3230942da67",
  "_rev": "000000007429750e",
  "name": "contractor",
  "description": "Role granted to contract workers for 2020",
  "temporalConstraints": [
    {
      "duration": "2020-03-01T00:00:00.000Z/2020-08-31T00:00:00.000Z"
    }
  ]
}

A query on bjensen’s roles property shows that the temporal constraint has been applied to this grant:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/bjensen/roles?_queryFilter=true"
{
  "result": [
    {
      "_id": "40600260-111d-4695-81f1-450365025784",
      "_rev": "00000000173daedb",
      "_ref": "managed/role/ed761370-b24f-4e21-8e58-a3230942da67",
      "_refResourceCollection": "managed/role",
      "_refResourceId": "ed761370-b24f-4e21-8e58-a3230942da67",
      "_refProperties": {
        "temporalConstraints": [
          {
            "duration": "2020-01-01T00:00:00.000Z/2021-01-01T00:00:00.000Z"
          }
        ],
        "_id": "40600260-111d-4695-81f1-450365025784",
        "_rev": "00000000173daedb"
      }
    }
  ],
  ...
}

To restrict the period during which a role grant is valid using the admin UI, set a temporal constraint when you add the member to the role.

For example, to specify that bjensen be added to a Contractor role only for the period of her employment contract, select Manage > Role, select the Contractor role, then select Add Role Members. On the Add Role Members screen, select bjensen from the list, then enable the Temporal Constraint, and specify the start and end date of her contract.

Use assignments to provision users

Authorization roles control access to IDM itself. Provisioning roles define rules for how attribute values are updated on external systems. These rules are configured through assignments attached to a provisioning role definition. The purpose of an assignment is to provision an attribute or set of attributes based on an object’s role membership.

The synchronization mapping configuration between two resources provides the logic governing how an account is mapped from a source to a target system. Role assignments provide additional provisioning logic not covered in the basic mapping configuration. The attributes and values updated by using assignments may include group membership, access to specific external resources, and so on. A group of assignments can collectively represent a role.

Assignment objects are created, updated, and deleted like any other managed object. They are attached to a role using the relationships mechanism. Assignments are accessible at the context path /openidm/managed/assignment.

This section describes how to manipulate assignments over the REST interface and the admin UI. When you have created an assignment and attached it to a role definition, all user objects that reference that role definition will reference the corresponding assignment in their effectiveAssignments attribute.

If you have mapped roles and assignments to properties on a target system, and you are preloading the result set into memory, make sure that your targetQuery returns the mapped property. For example, if you have mapped a specific role to the ldapGroups property on the target system, the target query must include the ldapGroups property when it returns the object.

The following mapping excerpt indicates the target query must return the _id of the object and its ldapGroups property:

"targetQuery": {
    "_queryFilter": true,
    "_fields": "_id,ldapGroups"
}

For more information about preloading the result set for reconciliation operations, refer to Improve Reconciliation Query Performance.

Create an Assignment

You can create assignments over the REST interface or using the admin UI:

Over REST

To create a new assignment over REST, send a PUT or POST request to the /openidm/managed/assignment context path.

The following example creates a new managed assignment named employee. The JSON payload in this example shows the following:

  • The assignment is applied for the mapping managedUser_systemLdapAccounts. Attributes are updated on the external LDAP system specified in this mapping.

  • The name of the attribute on the external system with a value to be set is employeeType. It will be set to Employee.

  • When the assignment is applied during a sync operation, the attribute value Employee is added to any existing values for that attribute. When the assignment is removed (such as if the user is no longer a member of that role), the attribute value Employee is also removed.

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --header "Content-Type: application/json" \
    --request POST \
    --data '{
      "name": "employee",
      "description": "Assignment for employees.",
      "mapping": "managedUser_systemLdapAccounts",
      "attributes": [
        {
          "name": "employeeType",
          "value": [
            "Employee"
          ],
          "assignmentOperation": "mergeWithTarget",
          "unassignmentOperation": "removeFromTarget"
        }
      ]
    }' \
    "http://localhost:8080/openidm/managed/assignment?_action=create"
    {
      "_id": "1a6a3af3-024f-4cf1-b4f6-116b98053816",
      "_rev": "00000000b2329649",
      "name": "employee",
      "description": "Assignment for employees.",
      "mapping": "managedUser_systemLdapAccounts",
      "attributes": [
        {
          "name": "employeeType",
          "value": [
            "Employee"
          ],
          "assignmentOperation": "mergeWithTarget",
          "unassignmentOperation": "removeFromTarget"
        }
      ]
    }

    Note that at this stage, the assignment is not linked to any role, so no user can make use of the assignment. You must add the assignment to a role, as described in Add an Assignment to a Role.

Using the admin UI
  1. Select Manage > Assignment > New Assignment.

  2. Enter a name and description for the new assignment.

  3. Select the mapping to which the assignment should apply. The mapping indicates the target resource, that is, the resource on which the attributes specified in the assignment will be adjusted.

  4. Select Save to add the assignment.

  5. Select the Attributes tab and select the attribute or attributes whose values will be adjusted by this assignment. The attribute you select here will determine what is displayed next:

    • Regular text field—specify what the value of the attribute should be, when this assignment is applied.

    • Item button—specify a managed object type, such as an object, relationship, or string.

    • Properties button—specify additional information, such as an array of role references.

  6. Select the assignment operation from the dropdown list:

    • Merge With Target: the attribute value will be added to any existing values for that attribute. This operation merges the existing value of the target object attribute with the value(s) from the assignment. If duplicate values are found (for attributes that take a list as a value), each value is included only once in the resulting target. This assignment operation is used only with complex attribute values like arrays and objects, and does not work with strings or numbers.

    • Replace Target: the attribute value will overwrite any existing values for that attribute. The value from the assignment becomes the authoritative source for the attribute.

  7. Select the unassignment operation from the drop-down list:

    • Remove From Target: the attribute value is removed from the system object when the user is no longer a member of the role, or when the assignment itself is removed from the role definition.

    • No Operation: removing the assignment from the user’s effectiveAssignments has no effect on the current state of the attribute in the system object.

  8. Select the Events tab to specify any scriptable events associated with this assignment.

    The assignment and unassignment operations described in the previous step operate at the attribute level. That is, you specify what should happen with each attribute affected by the assignment when the assignment is applied to a user, or removed from a user.

    The scriptable On assignment and On unassignment events operate at the assignment level, rather than the attribute level. Define scripts here to apply additional logic or operations that should be performed when a user (or other object) receives or loses an entire assignment. This logic can be anything that is not restricted to an operation on a single attribute.

    For information about the variables available to these scripts, refer to Variables available to role assignment scripts.

  9. Select the Roles tab to attach this assignment to an existing role definition.

Attribute encryption on assignments

Assignment attributes are encrypted if the corresponding connector attribute indicates confidentiality, based on the attribute’s nativeType (such as JAVA_TYPE_GUARDEDSTRING or JAVA_TYPE_GUARDED_BYTE_ARRAY).

The managed assignment object includes the following property:

"attributeEncryption" : { }

If attributeEncryption is not present on managed/assignment, the assignment attributes are not encrypted. If the property is present but empty, it defaults to IDM’s default encryption cipher. To specify a different cipher, add the cipher property. For example:

"attributeEncryption" : {
  "cipher" : "AES/CBC/PKCS5Padding"
}

This functionality uses the idm.assignment.attribute.encryption secret. For more information, refer to Secret stores.

Add an assignment to a role

After you have created a role and an assignment, you create a relationship between the assignment and the role in much the same way as a user references a role.

Update a role definition to include one or more assignments over the REST interface or by using the admin UI:

Over REST

Update the role definition to include a reference to the ID of the assignment in the assignments property of the role. The following example adds the employee assignment (ID 1a6a3af3-024f-4cf1-b4f6-116b98053816) to an existing employee role (ID 2243f5f8-ed75-4c3b-b4b3-058d5c58fbb4):

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request PATCH \
--data '[
  {
    "operation": "add",
    "field": "/assignments/-",
    "value": { "_ref": "managed/assignment/1a6a3af3-024f-4cf1-b4f6-116b98053816" }
  }
]' \
"http://localhost:8080/openidm/managed/role/2243f5f8-ed75-4c3b-b4b3-058d5c58fbb4"
{
  "_id": "2243f5f8-ed75-4c3b-b4b3-058d5c58fbb4",
  "_rev": "00000000e85263c7",
  "privileges": [],
  "name": "employee",
  "description": "Roll granted to all permanent employees"
}

To check that the assignment was added successfully, query the role’s assignments property:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/role/2243f5f8-ed75-4c3b-b4b3-058d5c58fbb4/assignments?_queryFilter=true&_fields=_ref/*,name,assignments"
{
  "result": [
    {
      "_id": "d15822f0-05bc-464a-927d-8e5018a234d3",
      "_rev": "0000000010eea343",
      "_refResourceCollection": "managed/assignment",
      "_refResourceId": "1a6a3af3-024f-4cf1-b4f6-116b98053816",
      "_refResourceRev": "00000000b2329649",
      "name": "employee",
      "_ref": "managed/assignment/1a6a3af3-024f-4cf1-b4f6-116b98053816",
      "_refProperties": {
        "_id": "d15822f0-05bc-464a-927d-8e5018a234d3",
        "_rev": "0000000010eea343"
      }
    }
  ],
  ...
}

Note that the assignments property references the assignment that you created in the previous step.

To remove an assignment from a role definition, remove the reference to the assignment from the role’s assignments property.

Using the admin UI
  1. Select Manage > Role, and select the role to which you want to add an assignment.

  2. Select the Managed Assignments tab, and select Add Managed Assignments.

  3. Select the assignment that you want to add to the role, then select Add.

Delete an assignment

Delete assignments over the REST interface, or by using the admin UI:

Over REST

To delete an assignment over the REST interface, simply delete that object. The following example deletes the employee assignment created in the previous example:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request DELETE \
"http://localhost:8080/openidm/managed/assignment/1a6a3af3-024f-4cf1-b4f6-116b98053816"
{
  "_id": "1a6a3af3-024f-4cf1-b4f6-116b98053816",
  "_rev": "00000000b2329649",
  "name": "employee",
  "description": "Assignment for employees.",
  "mapping": "managedUser_systemLdapAccounts",
  "attributes": [
    {
      "name": "employeeType",
      "value": [
        "Employee"
      ],
      "assignmentOperation": "mergeWithTarget",
      "unassignmentOperation": "removeFromTarget"
    }
  ]
}
You can delete an assignment, even if it is referenced by a managed role. When the assignment is removed, any users to whom the corresponding roles were granted will no longer have that assignment in their list of effectiveAssignments. For more information about effective roles and effective assignments, refer to Effective roles and effective assignments.
Using the admin UI

To delete an assignment by using the admin UI, select Manage > Assignment.

Select the assignment you want to remove, then select Delete.

Effective roles and effective assignments

Effective roles and effective assignments are virtual properties of a user object. Their values are calculated by IDM, using relationships between related objects to know when to recalculate when changes occur. The relationships between objects are configured using the notify, notifySelf, and notifyRelationships settings for managed/user, managed/role, and managed/assignment. Which related objects to traverse for calculation is configured using queryConfig. Calculation or recalculation is triggered when the roles or assignments for a managed user are added, removed, or changed, including by changes from temporal constraints, and notification of that change is sent to the related objects.

The following excerpt of the managed object configuration file shows how these two virtual properties are constructed for each managed user object:

"effectiveRoles" : {
    "type" : "array",
    "title" : "Effective Roles",
    "description" : "Effective Roles",
    "viewable" : false,
    "returnByDefault" : true,
    "isVirtual" : true,
    "queryConfig" : {
        "referencedRelationshipFields" : ["roles"]
    },
    "usageDescription" : "",
    "isPersonal" : false,
    "items" : {
        "type" : "object",
        "title" : "Effective Roles Items"
    }
},
"effectiveAssignments" : {
    "type" : "array",
    "title" : "Effective Assignments",
    "description" : "Effective Assignments",
    "viewable" : false,
    "returnByDefault" : true,
    "isVirtual" : true,
    "queryConfig" : {
        "referencedRelationshipFields" : ["roles", "assignments"],
        "referencedObjectFields" : ["*"]
    },
    "usageDescription" : "",
    "isPersonal" : false,
    "items" : {
        "type" : "object",
        "title" : "Effective Assignments Items"
    }
}

When a role references an assignment, and a user references the role, that user automatically references the assignment in its list of effective assignments.

effectiveRoles uses the roles relationship to calculate the grants that are currently in effect, including any qualified by temporal constraints.

effectiveAssignments uses the roles relationship, and the assignments relationship for each role, to calculate the current assignments in effect for that user. The synchronization engine reads the calculated value of the effectiveAssignments attribute when it processes the user. The target system is updated according to the configured assignmentOperation for each assignment.

When a user’s roles or assignments are updated, IDM calculates the effectiveRoles and effectiveAssignments for that user based on the current value of the user’s roles property, and the assignments property of any roles referenced by the roles property. The previous set of examples showed the creation of a role employee that referenced an assignment employee and was granted to user bjensen. Querying that user entry would show the following effective roles and effective assignments:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/bjensen?_fields=userName,roles,effectiveRoles,effectiveAssignments"
{
  "_id": "ca8855fd-a404-42c7-88b7-02f8a8a825b2",
  "_rev": "0000000081eebe1a",
  "userName": "bjensen",
  "effectiveRoles": [
    {
      "_refResourceCollection": "managed/role",
      "_refResourceId": "2243f5f8-ed75-4c3b-b4b3-058d5c58fbb4"
      "_ref": "managed/role/2243f5f8-ed75-4c3b-b4b3-058d5c58fbb4"
    }
  ],
  "effectiveAssignments": [
    {
      "name": "employee",
      "description": "Assignment for employees.",
      "mapping": "managedUser_systemLdapAccounts",
      "attributes": [
        {
          "assignmentOperation": "mergeWithTarget",
          "name": "employeeType",
          "unassignmentOperation": "removeFromTarget",
          "value": [
            "employee"
          ]
        }
      ],
      "_rev": "0000000087d5a9a5",
      "_id": "46befacf-a7ad-4633-864d-d93abfa561e9"
      "_refResourceCollection": "managed/assignment",
      "_refResourceId": "46befacf-a7ad-4633-864d-d93abfa561e9",
      "_ref": "managed/assignment/46befacf-a7ad-4633-864d-d93abfa561e9"
    }
  ],
  "roles": [
    {
      "_ref": "managed/role/2243f5f8-ed75-4c3b-b4b3-058d5c58fbb4",
      "_refResourceCollection": "managed/role",
      "_refResourceId": "2243f5f8-ed75-4c3b-b4b3-058d5c58fbb4",
      "_refProperties": {
        "_id": "93552530-10fa-49a4-865f-c942dffd2801",
        "_rev": "0000000081ed9f2b"
      }
    }
  ]
}

In this example, synchronizing the managed/user repository with the external LDAP system defined in the mapping populates user bjensen’s employeeType attribute in LDAP with the value employee.

Roles and relationship change notification

Before you read this section, refer to Configure relationship change notification to understand the notify and notifyRelationships properties, and how change notification works for relationships in general. In the case of roles, the change notification configuration exists to ensure that managed users are notified when any of the relationships that link users, roles, and assignments are manipulated (that is, created, updated, or deleted).

Consider the situation where a user has role R. A new assignment A is created that references role R. Ultimately, we want to notify all users that have role R so that their reconciliation state will reflect any attributes in the new assignment A. We achieve this notification with the following configuration:

In the managed object schema, the assignment object definition has a roles property that includes a resourceCollection. The path of this resource collection is managed/role and "notify" : true for the resource collection:

{
    "name" : "assignment",
    "schema" : {
        ...
        "properties" : {
            ...
            "roles" : {
                ...
                "items" : {
                    ...
                    "resourceCollection" : [
                        {
                            "notify" : true,
                            "path" : "managed/role",
                            "label" : "Role",
                            "query" : {
                                "queryFilter" : "true",
                                "fields" : [
                                    "name"
                                ]
                            }
                        }
                    ...
}

With this configuration, when assignment A is created, with a reference to role R, role R is notified of the change. However, we still need to propagate that notification to any users who are members of role R. To do this, we configure the role object as follows:

{
    "name" : "role",
    "schema" : {
        ...
        "properties" : {
            ...
            "assignments" : {
                ...
                "notifyRelationships" : ["members"]
            }
    ...
}

When role R is notified of the creation of a new relationship to assignment A, the notification is propagated through the assignments property. Because "notifyRelationships" : ["members"] is set on the assignments property, the notification is propagated across role R to all members of role R.

Managed role script hooks

Like any other managed object, you can use script hooks to configure role behavior.

Map roles to external groups

A user’s access to IDM is based on one or more authorization roles. Authorization roles are cumulative, and are calculated for a user in the following order:

  1. Roles set specifically in the user’s userRoles property

  2. Group roles — based on group membership in an external system

    Group roles are controlled with the following properties in the authentication configuration:

    • groupMembership: the property on the external system that represents group membership. In a DS directory server, that property is ldapGroups by default. In an Active Directory server, the property is memberOf by default. For example:

      "groupMembership" : "ldapGroups"

      Note that the value of the groupMembership property must be the ICF property name defined in the provisioner file, rather than the property name on the external system.

    • groupRoleMapping: a mapping between an IDM role and a group on the external system. Setting this property ensures that if a user authenticates through pass-through authentication, they are given specific IDM roles depending on their membership in groups on the external system. In the following example, users who are members of the group cn=admins,ou=Groups,dc=example,dc=com are given the internal openidm-admin role when they authenticate:

      "groupRoleMapping" : {
          "internal/role/openidm-admin" : ["cn=admins,ou=Groups,dc=example,dc=com"]
      }
    • groupComparisonMethod: the method used to check whether the authenticated user’s group membership matches one of the groups mapped to an IDM role (in the groupRoleMapping property).

      The groupComparisonMethod can be one of the following:

      • equals: a case-sensitive equality check

      • caseInsensitive: a case-insensitive equality check

      • ldap: a case-insensitive and whitespace-insensitive equality check. Because LDAP directories do not take case or whitespace into account in group DNs, you must set the groupComparisonMethod if you are using pass-through authentication with an LDAP directory.

To control access to external systems, use provisioning roles and assignments, as described in Use assignments to provision users.

Organizations

Organization objects let you arrange and manage users in hierarchical trees. Organizations also let you give users fine-grained administrative privileges to various parts of the tree based on their location in that tree. For example, an administrator of one organization might have full access to the users within that organization but no access to the users in an adjacent organization.

An organization object (defined in the managed object configuration) contains a set of relationship properties that reference the parent, child, owners, admins, and members of an organization. These relationship properties enable a hierarchical organizational model.

Users and organizations have a set of relationship-derived virtual properties used by the delegated administration filters to provide the visibility and access constraints that underpin the organization model. Users have the ids of all the organizations of which they are members, and organizations have the ids of all their admin and owner users.

Only IDM administrative users can create top-level organizations. Within organizations, there are various levels of privileges, depending on how a user is related to the organization.

The following diagram gives a high-level overview of how privileges are assigned to various entities in the organization hierarchy:

org-model1
  • An organization owner can manipulate all organizations, members, and admins in their ownership area. The ownership area includes any part of the tree in or beneath the organization that they own. So, in the preceding image, the owner of Org A can do the following anywhere within their ownership area (the pale green region):

    • Add and update members.

      Organization owners only have access to the members in their ownership area. So, an owner can create a new user as a member of their organization, but cannot add an existing managed user to their organization if that user is outside of their ownership area (that is, in any part of the tree not in or beneath the organization that they own).
    • Add and update sub-organizations, such as Org B and Org C.

    • Give an organization member the admin privilege for the parent organization or any sub-organizations.

      An organization owner cannot create additional owners in their root organization.

      An organization owner does not have to be a member of the organization. If the organization owner is a member of the organization, that owner is automatically in the administrative area of any admins of that organization, and can therefore be manipulated by an organization admin. To avoid accidentally giving organization admins privileges over an organization owner, do not make the owner a member of the organization.
  • An organization admin has control over their administrative area. The administrative area includes any part of the tree in or beneath the organization that they administer. In the preceding diagram, the administrative area of Admin A is shaded red. The administrative areas of Admins B and C are shaded grey. An admin can do the following in their administrative area:

    • Add and update members.

      Organization admins only have access to the members in their administrative area. So, an admin can create a new user as a member of their organization, but cannot add an existing managed user to their organization if that user is outside of their administrative area (that is, in any part of the tree not in or beneath the organization that they administer).
    • Add and update sub-organizations of the organization they administer.

      Notice that Admin B and C are outside of the administrative area of Admin A. An organization admin cannot create additional admins in their administrative areas.

      An organization admin must be a member of the organization, so must either be an existing member of the organization, or must be given the memberOfOrg relationship at the time they are created.

  • Organization members are regular users, with no special privileges in the organization hierarchy.

    Managed users have a memberOfOrgIDs virtual property that lists the organizations to which the user belongs (either directly, or through any parent organizations).

  • Parent and child organizations are essentially relationships between an organization and existing organizations in the tree.

Organizations rely on the privilege mechanism. To use organizations effectively, you must therefore enable dynamic role calculation, as required by the privilege model.

This is not required if you are authenticating through AM, using the rsFilter authentication module.

Select Configure > Authentication > Session > Enable Dynamic Roles in the admin UI or set the enableDynamicRoles property to true in the sessionModule in your conf/authentication.json file:

"sessionModule" : {
    "name" : "JWT_SESSION",
    "properties" : {
        "maxTokenLifeMinutes" : 120,
        "tokenIdleTimeMinutes" : 30,
        "sessionOnly" : true,
        "isHttpOnly" : true,
        "enableDynamicRoles" : true
    }
}

For more information about dynamic role calculation, refer to Dynamic Role Calculation.

Manage organizations over REST

IDM provides RESTful access to managed organizations, at the context path /openidm/managed/organization. You can add, change, and delete organizations by using the admin UI or over the REST interface. To use the admin UI, select Manage > Organization.

The following examples show how to add, change, and delete organizations over the REST interface. For a reference of all managed organization endpoints and actions, refer to Managed organizations.

You can also use the REST API Explorer as a reference to the managed object REST API.

Add an organization

Only IDM administrators can create top level organizations.

curl \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "If-None-Match: *" \
--request PUT \
--data '{
  "name": "example-org"
}' \
"http://localhost:8080/openidm/managed/organization/example-org"
{
  "_id": "example-org",
  "_rev": "00000000bc9871c8",
  "adminIDs": [],
  "ownerIDs": [],
  "parentAdminIDs": [],
  "parentIDs": [],
  "parentOwnerIDs": [],
  "name": "example-org"
}
Add an organization owner

IDM administrators can create owners for an organization. This example makes bjensen the owner of the organization created previously. The example assumes that the managed user bjensen already exists:

curl \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request POST \
--data '{"_ref":"managed/user/bjensen"}' \
"http://localhost:8080/openidm/managed/organization/example-org/owners?_action=create"
{
  "_id": "fcb0f4d0-dad2-4138-a80c-62407a8e831e",
  "_rev": "00000000496d9920",
  "_ref": "managed/user/bjensen",
  "_refResourceCollection": "managed/user",
  "_refResourceId": "bjensen",
  "_refProperties": {
    "_id": "fcb0f4d0-dad2-4138-a80c-62407a8e831e",
    "_rev": "00000000496d9920"
  }
}
List an owner’s organizations

This example lists the organizations of which bjensen is an owner:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/bjensen/ownerOfOrg?_queryFilter=true"
{
  "result": [
    {
      "_id": "fcb0f4d0-dad2-4138-a80c-62407a8e831e",
      "_rev": "00000000496d9920",
      "_ref": "managed/organization/example-org",
      "_refResourceCollection": "managed/organization",
      "_refResourceId": "example-org",
      "_refProperties": {
        "_id": "fcb0f4d0-dad2-4138-a80c-62407a8e831e",
        "_rev": "00000000496d9920"
      }
    }
  ],
  ...
}
Add an organization member

Organization owners can create members in the organizations that they own. In this example bjensen creates user scarter and makes him a member of the organization created previously:

curl \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: bjensen" \
--header "X-OpenIDM-Password: Th3Password" \
--header "Accept-API-Version: resource=1.0" \
--request PUT \
--data '{
  "userName": "scarter",
  "sn": "Carter",
  "givenName": "Steven",
  "mail": "scarter@example.com",
  "password": "Th3Password",
  "memberOfOrg": [{"_ref": "managed/organization/example-org"}]
}' \
"http://localhost:8080/openidm/managed/user/scarter"
{
  "_id": "scarter",
  "_rev": "00000000eac81c23"
}
List the members of an organization

Organization owners can view the members of the organizations that they own. In this example, bjensen lists the members of example-org:

curl \
--header "X-OpenIDM-Username: bjensen" \
--header "X-OpenIDM-Password: Th3Password" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/organization/example-org/members?_queryFilter=true"
{
  "result": [
    {
      "_id": "b71e8dd9-6224-466f-9630-4358a69c69fd",
      "_rev": "0000000038ea999e",
      "_ref": "managed/user/scarter",
      "_refResourceCollection": "managed/user",
      "_refResourceId": "scarter",
      "_refProperties": {
        "_id": "b71e8dd9-6224-466f-9630-4358a69c69fd",
        "_rev": "0000000038ea999e"
      }
    }
  ],
  ...
}
Add an organization admin

Organization owners can create admins of the organizations that they own. An organization admin must be a member of the organization. In this example, bjensen makes scarter an admin of example-org:

curl \
--header 'Content-Type: application/json' \
--header "Accept-API-Version: resource=1.0" \
--header 'X-OpenIDM-Username: bjensen' \
--header 'X-OpenIDM-Password: Th3Password' \
--request PATCH \
--data '[
    {
        "operation": "add",
        "field": "/admins/-",
        "value": {
            "_ref": "managed/user/scarter"
        }
    }
]' \
"http://localhost:8080/openidm/managed/organization/example-org"
{
  "_id": "example-org",
  "_rev": "000000009c248a4a",
  "adminIDs": [
    "scarter"
  ],
  "ownerIDs": [
    "bjensen"
  ],
  "parentAdminIDs": [],
  "parentIDs": [],
  "parentOwnerIDs": [],
  "name": "example-org"
}
List an admin’s organizations

An organization owner or admin can only access the organizations that they own or administer. In this example, the admin scarter lists the organizations, and accesses only those of which they are an admin:

curl \
--header "X-OpenIDM-Username: scarter" \
--header "X-OpenIDM-Password: Th3Password" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/organization?_queryFilter=true"
{
  "result": [
    {
      "_id": "example-org",
      "_rev": "000000009c248a4a",
      "adminIDs": [
        "scarter"
      ],
      "ownerIDs": [
        "bjensen"
      ],
      "parentAdminIDs": [],
      "parentIDs": [],
      "parentOwnerIDs": [],
      "name": "example-org"
    }
  ],
  ...
}
Add a member as an organization admin

Organization admins can also add members to the organizations they administer. In this example, the organization admin, scarter, creates a new member, jsanchez, and makes her a member of example-org:

curl \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: scarter" \
--header "X-OpenIDM-Password: Th3Password" \
--header "Accept-API-Version: resource=1.0" \
--request PUT \
--data '{
  "userName": "jsanchez",
  "sn": "Sanchez",
  "givenName": "Juanita",
  "mail": "jsanchez@example.com",
  "password": "Th3Password",
  "memberOfOrg": [{"_ref": "managed/organization/example-org"}]
}' \
"http://localhost:8080/openidm/managed/user/jsanchez"
{
  "_id": "jsanchez",
  "_rev": "00000000f9341bd6"
}
List a member’s organizations

Organization owners and admins can list the organizations of which a user is a member, as long as those organizations are owned or administrated by them. In this example, scarter lists the organizations of which jsanchez is a member:

curl \
--header "X-OpenIDM-Username: scarter" \
--header "X-OpenIDM-Password: Th3Password" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/jsanchez?_fields=memberOfOrg"
{
  "_id": "jsanchez",
  "_rev": "00000000f9341bd6",
  "memberOfOrg": [
    {
      "_ref": "managed/organization/example-org",
      "_refResourceCollection": "managed/organization",
      "_refResourceId": "example-org",
      "_refProperties": {
        "_id": "078d14b2-e5f1-4b21-9801-041138e691f4",
        "_rev": "00000000ac2e9927"
      }
    }
  ]
}

The organization established by the previous set of examples can be represented as follows:

example-org

In this organization, both bjensen and scarter can create and delete sub-organizations, also known as child organizations, of example-org, and can create and delete members within these child organizations.

The following example shows how to add and delete child organizations over the REST interface:

Add a child organization

Organization owners and admins can create and manage child organizations of the organizations that they own or administer. In this example, the organization owner, bjensen, creates a new organization named example-child-org, and makes it a child organization of example-org:

curl \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: bjensen" \
--header "X-OpenIDM-Password: Th3Password" \
--header "Accept-API-Version: resource=1.0" \
--header "If-None-Match: *" \
--request PUT \
--data '{
  "name": "example-child-org",
  "parent": {"_ref": "managed/organization/example-org"}
}' \
"http://localhost:8080/openidm/managed/organization/example-child-org"
{
  "_id": "example-child-org",
  "_rev": "00000000db852a9d"
}

The organization model is based on delegated administration. As with delegated administration, you cannot explicitly change the relationship endpoints. So, for example, so you cannot create, update, delete, or patch relationship edges. The following type of request is therefore not possible with the organization model:

curl \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: bjensen" \
--header "X-OpenIDM-Password: Th3Password" \
--header "Accept-API-Version: resource=1.0" \
--header "If-None-Match: *" \
--request PUT \
--data '{
"name": "example-child-org",
"parent": {"_ref": "managed/organization/example-org"}
}' \
"http://localhost:8080/openidm/managed/organization/children?_action=create"
List an owner’s organizations and child organzations

Organization owners and admins have access to any organizations that are child organizations of their own orgs. In this example, admin scarter lists his visible organizations again:

curl \
--header "X-OpenIDM-Username: scarter" \
--header "X-OpenIDM-Password: Th3Password" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/organization?_queryFilter=true"
{
  "result": [
    {
      "_id": "example-org",
      "_rev": "000000009c248a4a",
      "adminIDs": [
        "scarter"
      ],
      "ownerIDs": [
        "bjensen"
      ],
      "parentAdminIDs": [],
      "parentIDs": [],
      "parentOwnerIDs": [],
      "name": "example-org"
    },
    {
      "_id": "example-child-org",
      "_rev": "00000000db852a9d",
      "adminIDs": [],
      "ownerIDs": [],
      "parentAdminIDs": [
        "scarter"
      ],
      "parentIDs": [
        "example-org"
      ],
      "parentOwnerIDs": [
        "bjensen"
      ],
      "name": "example-child-org"
    }
  ],
  ...
}

Notice that scarter can now access the example-child-org that bjensen created in the previous example.

Organizations in high latency environments

The relationship-derived virtual properties that support the organization model are generally calculated in response to relationship signals that travel down the organization tree hierarchy. Imagine, for example, that a new root organization is added to an existing organization hierarchy (or that a new admin or owner is added to the root of an existing organization hierarchy). The relationship signals that trigger relationship-derived virtual property calculation are propagated down the organization hierarchy, and to all members of the organizations in this hierarchy. This, in turn, updates their relationship-derived virtual property state.

If there are many thousands of members of the organizations in the hierarchy, this operation can take a long time to complete. It is therefore best practice to grow an organization hierarchy downwards, adding new organizations as leaves to an existing hierarchy, and adding new admins and members to the leaves in the hierarchy tree. This is preferable to growing the hierarchy upwards, starting with the leaves, and growing the hierarchy up towards the root.

If you must add a new root to an existing organization hierarchy with many organizations and many members, or a new admin or owner to an organization near the top of the hierarchy, rather perform this request over the command-line, using the examples provided in the previous section.

Use policies to validate data

IDM provides an extensible policy service that lets you apply specific validation requirements to various components and properties. This chapter describes the policy service, and provides instructions on configuring policies for managed objects.

The policy service provides a REST interface for reading policy requirements and validating the properties of components against configured policies. Objects and properties are validated automatically when they are created, updated, or patched. Policies are generally applied to user passwords, but can also be applied to any managed or system object, and to internal user objects.

The policy service lets you accomplish the following tasks:

  • Read the configured policy requirements of a specific component.

  • Read the configured policy requirements of all components.

  • Validate a component object against the configured policies.

  • Validate the properties of a component against the configured policies.

The router service limits policy application to managed and internal user objects. To apply policies to additional objects, such as the audit service, modify your project’s router configuration. For more information about the router service, refer to Script triggers defined in the router configuration.

A configurable default policy applies to all managed objects.

You can extend the policy service by supplying your own scripted policies.

For DS repositories, objects are subject to a combination of IDM password policies and any configured DS password policies, when they are created or updated.

Default policy for managed objects

Policies applied to managed objects are configured in two places:

  • A policy script that defines each policy and specifies how policy validation is performed.

    For more information, refer to Policy Script.

  • A managed object policy element, defined in your managed object configuration, that specifies which policies are applicable to each managed resource. For more information, refer to Policy Configuration Element.

    The policy configuration determines which policies apply to resources other than managed objects. The default policy configuration includes policies that are applied to internal user objects, but you can extend the configuration to apply policies to system objects.

Policy script

The policy script file (openidm/bin/defaults/script/policy.js) separates policy configuration into two parts:

  • A policy configuration object, which defines each element of the policy. For more information, refer to Policy Configuration Objects.

  • A policy implementation function, which describes the requirements that are enforced by that policy.

Together, the configuration object and the implementation function determine whether an object is valid in terms of the applied policy. The following excerpt of a policy script file configures a policy that specifies that the value of a property must contain a certain number of capital letters:

...
{   "policyId": "at-least-X-capitals",
    "policyExec": "atLeastXCapitalLetters",
    "clientValidation": true,
    "validateOnlyIfPresent": true,
    "policyRequirements": ["AT_LEAST_X_CAPITAL_LETTERS"]
},
...

policyFunctions.atLeastXCapitalLetters = function(fullObject, value, params, property) {
    var isRequired = _.find(this.failedPolicyRequirements, function (fpr) {
            return fpr.policyRequirement === "REQUIRED";
        }),
        isString = (typeof(value) === "string"),
        valuePassesRegexp = (function (v) {
            var test = isString ? v.match(/[A-Z]/g) : null;
            return test !== null && test.length >= params.numCaps;
        }(value));

    if ((isRequired || isString) && !valuePassesRegexp) {
        return [ { "policyRequirement" : "AT_LEAST_X_CAPITAL_LETTERS", "params" : {"numCaps": params.numCaps} } ];
    }

    return [];
}
...

To enforce user passwords that contain at least one capital letter, the policyId from the preceding example is applied to the appropriate resource (managed/user/*). The required number of capital letters is defined in the policy configuration element of the managed object configuration file (see Policy Configuration Element).

Policy configuration objects

Each element of the policy is defined in a policy configuration object. The structure of a policy configuration object is as follows:

{
    "policyId": "minimum-length",
    "policyExec": "minLength",
    "clientValidation": true,
    "validateOnlyIfPresent": true,
    "policyRequirements": ["MIN_LENGTH"]
}

policyId

A unique ID that enables the policy to be referenced by component objects.

policyExec

The name of the function that contains the policy implementation. For more information, refer to Policy Implementation Functions.

clientValidation

Indicates whether the policy decision can be made on the client. When "clientValidation": true, the source code for the policy decision function is returned when the client requests the requirements for a property.

validateOnlyIfPresent

Notes that the policy is to be validated only if the field within the object being validated exists.

policyRequirements

An array containing the policy requirement ID of each requirement that is associated with the policy. Typically, a policy will validate only one requirement, but it can validate more than one.

Policy implementation functions

Each policy ID has a corresponding policy implementation function that performs the validation. Implementation functions take the following form:

function <name>(fullObject, value, params, propName) {
	<implementation_logic>
}
  • fullObject is the full resource object that is supplied with the request.

  • value is the value of the property that is being validated.

  • params refers to the params array that is specified in the property’s policy configuration.

  • propName is the name of the property that is being validated.

The following example shows the implementation function for the required policy:

function required(fullObject, value, params, propName) {
    if (value === undefined) {
        return [ { "policyRequirement" : "REQUIRED" } ];
    }
    return [];
}

Default policy reference

IDM includes the following default policies and parameters:

Policy Id Parameters

required

The property is required; not optional.

not-empty

The property can’t be empty.

not-null

The property can’t be null.

unique

The property must be unique.

valid-username

Tests for uniqueness and internal user conflicts.

no-internal-user-conflict

Tests for internal user conflicts.

regexpMatches

Matches a regular expression.

regexp

flags

The regular expression pattern.

valid-type

Tests for the specified types.

types

valid-query-filter

Tests for a valid query filter.

valid-array-items

Tests for valid array items.

valid-date

Tests for a valid date.

valid-formatted-date

Tests for a valid date format.

valid-time

Tests for a valid time.

valid-datetime

Tests for a valid date and time.

valid-duration

Tests for a valid duration format.

valid-email-address-format

Tests for a valid email address.

valid-name-format

Tests for a valid name format.

valid-phone-format

Tests for a valid phone number format.

at-least-X-capitals

The property must contain the minimum specified number of capital letters.

numCaps

Minimum number of capital letters.

at-least-X-numbers

The property must contain the minimum specified number of numbers.

numNums

Minimum number of numbers.

validNumber

Tests for a valid number.

minimumNumber

The property value must be greater than the minimum.

minimum

The minimum value.

maximumNumber

The property value must be less than the maximum.

maximum

The maximum value.

minimum-length

The property’s minimum string length.

minLength

The minimum string length.

maximum-length

The property’s maximum string length.

maxLength

The maximum string length.

cannot-contain-others

The property cannot contain values of the specified fields.

disallowedFields

A comma-separated list of the fields to check against. For example, the default managed user password policy specifies userName,givenName,sn as disallowed fields.

cannot-contain-characters

The property cannot contain the specified characters.

forbiddenChars

A comma-separated list of disallowed characters. For example, the default managed user userName policy specifies / as a disallowed character.

cannot-contain-duplicates

The property cannot contain duplicate characters.

mapping-exists

A sync mapping must exist for the property.

valid-permissions

Tests for valid permissions.

valid-accessFlags-object

Tests for valid access flags.

valid-privilege-path

Tests for a valid privilege path.

valid-temporal-constraints

Tests for valid temporal constraints.

Policy configuration element

Properties defined in the managed object configuration can include a policies element that specifies how policy validation should be applied to that property. The following excerpt of the default managed object configuration shows how policy validation is applied to the password and _id properties of a managed/user object.

You can only declare policies on top-level managed object attributes. Nested attributes (those within an array or object) cannot have policy declared on them.
{
    "name" : "user",
    "schema" : {
        "id" : "http://jsonschema.net",
        "properties" : {
            "_id" : {
                "description" : "User ID",
                "type" : "string",
                "viewable" : false,
                "searchable" : false,
                "userEditable" : false,
                "usageDescription" : "",
                "isPersonal" : false,
                "policies" : [
                    {
                        "policyId" : "cannot-contain-characters",
                        "params" : {
                            "forbiddenChars" : [
                                "/"
                            ]
                        }
                    }
                ]
            },
            "password" : {
                "title" : "Password",
                "description" : "Password",
                "type" : "string",
                "viewable" : false,
                "searchable" : false,
                "userEditable" : true,
                "encryption" : {
                    "purpose" : "idm.password.encryption"
                },
                "scope" : "private",
                "isProtected": true,
                "usageDescription" : "",
                "isPersonal" : false,
                "policies" : [
                    {
                        "policyId" : "minimum-length",
                        "params" : {
                            "minLength" : 8
                        }
                    },
                    {
                        "policyId" : "at-least-X-capitals",
                        "params" : {
                            "numCaps" : 1
                        }
                    },
                    {
                        "policyId" : "at-least-X-numbers",
                        "params" : {
                            "numNums" : 1
                        }
                    },
                    {
                        "policyId" : "cannot-contain-others",
                        "params" : {
                            "disallowedFields" : [
                                "userName",
                                "givenName",
                                "sn"
                            ]
                        }
                    }
                ]
            }
        }
    }
}

Note that the policy for the _id property references the function cannot-contain-characters, that is defined in the policy.js file. The policy for the password property references the functions minimum-length, at-least-X-capitals, at-least-X-numbers, and cannot-contain-others, that are defined in the policy.js file. The parameters that are passed to these functions (number of capitals required, and so forth) are specified in the same element.

Validate managed object data types

The type property of a managed object specifies the data type of that property, for example, array, boolean, number, null, object, or string. For more information about data types, refer to the JSON Schema Primitive Types section of the JSON Schema standard.

The type property is subject to policy validation when a managed object is created or updated. Validation fails if data does not match the specified type, such as when the data is an array instead of a string. The default valid-type policy enforces the match between property values and the type defined in the managed object configuration.

IDM supports multiple valid property types. For example, you might have a scenario where a managed user can have more than one telephone number, or a null telephone number (when the user entry is first created and the telephone number is not yet known). In such a case, you could specify the accepted property type as follows in your managed object configuration:

"telephoneNumber" : {
    "type" : "string",
    "title" : "Telephone Number",
    "description" : "Telephone Number",
    "viewable" : true,
    "userEditable" : true,
    "pattern" : "^\\+?([0-9\\- \\(\\)])*$",
    "usageDescription" : "",
    "isPersonal" : true,
    "policies" : [
        {
            "policyId" : "minimum-length",
            "params" : {
                "minLength" : 1
            }
        },
        {
            "policyId": "maximum-length",
            "params": {
                "maxLength": 255
            }
        }
    ]
}

In this case, the valid-type policy from the policy.js file checks the telephone number for an accepted type and pattern, either for a real telephone number or a null entry.

Configure policy validation using the admin UI

To configure policy validation for a managed object type using the admin UI, update the configuration of the object type—a high-level overview:

  1. Go to the managed object, and edit or create a property.

  2. Click the Validation tab, and add the policy.

Show Me
createPolicyUI
  1. From the navigation bar, click Configure > Managed Objects.

  2. On the Managed Objects page, edit or create a managed object.

  3. On the Managed Object NAME page, do one of the following:

    • To edit an existing property, click the property.

    • To create a property, click Add a Property, enter the required information, and click Save.

      • Now click the property.

  4. From the Validation tab, click Add Policy.

  5. In the Add/Edit Policy window, enter information in the following fields, and click Add or Save:

    Policy Id

    Refers to the unique PolicyId in the policy.js file.

    For a list of the default policies, refer to Policy Reference.

    Parameter Name

    Refers to the parameters for the PolicyId. For a list of the default policy parameters, refer to Policy Reference.

    Value

    The parameter’s value to validate.

Be cautious when using Validation Policies. If a policy relates to an array of relationships, such as between a user and multiple devices, Return by Default should always be set to false. You can verify this in your managed object configuration. Any managed object that has items of "type" : "relationship", must also have "returnByDefault" : false.

Extend the policy service

You can extend the policy service by adding custom scripted policies, and by adding policies that are applied only under certain conditions.

Add custom scripted policies

If your deployment requires additional validation functionality that is not supplied by the default policies, you can add your own policy scripts to your project’s script directory, and reference them in your project’s policy configuration.

Do not modify the default policy script file (openidm/bin/defaults/script/policy.js) as doing so might result in interoperability issues in a future release.

To reference additional policy scripts, set the additionalFiles property in you policy configuration.

The following example creates a custom policy that rejects properties with null values. The policy is defined in a script named mypolicy.js:

var policy = {   "policyId" : "notNull",
       "policyExec" : "notNull",
       "policyRequirements" : ["NOT_NULL"]
}

addPolicy(policy);

function notNull(fullObject, value, params, property) {
   if (value == null) {
      var requireNotNull = [
        {"policyRequirement": "NOT_NULL"}
      ];
      return requireNotNull;
   }
   return [];
}

The mypolicy.js policy is referenced in the policy.json configuration file as follows:

{
    "type" : "text/javascript",
    "file" : "policy.js",
    "additionalFiles" : ["script/mypolicy.js"],
    "resources" : [
        {
            ...
        }
    ]
}

In cases where you are using the admin UI, both policy.js and mypolicy.js will be run within the client, and then again by the the server. When creating new policies, be aware that these policies may be run in both contexts.

Add conditional policy definitions

You can extend the policy service to support policies that are applied only under specific conditions. To apply a conditional policy to managed objects, add the policy to your project’s managed object configuration. To apply a conditional policy to other objects, add it to your project’s policy configuration.

The following managed object configuration shows a sample conditional policy for the password property of managed user objects. The policy indicates that sys-admin users have a more lenient password policy than regular employees:

{
    "objects" : [
        {
            "name" : "user",
            ...
                "properties" : {
                ...
                    "password" : {
                        "title" : "Password",
                        "type" : "string",
                        ...
                        "conditionalPolicies" : [
                            {
                                "condition" : {
                                    "type" : "text/javascript",
                                    "source" : "(fullObject.org === 'sys-admin')"
                                },
                                "dependencies" : [ "org" ],
                                "policies" : [
                                    {
                                        "policyId" : "max-age",
                                        "params" : {
                                            "maxDays" : ["90"]
                                        }
                                    }
                                ]
                            },
                            {
                                "condition" : {
                                    "type" : "text/javascript",
                                    "source" : "(fullObject.org === 'employees')"
                                },
                                "dependencies" : [ "org" ],
                                "policies" : [
                                    {
                                        "policyId" : "max-age",
                                        "params" : {
                                            "maxDays" : ["30"]
                                        }
                                    }
                                ]
                            }
                        ],
                        "fallbackPolicies" : [
                            {
                                "policyId" : "max-age",
                                "params" : {
                                    "maxDays" : ["7"]
                                }
                            }
                        ]
                    }
                    ...
}

To understand how a conditional policy is defined, examine the components of this sample policy. For more information on the policy function, refer to Policy Implementation Functions.

There are two distinct scripted conditions (defined in the condition elements). The first condition asserts that the user object, contained in the fullObject argument, is a member of the sys-admin org. If that assertion is true, the max-age policy is applied to the password attribute of the user object, and the maximum number of days that a password may remain unchanged is set to 90.

The second condition asserts that the user object is a member of the employees org. If that assertion is true, the max-age policy is applied to the password attribute of the user object, and the maximum number of days that a password may remain unchanged is set to 30.

In the event that neither condition is met (the user object is not a member of the sys-admin org or the employees org), an optional fallback policy can be applied. In this example, the fallback policy also references the max-age policy and specifies that for such users, their password must be changed after 7 days.

The dependencies field prevents the condition scripts from being run at all, if the user object does not include an org attribute.

This example assumes that a custom max-age policy validation function has been defined, as described in Add Custom Scripted Policies.

These scripted conditions do not apply to progressive profiling.

Disable policy enforcement

Policy enforcement is the automatic validation of data when it is created, updated, or patched. In certain situations you might want to disable policy enforcement temporarily. You might, for example, want to import existing data that does not meet the validation requirements with the intention of cleaning up this data at a later stage.

You can disable policy enforcement by setting openidm.policy.enforcement.enabled to false in your resolver/boot.properties file. This setting disables policy enforcement in the back-end only, and has no impact on direct policy validation calls to the Policy Service (which the UI makes to validate input fields). So, with policy enforcement disabled, data added directly over REST is not subject to validation, but data added with the UI is still subject to validation.

You should not disable policy enforcement permanently, in a production environment.

Manage policies over REST

Manage the policy service over the REST interface at the openidm/policy endpoint.

List the defined policies

The following REST call displays a list of all the policies defined in policy.json (policies for objects other than managed objects). The policy objects are returned in JSON format, with one object for each defined policy ID:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/policy"
{
  "_id": "",
  "resources": [
    ...
    {
      "resource": "internal/user/*",
      "properties": [
        {
          "name": "_id",
          "policies": [
            {
              "policyId": "cannot-contain-characters",
              "params": {
                "forbiddenChars": [ "/" ]
              },
              "policyFunction": "\nfunction (fullObject, value, params, property) {\n    ...",
              "policyRequirements": [
                "CANNOT_CONTAIN_CHARACTERS"
              ]
            }
          ],
          "policyRequirements": [
            "CANNOT_CONTAIN_CHARACTERS"
          ]
        }
        ...
      ]
      ...
    }
  ]
}

To display the policies that apply to a specific resource, include the resource name in the URL. For example, the following REST call displays the policies that apply to managed users:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/policy/managed/user/*"
{
  "_id": "*",
  "resource": "managed/user/*",
  "properties": [
    {
      "policyRequirements": [
        "VALID_TYPE",
        "CANNOT_CONTAIN_CHARACTERS"
      ],
      "fallbackPolicies": null,
      "name": "_id",
      "policies": [
        {
          "policyRequirements": [
            "VALID_TYPE"
          ],
          "policyId": "valid-type",
          "params": {
            "types": [
              "string"
            ]
          }
        },
        {
          "policyId": "cannot-contain-characters",
          "params": {
            "forbiddenChars": [ "/" ]
          },
          "policyFunction": "...",
          "policyRequirements": [
            "CANNOT_CONTAIN_CHARACTERS"
          ]
        }
      ],
      "conditionalPolicies": null
    }
    ...
  ]
}

Validate objects and properties over REST

To verify that an object adheres to the requirements of all applied policies, include the validateObject action in the request.

The following example verifies that a new managed user object is acceptable, in terms of the policy requirements. Note that the ID in the URL (test in this example) is ignored—the action simply validates the object in the JSON payload:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request POST \
--data '{
  "sn": "Jones",
  "givenName": "Bob",
  "telephoneNumber": "0827878921",
  "passPhrase": null,
  "mail": "bjones@example.com",
  "accountStatus": "active",
  "userName": "bjones@example.com",
  "password": "123"
}' \
"http://localhost:8080/openidm/policy/managed/user/test?_action=validateObject"
{
  "result": false,
  "failedPolicyRequirements": [
    {
      "policyRequirements": [
        {
          "policyRequirement": "MIN_LENGTH",
          "params": {
            "minLength": 8
          }
        }
      ],
      "property": "password"
    },
    {
      "policyRequirements": [
        {
          "policyRequirement": "AT_LEAST_X_CAPITAL_LETTERS",
          "params": {
            "numCaps": 1
          }
        }
      ],
      "property": "password"
    }
  ]
}

The result (false) indicates that the object is not valid. The unfulfilled policy requirements are provided as part of the response - in this case, the user password does not meet the validation requirements.

Use the validateProperty action to verify that a specific property adheres to the requirements of a policy.

The following example checks whether a user’s new password (12345) is acceptable:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request POST \
--data '{
  "password": "12345"
}' \
"http://localhost:8080/openidm/policy/managed/user/9dce06d4-2fc1-4830-a92b-bd35c2f6bcbb?_action=validateProperty"
{
  "result": false,
  "failedPolicyRequirements": [
    {
      "policyRequirements": [
        {
          "policyRequirement": "MIN_LENGTH",
          "params": {
            "minLength": 8
          }
        }
      ],
      "property": "password"
    },
    {
      "policyRequirements": [
        {
          "policyRequirement": "AT_LEAST_X_CAPITAL_LETTERS",
          "params": {
            "numCaps": 1
          }
        }
      ],
      "property": "password"
    }
  ]
}

The result (false) indicates that the password is not valid. The unfulfilled policy requirements are provided as part of the response - in this case, the minimum length and the minimum number of capital letters.

Validating a property that fulfills the policy requirements returns a true result, for example:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request POST \
--data '{
  "password": "1NewPassword"
}' \
"http://localhost:8080/openidm/policy/managed/user/9dce06d4-2fc1-4830-a92b-bd35c2f6bcbb?_action=validateProperty"
{
  "result": true,
  "failedPolicyRequirements": []
}

Validate field removal

To validate field removal, specify the fields to remove when calling the policy validateProperty action. You cannot remove fields that:

  • Are required in the required schema array.

  • Have a required policy.

  • Have a default value.

The following example validates the removal of the fields description and givenName:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request POST \
--data '{
  "_remove": [ "description", "givenName" ]
}' \
"http://localhost:8080/openidm/policy/managed/user/ca5a3196-2ed3-4a76-8881-30403dee70e9?_action=validateProperty"

Because givenName is a required field, IDM returns a failed policy validation:

{
  "result": false,
  "failedPolicyRequirements": [
    {
      "policyRequirements": [
        {
          "policyRequirement": "REQUIRED"
        }
      ],
      "property": "givenName"
    }
  ]
}

Force validation of default values

IDM does not perform policy validation for default values specified in the managed objects schema. It may be necessary to force validation when validating properties for an object that does not yet exist. To force validation, include forceValidate=true in the request URL.

Validate properties to unknown resource paths

To perform a validateProperty action to a path that is unknown (*), such as managed/user/* or managed/user/userDoesntExistYet, the payload must include:

  • An object field that contains the object details.

  • A properties field that contains the properties to be evaluated.

Pre-registration validation example

A common use case for validating properties for unknown resources is prior to object creation, such as during pre-registration.

  1. Always pass the object and properties content in the POST body because IDM has no object to look up.

  2. Use any placeholder id in the request URL, as * has no special meaning in the API.

    This example uses a conditional policy for any object with the description test1:

    "password" : {
        ...
        "conditionalPolicies" : [
            {
                "condition" : {
                    "type" : "text/javascript",
                    "source" : "(fullObject.description === 'test1')"
                },
                "dependencies" : [ "description" ],
                "policies" : [
                    {
                        "policyId" : "at-least-X-capitals",
                        "params" : {
                            "numCaps" : 1
                        }
                    }
                ]
            }
        ],
  3. Using the above conditional policy, you could perform a validateProperty action to managed/user/* with the request:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --header "Content-Type: application/json" \
    --request POST \
    --data '{
      "object": {
        "description": "test1"
      },
      "properties": {
        "password": "passw0rd"
      }
    }' \
    "http://localhost:8080/openidm/policy/managed/user/*?_action=validateProperty"
    {
      "result": false,
      "failedPolicyRequirements": [
        {
          "policyRequirements": [
            {
              "params": {
                "numCaps": 1
              },
              "policyRequirement": "AT_LEAST_X_CAPITAL_LETTERS"
            }
          ],
          "property": "password"
        }
      ]
    }

Store managed objects in the repository

IDM stores managed objects, internal users, and configuration objects in a repository. By default, the server uses an embedded ForgeRock Directory Services (DS) instance as its repository.

In production, you must replace this embedded instance with an external DS instance, or with a JDBC repository, as described in Select a repository.

These topics describe the repository configuration, and how objects are mapped in the repository.

Repository configuration files

Configuration files for all supported repositories are located in the /path/to/openidm/db/database/conf directory. For JDBC repositories, the configuration is defined in two files:

  • datasource.jdbc-default.json specifies the connection to the database.

  • repo.jdbc.json specifies the mapping between IDM resources and database tables.

For a DS repository, the repo.ds.json file specifies the resource mapping and, in the case of an external repository, the connection details to the LDAP server.

For both DS and JDBC, the conf/repo.init.json file specifies IDM’s initial internal roles and users.

Copy the configuration files for your specific database type to your project’s conf/ directory.

JDBC connection configuration

The default database connection configuration file for a MySQL database follows:

{
    "driverClass" : "com.mysql.cj.jdbc.Driver",
    "jdbcUrl" : "jdbc:mysql://&{openidm.repo.host}:&{openidm.repo.port}/openidm?allowMultiQueries=true&characterEncoding=utf8&serverTimezone=UTC",
    "databaseName" : "openidm",
    "username" : "openidm",
    "password" : "openidm",
    "connectionTimeout" : 30000,
    "connectionPool" : {
        "type" : "hikari",
        "minimumIdle" : 20,
        "maximumPoolSize" : 50
    }
}

The configuration file includes the following properties:

driverClass

"driverClass" : string

To use the JDBC driver manager to acquire a data source, set this property, as well as jdbcUrl, username, and password. The driver class must be the fully-qualified class name of the database driver to use for your database.

Using the JDBC driver manager to acquire a data source is the most likely option, and the only one supported "out of the box". The remaining options in the sample repository configuration file assume that you are using a JDBC driver manager.

Example: "driverClass" : "com.mysql.cj.jdbc.Driver"

jdbcUrl

The connection URL to the JDBC database. The URL should include all parameters required by your database. For example, to specify the encoding in MySQL use 'characterEncoding=utf8'.

Specify the values for openidm.repo.host and openidm.repo.port in one of the following ways:

  • Set the values in resolver/boot.properties or your project’s conf/system.properties file, for example:

    openidm.repo.host = localhost
    openidm.repo.port = 3306
  • Set the properties in the OPENIDM_OPTS environment variable and export that variable before startup. You must include the JVM memory options when you set this variable. For example:

    export OPENIDM_OPTS="-Xmx1024m -Xms1024m -Dopenidm.repo.host=localhost -Dopenidm.repo.port=3306"
    /path/to/openidm/startup.sh
    Executing ./startup.sh...
    Using OPENIDM_HOME:   /path/to/openidm
    Using PROJECT_HOME:   /path/to/openidm
    Using OPENIDM_OPTS:   -Xmx1024m -Xms1024m -Dopenidm.repo.host=localhost -Dopenidm.repo.port=3306
    Using LOGGING_CONFIG: -Djava.util.logging.config.file=/path/to/openidm/conf/logging.properties
    Using boot properties at /path/to/openidm/resolver/boot.properties
    -> OpenIDM version "7.4.1"
    OpenIDM ready
databaseName

The name of the database, used in SQL queries. For example:

select * from databaseName.managedobjects

In addition to the SQL queries that are generated by IDM, any queries defined in the repo.jdbc.json file replace {_dbSchema} with the value of the databaseName property. For example, the following query in the repo.jdbc.json file replaces the {_dbSchema} with the value of the databaseName:

"delete-mapping-links" : "DELETE FROM ${_dbSchema}.${_table} WHERE linktype = ${mapping}",
username

The username with which to access the JDBC database.

password

The password with which to access the JDBC database. IDM automatically encrypts clear string passwords. To replace an existing encrypted value, replace the whole crypto-object value, including the brackets, with a string of the new password.

connectionTimeout

The period of time, in milliseconds, after which IDM should consider an attempted connection to the database to have failed. The default period is 30000 milliseconds (30 seconds).

connectionPool

Database connection pooling configuration. The default connection pool library is HikariCP:

"connectionPool" : {
    "type" : "hikari"
}

IDM uses the default HikariCP configuration, except for the following parameters. You might need to adjust these parameters, according to your database workload:

  • minimumIdle

    This property controls the minimum number of idle connections that HikariCP maintains in the connection pool. If the number of idle connections drops below this value, HikariCP attempts to add additional connections.

    By default, HikariCP runs as a fixed-sized connection pool, that is, this property is not set. The connection configuration files provided with IDM set the minimum number of idle connections to 20.

  • maximumPoolSize

    This property controls the maximum number of connections to the database, including idle connections and connections that are being used.

    By default, HikariCP sets the maximum number of connections to 10. The connection configuration files provided with IDM set the maximum number of connections to 50.

For information about the HikariCP configuration parameters, refer to the HikariCP Project Page.

JDBC database table configuration

An excerpt of a MySQL database table configuration file follows:

{
    "dbType" : "MYSQL",
    "useDataSource" : "default",
    "maxBatchSize" : 100,
    "maxTxRetry" : 5,
    "queries" : {...},
    "commands" : {...},
    "resourceMapping" : {...}
}

The configuration file includes the following properties:

dbType : string, optional

The type of database. The database type might affect the queries used and other optimizations. Supported database types include the following:
DB2
SQLSERVER (for Microsoft SQL Server)
MYSQL
ORACLE
POSTGRESQL

useDataSource : string, optional

This option refers to the connection details that are defined in the configuration file, described previously. The default configuration file is named datasource.jdbc-default.json. This is the file that is used by default (and the value of the "useDataSource" is therefore "default"). You might want to specify a different connection configuration file, instead of overwriting the details in the default file. In this case, set your connection configuration file datasource.jdbc-name.json and set the value of "useDataSource" to whatever name you have used.

maxBatchSize

The maximum number of SQL statements that will be batched together. This parameter lets you optimize the time taken to execute multiple queries. Certain databases do not support batching, or limit how many statements can be batched. A value of 1 disables batching.

maxTxRetry

The maximum number of times that a specific transaction should be attempted before that transaction is aborted.

queries

Any custom queries that can be referenced from the configuration.

Options supported for query parameters include the following:

  • A default string parameter, for example:

    openidm.query("managed/user", { "_queryId": "for-userName", "uid": "jdoe" });

    For more information about the query function, refer to openidm.query.

  • A list parameter (${list:propName}).

    Use this parameter to specify a set of indeterminate size as part of your query. For example:

    WHERE targetObjectId IN (${list:filteredIds})
  • A boolean parameter (${bool:propName}).

    Use this parameter to query boolean values in the database.

  • Numeric parameters for integers (${int:propName}), large integers (${long:propName}), and decimal values (${num:propName}).

    Use these parameters to query numeric values in the database, corresponding to the column data type in your repository.

commands

Specific commands configured to manage the database over the REST interface. Currently, the following default commands are included in the configuration:

  • purge-by-recon-expired

  • purge-by-recon-number-of

  • delete-mapping-links

  • delete-target-ids-for-recon

These commands assist with removing stale reconciliation audit information from the repository, and preventing the repository from growing too large. The commands work by executing a query filter, then performing the specified operation on each result set. Currently the only supported operation is DELETE, which removes all entries that match the filter.

resourceMapping

Defines the mapping between IDM resource URIs (for example, managed/user) and JDBC tables. The structure of the resource mapping is as follows:

"resourceMapping" : {
    "default" : {
        "mainTable" : "genericobjects",
        "propertiesTable" : "genericobjectproperties",
        "searchableDefault" : true
    },
    "genericMapping" : {...},
    "explicitMapping" : {...}
}

The default mapping object represents a default generic table in which any resource that does not have a more specific mapping is stored.

The generic and explicit mapping objects are described in the following section.

DS repository configuration

An excerpt of a DS repository configuration file follows:

{
    "embedded" : false,
    "maxConnectionAttempts" : 5,
    "security" : {...},
    "ldapConnectionFactories" : {...},
    "queries" : {...},
    "commands" : {...},
    "rest2LdapOptions" : {...},
    "indices" : {...},
    "schemaProviders" : {...},
    "resourceMapping" : {...}
}

The configuration file includes the following properties:

embedded : boolean

Specifies an embedded or external DS instance.

IDM uses an embedded DS instance by default. The embedded instance is not supported in production.

maxConnectionAttempts : integer

Specifies the number of times IDM should attempt to connect to the DS instance. On startup, IDM will attempt to connect to DS indefinitely. The maxConnectionAttempts parameter controls the number of reconnection attempts in the event of a failure during normal operation, for example, if an attempt to access the DS repository times out.

By default, IDM will attempt to reconnect to the DS instance 5 times.

security

Specifies the keystore and truststore for secure connections to DS.

"security": {
    "trustManager": "file",
    "fileBasedTrustManagerType": "JKS",
    "fileBasedTrustManagerFile": "&{idm.install.dir}/security/truststore",
    "fileBasedTrustManagerPasswordFile": "&{idm.install.dir}/security/storepass"
}

In the default case, where DS servers use TLS key pairs generated using a deploymentId and deploymentIdPassword, you must import the deploymentId-based CA certificate into the IDM truststore. For more information, refer to External DS repository.

Note that the security settings have no effect for an embedded DS repository. Embedded DS is not supported in production, and is meant for evaluation or testing purposes only.

ldapConnectionFactories

For an external DS repository, configures the connection to the DS instance. For example:

"ldapConnectionFactories": {
  "bind": {
    "connectionSecurity": "startTLS",
    "heartBeatIntervalSeconds": 60,
    "heartBeatTimeoutMilliSeconds": 10000,
    "primaryLdapServers": [
      {
        "hostname": "localhost",
        "port": 31389
      }
    ],
    "secondaryLdapServers": []
  },
  "root": {
    "inheritFrom": "bind",
    "authentication": {
      "simple": { "bindDn": "uid=admin", "bindPassword": "password" }
    }
  }
}

The connection to the DS repository uses the DS REST2LDAP gateway and the ldapConnectionFactories property sets the gateway configuration. For example, the secondaryLdapServers property specifies an array of LDAP servers that the gateway can contact if the primary LDAP servers cannot be contacted.

For information on all the gateway configuration properties, refer to Gateway Configuration in the DS REST API Guide.

queries

Predefined queries that can be referenced from the configuration. For a DS repository, all predefined queries are really filtered queries (using the _queryFilter parameter), for example:

"query-all-ids": {
    "_queryFilter": "true",
    "_fields": "_id,_rev"
}

The queries are divided between those for generic mappings and those for explicit mappings, but the queries themselves are the same for both mapping types.

commands

Specific commands configured to manage the repository over the REST interface. Currently, only two commands are included by default:

  • delete-mapping-links

  • delete-target-ids-for-recon

Both of these commands assist with removing stale reconciliation audit information from the repository, and preventing the repository from growing too large.

rest2LdapOptions

Specifies the configuration for accessing the LDAP data stored in DS. For more information, refer to Gateway REST2LDAP Configuration in the DS REST API Guide.

indices

For generic mappings, enables you to set up LDAP indices on custom object properties. For more information, refer to Improving Generic Mapping Search Performance (DS).

schemaProviders

For generic mappings, enables you to list custom objects whose properties should be indexed. For more information, refer to Improving Generic Mapping Search Performance (DS).

resourceMapping

Defines the mapping between IDM resource URIs (for example, managed/user) and the DS directory tree. The structure of the resource mapping object is as follows:

{
    "resourceMapping" : {
        "defaultMapping": {
            "dnTemplate": "ou=generic,dc=openidm,dc=forgerock,dc=com"
        },
        "explicitMapping" : {...},
        "genericMapping" : {...}
    }
}

The default mapping object represents a default generic organizational unit (ou) in which any resource that does not have a more specific mapping is stored.

The generic and explicit mapping objects are described in Object mappings.

Object mappings

You can map IDM objects to the tables in a JDBC database or to organizational units in DS using:

Generic Mapping

Lets you store arbitrary objects without special configuration or administration.

Explicit Mapping

Maps specific objects and properties to tables and columns in the JDBC database or to organizational units in DS.

Hybrid Mapping

Similar to Generic Mapping, except some objects and properties are mapped explicitly.

By default, IDM uses a generic mapping for user-definable objects, for both a JDBC and a DS repository. A generic mapping speeds up initial deployment, and can make system maintenance more flexible by providing a stable database structure. In a test environment, generic tables let you modify the user and object model easily, without database access, and without the need to constantly add and drop table columns. However, generic mapping does not take full advantage of the underlying database facilities, such as validation within the database and flexible indexing. Using an explicit mapping generally results in a substantial performance improvement. It is therefore strongly advised that you change to an explicit mapping before deploying in a production environment. If you are integrating IDM with AM, and using a shared DS repository, you must use an explicit schema mapping.

Mapping strategies are discussed in the following sections, with separate topics for JDBC DS repositories.

Mappings with a JDBC repository

Generic, explicit, and hybrid, oh my!

Reasons for choosing generic or hybrid over explicit mappings include:

  • Generic and hybrid mapped objects offer the flexibility to add and subtract non-searchable properties without having to modify the Database Data Definition Language (DDL) or IDM object configuration.

  • The properties table for generic objects can grow large quickly.

    Consider that a single object with 10 searchable properties would populate 10 rows within the generic properties table. Performance can be increased if commonly searched properties are mapped to a single column in the object table. In addition, the datatype of the property value can be enforced by the DDL of the column, or perhaps a required field could be marked as NOT NULL. However, once a property is mapped to an explicit column, future changes to the property mapping may require a DDL change and possibly, a migration effort.

PostgreSQL offers JSON capabilities that automatically makes all properties searchable. Although indexes will likely still need to be created for properties that need a performance boost.

Generic mappings (JDBC)

Generic mapping speeds up development, and can make system maintenance more flexible by providing a stable database structure. However, generic mapping can have a performance impact and does not take full advantage of the database facilities (such as validation within the database and flexible indexing). In addition, queries can be more difficult to set up.

In a generic table, the entire object content is stored in a single large-character field named fullobject in the mainTable for the object. To search on specific fields, you can read them by referring to them in the corresponding properties table for that object. The disadvantage of generic objects is that, because every property you might like to filter by is stored in a separate table, you must join to that table each time you need to filter by anything.

The following diagram shows a pared down database structure for the default generic tables, when using a MySQL repository. The diagram indicates the relationship between the main table and the corresponding properties table for each object.

generic-tables-erd
Figure 3. Generic Tables Entity Relationship Diagram

These separate tables can make the query syntax particularly complex. For example, a simple query to return user entries based on a user name would need to be implemented as follows:

SELECT obj.objectid, obj.rev, obj.fullobject FROM ${_dbSchema}.${_mainTable} obj
INNER JOIN ${_dbSchema}.${_propTable} prop ON obj.id = prop.${_mainTable}_id
INNER JOIN ${_dbSchema}.objecttypes objtype ON objtype.id = obj.objecttypes_id
WHERE prop.propkey='/userName' AND prop.propvalue = ${uid} AND objtype.objecttype = ${_resource}

The query can be broken down as follows:

  1. Select the full object, the object ID, and the object revision from the main table:

    SELECT obj.objectid, obj.rev, obj.fullobject FROM ${_dbSchema}.${_mainTable} obj
  2. Join to the properties table and locate the object with the corresponding ID:

    INNER JOIN ${_dbSchema}.${_propTable} prop  ON obj.id = prop.${_mainTable}_id
  3. Join to the object types table to restrict returned entries to objects of a specific type. For example, you might want to restrict returned entries to managed/user objects, or managed/role objects:

    INNER JOIN ${_dbSchema}.objecttypes objtype ON objtype.id = obj.objecttypes_id
  4. Filter records by the userName property, where the userName is equal to the specified uid and the object type is the specified type (in this case, managed/user objects):

    WHERE prop.propkey='/userName'
     AND prop.propvalue = ${uid}
     AND objtype.objecttype = ${_resource}

    The value of the uid field is provided as part of the query call, for example:

    openidm.query("managed/user", { "_queryId": "for-userName", "uid": "jdoe" });

Tables for user definable objects use a generic mapping by default.

The following sample generic mapping object illustrates how managed/ objects are stored in a generic table:

"genericMapping" : {
    "managed/*" : {
        "mainTable" : "managedobjects",
        "propertiesTable" : "managedobjectproperties",
        "searchableDefault" : true,
        "properties" : {
            "/picture" : {
                "searchable" : false
            }
        }
    }
}
mainTable (string, mandatory)

Indicates the main table in which data is stored for this resource.

The complete object is stored in the fullobject column of this table. The table includes an objecttypes foreign key that is used to distinguish the different objects stored within the table. In addition, the revision of each stored object is tracked, in the rev column of the table, enabling multiversion concurrency control (MVCC). For more information, refer to Manipulating Managed Objects Programmatically.

propertiesTable (string, mandatory)

Indicates the properties table, used for searches.

PostgreSQL repositories do not use these properties tables to access specific properties. Instead, the PostgreSQL json_extract_path_text() function achieves this functionality.

The contents of the properties table is a defined subset of the properties, copied from the character large object (CLOB) that is stored in the fullobject column of the main table. The properties are stored in a one-to-many style separate table. The set of properties stored here is determined by the properties that are defined as searchable.

The stored set of searchable properties makes these values available as discrete rows that can be accessed with SQL queries, specifically, with WHERE clauses. It is not otherwise possible to query specific properties of the full object.

The properties table includes the following columns:

  • ${_mainTable}_id corresponds to the id of the full object in the main table, for example, manageobjects_id, or genericobjects_id.

  • propkey is the name of the searchable property, stored in JSON pointer format (for example /mail).

  • proptype is the data type of the property, for example java.lang.String. The property type is obtained from the Class associated with the value.

  • propvalue is the value of property, extracted from the full object that is stored in the main table.

    Regardless of the property data type, this value is stored as a string, so queries against it should treat it as such.

searchableDefault (boolean, optional)

Specifies whether all properties of the resource should be searchable by default. Properties that are searchable are stored and indexed. You can override the default for individual properties in the properties element of the mapping. The preceding example indicates that all properties are searchable, with the exception of the picture property.

For large, complex objects, having all properties searchable implies a substantial performance impact. In such a case, a separate insert statement is made in the properties table for each element in the object, every time the object is updated. Also, because these are indexed fields, the recreation of these properties incurs a cost in the maintenance of the index. You should therefore enable searchable only for those properties that must be used as part of a WHERE clause in a query.

PostgreSQL repositories do not use the searchableDefault property.
properties

Lists any individual properties for which the searchable default should be overridden.

Note that if an object was originally created with a subset of searchable properties, changing this subset (by adding a new searchable property in the configuration, for example) will not cause the existing values to be updated in the properties table for that object. To add the new property to the properties table for that object, you must update or recreate the object.

Improve generic mapping search performance (JDBC)

All properties in a generic mapping are searchable by default. In other words, the value of the searchableDefault property is true unless you explicitly set it to false. Although there are no individual indexes in a generic mapping, you can improve search performance by setting only those properties that you need to search as searchable. Properties that are searchable are created within the corresponding properties table. The properties table exists only for searches or look-ups, and has a composite index, based on the resource, then the property name.

The sample JDBC repository configuration files (db/database/conf/repo.jdbc.json) restrict searches to specific properties by setting the searchableDefault to false for managed/user mappings. You must explicitly set searchable to true for each property that should be searched. The following sample extract from repo.jdbc.json indicates searches restricted to the userName property:

"genericMapping" : {
    "{managed_user}" : {
        "mainTable" : "manageduserobjects",
        "propertiesTable" : "manageduserobjectproperties",
        "searchableDefault" : false,
        "properties" : {
            "/userName" : {
                "searchable" : true
            }
        }
    }
}

With this configuration, IDM creates entries in the properties table only for userName properties of managed user objects.

If the global searchableDefault is set to false, properties that do not have a searchable attribute explicitly set to true are not written in the properties table.

Explicit mappings (JDBC)

Explicit mapping is more difficult to set up and maintain, but can take complete advantage of the native database facilities.

An explicit table offers better performance and simpler queries. There is less work in the reading and writing of data, because the data is all in a single row of a single table. In addition, it is easier to create different types of indexes that apply to only specific fields in an explicit table. The disadvantage of explicit tables is the additional work required in creating the table in the schema. Also, because rows in a table are inherently more simple, it is more difficult to deal with complex objects. Any non-simple key:value pair in an object associated with an explicit table is converted to a JSON string and stored in the cell in that format. This makes the value difficult to use, from the perspective of a query attempting to search within it.

You can have a generic mapping configuration for most managed objects, and an explicit mapping that overrides the default generic mapping in certain cases.

IDM provides a sample configuration, for each JDBC repository, that sets up an explicit mapping for the managed user object, and a generic mapping for all other managed objects. This configuration is defined in the files named /path/to/openidm/db/repository/conf/repo.jdbc-repository-explicit-managed-user.json. To use this configuration, copy the file that corresponds to your repository to your project’s conf/ directory, and rename it repo.jdbc.json. Run the sample-explicit-managed-user.sql data definition script (in the path/to/openidm/db/repository/scripts directory) to set up the corresponding tables when you configure your JDBC repository.

IDM uses explicit mapping for internal system tables, such as the tables used for auditing.

Depending on the types of usage your system is supporting, you might find that an explicit mapping performs better than a generic mapping. Operations such as sorting and searching (such as those performed in the default UI) tend to be faster with explicitly-mapped objects, for example.

The following sample explicit mapping object illustrates how internal/user objects are stored in an explicit table:

"explicitMapping" : {
    "internal/user" : {
        "table" : "internaluser",
        "objectToColumn" : {
            "_id" : "objectid",
            "_rev" : { "column" : "rev", "isNotNull" : true },
            "password" : "pwd"
        }
    },
    ...
}
resource-uri (string, mandatory)

Indicates the URI for the resources to which this mapping applies; for example, internal/user.

table (string, mandatory)

The name of the database table in which the object (in this case internal users) is stored.

objectToColumn (string, mandatory)

The way in which specific managed object properties are mapped to columns in the table.

The mapping can be a simple one to one mapping, for example "userName": "userName", or a more complex JSON map or list. When a column is mapped to a JSON map or list, the syntax is as shown in the following examples:

"messageDetail" : { "column" : "messagedetail", "type" : "JSON_MAP" }
"roles" : { "column" : "roles", "type" : "JSON_LIST" }

Available column data types you can specify are STRING (the default), NUMBER, JSON_MAP, JSON_LIST, and FULLOBJECT.

You can also prevent a column from accepting a NULL value, by setting the property isNotNull to true. This property is optional; if the property is omitted, it will default to false. Specifying which columns do not allow a null value can improve performance when sorting and paginating large queries. The syntax is similar to when specifying a column type:

"createDate" : { "column" : "createDate", "isNotNull" : true }

Pay particular attention to the following caveats when you map properties to explicit columns in your database:

  • Support for data types in columns is restricted to numeric values (NUMBER), strings (STRING), and boolean values (BOOLEAN). Although you can specify other data types, IDM handles all other data types as strings. Your database will need to convert these types from a string to the alternative data type. This conversion is not guaranteed to work.

    If the conversion does work, the format might not be the same when the data is read from the database as it was when it was saved. For example, your database might parse a date in the format 12/12/2012 and return the date in the format 2012-12-12 when the property is read.

  • Passwords are encrypted before they are stored in the repository. The length of the password column must be long enough to store the encrypted password value, which can vary depending on how it is encrypted and whether it is also hashed.

    The sample-explicit-managed-user.sql file referenced in this section sets the password column to a length of 511 characters (VARCHAR(511) to account for the additional space an encrypted password requires. For more information about IDM encryption and an example encrypted password value, refer to encrypt and Secure sensitive values.

  • If your data objects include virtual properties, you must include columns in which to store these properties. If you don’t explicitly map the virtual properties, you will encounter errors similar to the following when you attempt to create the corresponding object:

    {
        "code":400,
        "reason":"Bad Request",
        "message":"Unmapped fields [/property-name/0] for type managed/user and table openidm.managed_user"
    }

    To recalculate virtual property values in a query, you must set executeOnRetrieve to true in the query request parameters. For more information, refer to Property Storage Triggers.

Hybrid mappings (JDBC)

Hybrid mappings are similar to generic mappings, except some object fields are mapped directly to a column, and therefore not stored in the Entity–attribute–value (EAV) properties table. The fullobject column still holds all the object data and is used for object constitution. The combination of the explicit field columns and the EAV properties table is used for searching.

Object type conversion

You can use the migration service to convert objects from one type to another.

Convert an explicit mapped object to a hybrid mapped object (JDBC)

This procedure demonstrates how to migrate data to a different storage configuration within the same system using the migration service to convert the object data. After you finish the conversion, the converted objects are technically hybrid objects—generically mapped objects that have certain fields that are mapped to explicit columns.

Considerations before you start:

  • After you complete the process, object resource paths must stay the same to maintain relationship references.

  • You must migrate data to an empty table. Unlike generic tables, explicit mapped objects expect the table to contain records from a single object type.

  • Changes made to the source object during migration might not be transferred to the new object. To ensure everything is migrated correctly, run the migration during idle time, or when the system is least busy.

This procedure assumes that the repository configuration includes explicitly mapped object types, and that such objects already exist in the corresponding tables. For example:

"explicitMapping" : {
...
"managed/objectToConvert" : {
  "table" : "objecttoconvert",
  "objectToColumn" : {
    "_id" : "objectid",
    "_rev" : "rev",
    "desc" : "descr"
  }
}
  1. Create the new generic table and associated properties table. Adjust the following example to match your repository requirements, as needed:

    CREATE TABLE `openidm`.`objecttoconvert_gen` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT ,
    `objecttypes_id` BIGINT UNSIGNED NOT NULL ,
    `objectid` VARCHAR(255) NOT NULL ,
    `rev` VARCHAR(38) NOT NULL ,
    `descr` VARCHAR(255) NOT NULL ,
    `fullobject` MEDIUMTEXT NULL ,
    PRIMARY KEY (`id`) ,
    UNIQUE INDEX `idx-objecttoconvert_object` (`objecttypes_id` ASC, `objectid` ASC) ,
    INDEX `fk_objecttoconvert_objectypes` (`objecttypes_id` ASC) ,
    CONSTRAINT `fk_objecttoconvert_objectypes`
        FOREIGN KEY (`objecttypes_id` )
            REFERENCES `openidm`.`objecttypes` (`id` )
            ON DELETE CASCADE
            ON UPDATE NO ACTION)
    ENGINE = InnoDB;
    
    CREATE TABLE IF NOT EXISTS `openidm`.`objecttoconvert_genproperties` (
    `objecttoconvert_gen_id` BIGINT UNSIGNED NOT NULL ,
    `propkey` VARCHAR(255) NOT NULL ,
    `proptype` VARCHAR(32) NULL ,
    `propvalue` VARCHAR(2000) NULL ,
    `propindex` BIGINT NOT NULL DEFAULT 0,
    PRIMARY KEY (`objecttoconvert_gen_id`, `propkey`, `propindex`),
    INDEX `fk_objecttoconvertproperties_managedobjects` (`objecttoconvert_gen_id` ASC) ,
    INDEX `idx_objecttoconvertproperties_propkey` (`propkey` ASC) ,
    INDEX `idx_objecttoconvertproperties_propvalue` (`propvalue`(255) ASC) ,
    CONSTRAINT `fk_objecttoconvertproperties_objecttoconvert`
    FOREIGN KEY (`objecttoconvert_gen_id` )
    REFERENCES `openidm`.`objecttoconvert_gen` (`id` )
    ON DELETE CASCADE
    ON UPDATE NO ACTION)
    ENGINE = InnoDB;
  2. Modify conf/repo.jdbc.json to map the object path in the generic mapping section to the empty generic table. If the migrated data will have additional searchable columns, add them now.

  3. Create a conf/migration.json file with the following details:

    • Update the authentication settings to match the system configuration.

    • Modify the instanceUrl to point to the same system.

      For example:

      {
        "enabled" : true,
        "endpoint" : "",
        "connection" : {
          "instanceUrl" : "http://localhost:8080/openidm/",
          "authType" : "basic",
          "userName" : "openidm-admin",
          "password" : "openidm-admin"
        },
        "mappings" : [
          {
            "target" : "repo/managed/objectToConvert_gen",
            "source" : "repo/managed/objectToConvert"
          }
        ]
      }
  4. Call the migration service to view the mapping name that was generated:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header 'Accept-API-Version: resource=1.0' \
    --request POST 'http://localhost:8080/openidm/migration?_action=mappingNames'
    [
      [
        "repoManagedObjecttoconvert_repoManagedObjecttoconvertGen"
      ]
    ]
  5. Start the migration:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --request POST \
    "http://localhost:8080/openidm/migration?_action=migrate&mapping=repoManagedObjecttoconvert_repoManagedObjecttoconvertGen"
    {
      "migrationResults": {
        "recons": [
          {
            "name": "repoManagedObjecttoconvert_repoManagedObjecttoconvertGen",
            "status": "PENDING"
          }
        ]
      }
    }
  6. You must wait until the migration is completed. To check the status of the migration:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header 'Accept-API-Version: resource=1.0' \
    --request POST 'http://localhost:8080/openidm/migration?_action=status'
    Example Return
    {
      "migrationResults": {
        "recons": [
          {
            "name": "repoManagedObjecttoconvert_repoManagedObjecttoconvertGen",
            "status": {
              "_id": "820a1c66-6f1a-41d8-82a4-fc5a2d246326-424",
              "mapping": "repoManagedObjecttoconvert_repoManagedObjecttoconvertGen",
              "state": "SUCCESS",
              "stage": "COMPLETED_SUCCESS",
              "stageDescription": "reconciliation completed.",
              "progress": {
                "source": {
                  "existing": {
                    "processed": 0,
                    "total": "9"
                  }
                },
                "target": {
                  "existing": {
                    "processed": 0,
                    "total": "?"
                  },
                  "created": 0,
                  "unchanged": 0,
                  "updated": 0,
                  "deleted": 0
                },
                "links": {
                  "existing": {
                    "processed": 0,
                    "total": "0"
                  },
                  "created": 0
                }
              },
              "situationSummary": {
                "SOURCE_IGNORED": 0,
                "FOUND_ALREADY_LINKED": 0,
                "UNQUALIFIED": 0,
                "ABSENT": 0,
                "TARGET_IGNORED": 0,
                "MISSING": 0,
                "ALL_GONE": 0,
                "UNASSIGNED": 0,
                "AMBIGUOUS": 0,
                "CONFIRMED": 0,
                "LINK_ONLY": 0,
                "SOURCE_MISSING": 0,
                "FOUND": 0
              },
              "statusSummary": {
                "SUCCESS": 0,
                "FAILURE": 9
              },
              "durationSummary": {
                "sourceObjectQuery": {
                  "min": 26,
                  "max": 33,
                  "mean": 30,
                  "count": 9,
                  "sum": 277,
                  "stdDev": 2
                },
                "sourceQuery": {
                  "min": 37,
                  "max": 37,
                  "mean": 37,
                  "count": 1,
                  "sum": 37,
                  "stdDev": 0
                },
                "auditLog": {
                  "min": 0,
                  "max": 1,
                  "mean": 0,
                  "count": 11,
                  "sum": 9,
                  "stdDev": 0
                },
                "linkQuery": {
                  "min": 4,
                  "max": 4,
                  "mean": 4,
                  "count": 1,
                  "sum": 4,
                  "stdDev": 0
                },
                "correlationQuery": {
                  "min": 8,
                  "max": 18,
                  "mean": 15,
                  "count": 9,
                  "sum": 139,
                  "stdDev": 4
                },
                "sourcePhase": {
                  "min": 113,
                  "max": 113,
                  "mean": 113,
                  "count": 1,
                  "sum": 113,
                  "stdDev": 0
                }
              },
              "parameters": {
                "sourceQuery": {
                  "resourceName": "external/migration/repo/managed/objectToConvert",
                  "queryFilter": "true",
                  "_fields": "_id"
                },
                "targetQuery": {
                  "resourceName": "repo/managed/objectToConvert_gen",
                  "queryFilter": "true",
                  "_fields": "_id"
                }
              },
              "started": "2021-01-20T18:22:34.026Z",
              "ended": "2021-01-20T18:22:34.403Z",
              "duration": 377,
              "sourceProcessedByNode": {}
            }
          }
        ]
      }
    }
    Optionally, you can run the migration again to account for changes that may have occurred during the original migration.

    The data is now migrated to the new tables, but IDM is still referencing the previous mapping.

  7. Edit the repo.jdbc.json file:

    • Remove the old mapping from explicitMapping:

      "explicitMapping" : {
      ...
      "managed/objectToConvert" : {
        "table" : "objecttoconvert",
        "objectToColumn" : {
          "_id" : "objectid",
          "_rev" : "rev",
          "desc" : "descr"
        }
      }
    • Modify the newly added genericMapping to point to the old resource path:

      "genericMapping" : {
        ...
        "managed/objectToConvert" : {
          "mainTable" : "objecttoconvert_gen",
          "propertiesTable" : "objecttoconvert_genproperties",
          "searchableDefault" : false,
          "objectToColumn" : {
            "_id" : "objectid",
            "_rev" : "rev",
            "desc" : "descr"
          },
          "properties": {
            "/stringArrayField" : {
              "searchable" : true
            }
          }
        },
      }
  8. Run a SQL update statement so that the objecttypes table points the temporary object type to the original object type. Adjust the following example to match your repository requirements, as needed:

    update openidm.objecttypes set objecttype = 'managed/objectToConvert' where objecttype = 'managed/objectToConvert_gen';

Convert a generic mapped object to an explicit mapped object (JDBC)

This procedure demonstrates how to migrate data to a different storage configuration within the same system using the migration service to convert the object data.

Considerations before you start:

  • After you complete the process, object resource paths must stay the same to maintain relationship references.

  • You must migrate data to an empty table. Unlike generic tables, explicit mapped objects expect the table to contain records from a single object type.

  • During the migration, changes made to the source object might not be transferred to the new object. To ensure everything is migrated correctly, run the migration during idle time, or when the system is least busy.

This procedure assumes an existing generic object resource path of managed/objectToConvert, with objects stored in the generic objects table genericobjects. A sample object might be:

{
  "_id" : "4213-2134-23423",
  "_rev" : "AB231A",
  "name" : "Living room camera",
  "properties": { "location" : "45.123N100.123W", "uptime" : 123123 },
  "otherProperties" : { "bla": "blabla", "blahdee" : "da"}
}

Before you start, consider the following:

  • Make sure to map a column to each field of your object.

  • Fields that are objects, not simple scalar values, will be stored as serialized JSON, and won’t be easily searchable.

  • Object instances are constituted by selecting the mapped columns and putting the data in the JSON object using the field path that the column is mapped to.

  • Create table indexes that are inline with your system’s usage of searches and sorting of the column data. For example, modify or add indexes to include all newly created columns for any fields that were configured as searchable.

  1. Create the new explicit table:

    CREATE TABLE `openidm`.`objectToConvert` (
      `objectid` VARCHAR(255) NOT NULL ,
      `rev` VARCHAR(38) NOT NULL ,
      `name` VARCHAR(255) NOT NULL ,
      `location` VARCHAR(38) NULL ,
      `uptime` BIGINT NULL ,
      `misc` MEDIUMTEXT NULL,
    PRIMARY KEY (`objectid`));
  2. Modify conf/repo.jdbc.json to add a new mapping for the object type in the explicitMapping node. To avoid conflict with the generically mapped object path, slightly modify the resource path. A new explicit mapping example:

    "explicitMapping" : {
      ...
        "managed/objectToConvert_explicit": {
          "table": "objectToConvert",
          "objectToColumn" : {
            "_id" : "objectid",
            "_rev" : { "column" : "rev", "isNotNull" : true },
            "name" : { "column" : "name", "isNotNull" : true },
            "properties/location" : { "column": "location" },
            "properties/uptime" : { "column" : "uptime" },
            "otherProperties" : {
              "column" : "misc",
              "type" : "JSON_MAP"
            }
          }
        }
        ...
      ...
    }
  3. Create a conf/migration.json file with the following details:

    • Update the authentication settings to match the system configuration.

    • Modify the instanceUrl to point to the same system.

      For example:

      {
        "enabled" : true,
        "endpoint" : "",
        "connection" : {
          "instanceUrl" : "http://localhost:8080/openidm/",
          "authType" : "basic",
          "userName" : "openidm-admin",
          "password" : "openidm-admin"
        },
        "mappings" : [
          {
            "target" : "repo/managed/objectToConvert_explicit",
            "source" : "repo/managed/objectToConvert"
          }
        ]
      }
  4. Call the migration service to view the mapping name that was generated:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header 'Accept-API-Version: resource=1.0' \
    --request POST 'http://localhost:8080/openidm/migration?_action=mappingNames'

    IDM returns something similar to:

    [
      [
        "repoManagedObjecttoconvert_repoManagedObjecttoconvertExplicit"
      ]
    ]
  5. Start the migration:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --request POST \
    "http://localhost:8080/openidm/migration?_action=migrate&mapping=repoManagedObjecttoconvert_repoManagedObjecttoconvertExplicit"

    IDM returns something similar to:

    {
      "migrationResults": {
        "recons": [
          {
            "name": "repoManagedObjecttoconvert_repoManagedObjecttoconvertExplicit",
            "status": "PENDING"
          }
        ]
      }
    }
  6. You must wait until the migration completes. To check the status of the migration:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header 'Accept-API-Version: resource=1.0' \
    --request POST 'http://localhost:8080/openidm/migration?_action=status'

    IDM returns something similar to:

    {
      "migrationResults": {
        "recons": [
          {
            "name": "repoManagedObjecttoconvert_repoManagedObjecttoconvertExplicit",
            "status": {
              "_id": "820a1c66-6f1a-41d8-82a4-fc5a2d246326-424",
              "mapping": "repoManagedObjecttoconvert_repoManagedObjecttoconvertExplicit",
              "state": "SUCCESS",
              "stage": "COMPLETED_SUCCESS",
              "stageDescription": "reconciliation completed.",
              "progress": {
                "source": {
                  "existing": {
                    "processed": 0,
                    "total": "9"
                  }
                },
                "target": {
                  "existing": {
                    "processed": 0,
                    "total": "?"
                  },
                  "created": 0,
                  "unchanged": 0,
                  "updated": 0,
                  "deleted": 0
                },
                "links": {
                  "existing": {
                    "processed": 0,
                    "total": "0"
                  },
                  "created": 0
                }
              },
              "situationSummary": {
                "SOURCE_IGNORED": 0,
                "FOUND_ALREADY_LINKED": 0,
                "UNQUALIFIED": 0,
                "ABSENT": 0,
                "TARGET_IGNORED": 0,
                "MISSING": 0,
                "ALL_GONE": 0,
                "UNASSIGNED": 0,
                "AMBIGUOUS": 0,
                "CONFIRMED": 0,
                "LINK_ONLY": 0,
                "SOURCE_MISSING": 0,
                "FOUND": 0
              },
              "statusSummary": {
                "SUCCESS": 0,
                "FAILURE": 9
              },
              "durationSummary": {
                "sourceObjectQuery": {
                  "min": 26,
                  "max": 33,
                  "mean": 30,
                  "count": 9,
                  "sum": 277,
                  "stdDev": 2
                },
                "sourceQuery": {
                  "min": 37,
                  "max": 37,
                  "mean": 37,
                  "count": 1,
                  "sum": 37,
                  "stdDev": 0
                },
                "auditLog": {
                  "min": 0,
                  "max": 1,
                  "mean": 0,
                  "count": 11,
                  "sum": 9,
                  "stdDev": 0
                },
                "linkQuery": {
                  "min": 4,
                  "max": 4,
                  "mean": 4,
                  "count": 1,
                  "sum": 4,
                  "stdDev": 0
                },
                "correlationQuery": {
                  "min": 8,
                  "max": 18,
                  "mean": 15,
                  "count": 9,
                  "sum": 139,
                  "stdDev": 4
                },
                "sourcePhase": {
                  "min": 113,
                  "max": 113,
                  "mean": 113,
                  "count": 1,
                  "sum": 113,
                  "stdDev": 0
                }
              },
              "parameters": {
                "sourceQuery": {
                  "resourceName": "external/migration/repo/managed/objectToConvert",
                  "queryFilter": "true",
                  "_fields": "_id"
                },
                "targetQuery": {
                  "resourceName": "repo/managed/objectToConvert_explicit",
                  "queryFilter": "true",
                  "_fields": "_id"
                }
              },
              "started": "2021-01-20T18:22:34.026Z",
              "ended": "2021-01-20T18:22:34.403Z",
              "duration": 377,
              "sourceProcessedByNode": {}
            }
          }
        ]
      }
    }
    Optionally, you can run the migration again to account for changes that may have occurred during the original migration.

    The data is now migrated to the new tables, but IDM is still referencing the previous mapping and generic table.

  7. Edit the repo.jdbc.json file:

    • If the mapping of the generic resource had a mapping, it should be removed. If the generic resource was included in the managed/* path, as in the example, there is nothing to remove.

    • Modify the object path from managed/objectToConvert_explicit to managed/objectToConvert.

  8. Save the repo.jdbc.json file.

    Until IDM processes the configuration change, REST requests are unavailable.

  9. Once IDM finishes processing the configuration change, it is safe to delete the object data from the original generic table. Using a proper delete cascade, the searchable properties of the generic object are automatically deleted from the generic properties table. For example:

    delete
    from
        managedobjects
    where
        objecttypes_id = (
        select
            o2.id
        from
            objecttypes o2
        where
            objecttype = "managed/objectToConvert");

Convert a generic mapped object to a hybrid mapped object (JDBC)

This procedure demonstrates how to convert a generically stored property to an explicitly stored property; moving property value storage out of the object’s property table, and into a new column on the object table. After you finish the conversion, the converted objects are technically hybrid objects—generically mapped objects that have certain fields that are mapped to explicit columns.

Considerations before you start:

  • Changes made to the source object during migration might not be transferred to the new object. To ensure everything is migrated correctly, run the migration during idle time, or when the system is least busy.

This procedure assumes an existing generic object resource path as: managed/objectToConvert, with objects stored in the generic objects table objecttoconvertobjects. A sample object might be:

{
  "_id" : "4213-2134-23423",
  "_rev" : "AB231A",
  "name" : "Living room camera",
  "properties": { "location" : "45.123N100.123W", "uptime" : 123123 },
  "otherProperties" : { "bla": "blabla", "blahdee" : "da"}
}
  1. Create a new database column for the explicit field data:

    alter table openidm.objecttoconvertobjects add column `name` varchar(255);
  2. Edit the genericMapping section of the conf/repo.jdbc.json file:

    • For each object to convert, add the objectToColumn configuration for the fields to explicitly map to a column. For example:

      "genericMapping" : {
        ...
          "managed/objectToConvert": {
            "mainTable" : "objecttoconvertobjects",
            "propertiesTable" : "objecttoconvertobjectsproperties",
            "searchableDefault" : true,
            "objectToColumn" : {
              "name" : "name"
            }
          }
          ...
      }
    • If the object is defined with a wildcard mapping, such as managed/*, create a new mapping specifically for the object conversion.

  3. Save the conf/repo.jdbc.json file.

    Until IDM processes the configuration change, REST requests are unavailable.

  4. For any added columns to be usable, they must be reindexed and populated.

    The rewriteObjects.js script can read and rewrite object data to match the config from the conf/repo.jdbc.json file. The script reads one page of data at a time, and then writes out each object, one at a time. Consider the page size and queryFilter to efficiently process the data by splitting the data into groups that can run in parallel. The request will not return until ALL pages have been processed.

    For example, to run the rewriteObjects.js script with 1000 objects per page:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header 'Accept-API-Version: resource=1.0' \
    --header 'content-type: application/json' \
    --request POST \
    --data-raw '{
      "type":"text/javascript",
      "file":"bin/defaults/script/update/rewriteObjects.js",
      "globals" : {
        "rewriteConfig" :{
          "queryFilter": "true",
          "pageSize": 1000,
          "objectPaths": [
            "repo/managed/objectToConvert"
          ]
        }
      }
    }' \
    "http://localhost:8080/openidm/script?_action=eval"
  5. Now that the new column contains data, edit or create new indexes so that the new column can be queried efficiently. For example:

    alter table openidm.objecttoconvertobjects add index `idx_obj_name` (`name`);

Mappings with a DS repository

For both generic and explicit mappings, IDM maps object types using a dnTemplate property. The dnTemplate is effectively a pointer to where the object is stored in DS. For example, the following excerpt of the default repo.ds.json file shows how configuration objects are stored under the DN ou=config,dc=openidm,dc=forgerock,dc=com:

"config": {
    "dnTemplate": "ou=config,dc=openidm,dc=forgerock,dc=com"
}

Generic mappings (DS)

By default, IDM uses a generic mapping for all objects except the following:

  • Internal users, roles, and privileges

  • Links

  • Clustered reconciliation target IDs

    Clustered reconciliation is not supported with a DS repository.
  • Locks

  • Objects related to queued synchronization

With a generic mapping, all the properties of an object are stored as a single JSON blob in the fr-idm-json attribute. To create a new generic mapping, you need only specify the dnTemplate; that is, where the object will be stored in the directory tree.

You can specify a wildcard mapping, that stores all nested URIs under a particular branch of the directory tree, for example:

"managed/*": {
    "dnTemplate": "ou=managed,dc=openidm,dc=forgerock,dc=com"
}

With this mapping, all objects under managed/, such as managed/user and managed/device, will be stored in the branch ou=managed,dc=openidm,dc=forgerock,dc=com. You do not have to specify separate mappings for each of these objects. The mapping creates a new ou for each object. So, for example, managed/user objects will be stored under the DN ou=user,ou=managed,dc=openidm,dc=forgerock,dc=com and managed/device objects will be stored under the DN ou=device,ou=managed,dc=openidm,dc=forgerock,dc=com.

In cases where you want to improve external DS search performance, you can configure indexing for your resources within DS. For more information on indexing in DS, refer to Indexes in the DS documentation.

Explicit mappings (DS)

The default configuration uses a generic mapping for managed user objects. To use an explicit mapping for managed user objects, change the repository configuration before you start IDM for the first time.

To set up an explicit mapping:

  1. Copy the repo.ds-explicit-managed-user.json file to your project’s conf directory, and rename that file repo.ds.json :

    cp /path/to/openidm/db/ds/conf/repo.ds-explicit-managed-user.json project-dir/conf/repo.ds.json
    This file is configured for an embedded DS repository by default. To set up an explicit mapping for an external DS repository, change the value of the embedded property to false and add the following properties:
    "security": {
      "trustManager": "file",
      "fileBasedTrustManagerType": "JKS",
      "fileBasedTrustManagerFile": "&{idm.install.dir}/security/truststore",
      "fileBasedTrustManagerPasswordFile": "&{idm.install.dir}/security/storepass"
    },
    "ldapConnectionFactories": {
      "bind": {
        "connectionSecurity": "startTLS",
        "heartBeatIntervalSeconds": 60,
        "heartBeatTimeoutMilliSeconds": 10000,
        "primaryLdapServers": [
          {
            "hostname": "localhost",
            "port": 31389
          }
        ],
        "secondaryLdapServers": []
      },
      "root": {
        "inheritFrom": "bind",
        "authentication": {
          "simple": {
            "bindDn": "uid=admin",
            "bindPassword": "password"
          }
        }
      }
    }

    For more information on these properties, refer to DS Repository Configuration.

  2. Start IDM.

IDM uses the DS REST to LDAP gateway to map JSON objects to LDAP objects stored in the directory. To create additional explicit mappings, you must specify the LDAP objectClasses to which the object is mapped, and how each property maps to its corresponding LDAP attributes. Specify at least the property type and the corresponding ldapAttribute. For relationships between objects, you must explicitly define those objects in the repository configuration.

The following excerpt shows an example of an explicit managed user object mapping:

"managed/user" : {
    "dnTemplate": "ou=user,ou=managed,dc=openidm,dc=forgerock,dc=com",
    "objectClasses": [
        "person",
        "organizationalPerson",
        "inetOrgPerson",
        "fr-idm-managed-user-explicit",
        "inetuser"
    ],
    "properties": {
        "_id": {
            "type": "simple", "ldapAttribute": "uid", "isRequired": true, "writability": "createOnly"
        },
        "userName": {
            "type": "simple", "ldapAttribute": "cn"
        },
        "password": {
            "type": "json", "ldapAttribute": "fr-idm-password"
        },
        "accountStatus": {
            "type": "simple", "ldapAttribute": "fr-idm-accountStatus"
        },
        "roles": {
            "type": "json", "ldapAttribute": "fr-idm-role", "isMultiValued": true
        },
        "effectiveRoles": {
            "type": "json", "ldapAttribute": "fr-idm-effectiveRole", "isMultiValued": true
        },
        "effectiveAssignments": {
            "type": "json", "ldapAttribute": "fr-idm-effectiveAssignment", "isMultiValued": true
        },
        ...
    }
}

You do not need to map the _rev (revision) property of an object as this property is implicit in all objects and maps to the DS etag operational attribute.

If your data objects include virtual properties, you must include property mappings for these properties. If you don’t explicitly map the virtual properties, you will encounter errors similar to the following when you attempt to create the corresponding object:

{
    "code": 400,
    "reason": "Bad Request",
    "message": "Unmapped fields..."
}

For more information about the REST to LDAP property mappings, refer to Mapping Configuration in the DS REST API Guide.

For performance reasons, the DS repository does not apply unique constraints to links. This behavior is different to the JDBC repositories, where uniqueness on link objects is enforced.

DS currently has a default index entry limit of 4000. Therefore, you cannot query more than 4000 records unless you create a Virtual List View (VLV) index. A VLV index is designed to help DS respond to client applications that need to browse through a long list of objects.

You cannot create a VLV index on a JSON attribute. For generic mappings, IDM avoids this restriction by using client-side sorting and searching. However, for explicit mappings you must create a VLV index for any filtered or sorted results, such as results displayed in a UI grid. To configure a VLV index, use the dsconfig command described in Virtual List View Index in the DS Configuration Guide.

Specify how IDM IDs map to LDAP entry names

The DS REST2LDAP configuration lets you set a namingStrategy that specifies how LDAP entry names are mapped to JSON resources. When IDM stores its objects in a DS repository, this namingStrategy determines how the IDM _id value maps to the Relative Distinguished Name (RDN) of the corresponding DS object.

The namingStrategy is specified as part of the explicitMapping of an object in the repo.ds.json file. The following example shows a naming strategy configuration for an explicit managed user mapping:

"resourceMapping": {
    "defaultMapping": {
        "dnTemplate": "ou=generic,dc=openidm,dc=forgerock,dc=com"
    },
    ...
    "explicitMapping": {
        "managed/user": {
            "dnTemplate": "ou=user,ou=managed,dc=openidm,dc=forgerock,dc=com",
            "objectClasses": [
                "person",
                "organizationalPerson",
                "inetOrgPerson",
                "fr-idm-managed-user-explicit"
            ],
            "namingStrategy": {
                "type": "clientDnNaming",
                "dnAttribute": "uid"
            },
            ...
        }
    }
}

The namingStrategy can be one of the following:

  • clientDnNaming - IDM provides an _id to DS and that _id is used to generate the DS RDN. In the following example, the IDM _id maps to the LDAP uid attribute:

    {
        "namingStrategy": {
            "type": "clientDnNaming",
            "dnAttribute": "uid"
        }
    }

    With this default configuration, entries are stored in DS with a DN similar to the following:

    "uid=idm-uuid,ou=user,ou=managed,dc=openidm,dc=forgerock,dc=com"
    If these default DNs are suitable in your deployment, you do not have to change anything with regard to the naming strategy.
  • clientNaming - IDM provides an _id to DS but the DS RDN is derived from a different user attribute in the LDAP entry. In the following example, the RDN is the cn attribute. The _id that IDM provides for the object maps to the LDAP uid attribute:

    {
        "namingStrategy": {
            "type": "clientNaming",
            "dnAttribute": "cn",
            "idAttribute": "uid"
        }
    }

    With this configuration, entries are stored in DS with a DN similar to the following:

    "cn=username,ou=user,ou=managed,dc=openidm,dc=forgerock,dc=com"

Specifying a namingStrategy is optional. If you do not specify a strategy, the default is clientDnNaming with the following configuration:

{
    "namingStrategy" : {
        "type" : "clientDnNaming",
        "dnAttribute" : "uid"
    },
    "properties: : {
        "_id": {
            "type": "simple",
            "ldapAttribute": "uid",
            "isRequired": true,
            "writability": "createOnly"
        },
        ...
    }
}

If you do not set a dnAttribute as part of the naming strategy, the value of the dnAttribute is taken from the value of the ldapAttribute on the _id property.

Relationship properties in a DS repository

The IDM object model lets you define relationships between objects. In a DS repository, relationships are implemented using the reference and reverseReference REST to LDAP property types. For more information about the reference and reverseReference property types, read the JSON property mapping section of the DS HTTP User Guide.

Relationship properties must be defined in the repository configuration (repo.ds.json), for both generic and explicit object mappings.

The following property definitions for a managed/user object show how the relationship between a manager and their reports is defined in the repository configuration:

"managed/user" : {
    "dnTemplate" : "ou=user,ou=managed,dc=openidm,dc=forgerock,dc=com",
    ...
    "properties" : {
        ...
        "reports" : {
            "type" : "reverseReference",
            "resourcePath" : "managed/user",
            "propertyName" : "manager",
            "isMultiValued" : true
        },
        "manager" : {
            "type" : "reference",
            "ldapAttribute" : "fr-idm-managed-user-manager",
            "primaryKey" : "uid",
            "resourcePath" : "managed/user",
            "isMultiValued" : false
        },
        ...
    }
}

This configuration sets the reports property as a reverseReference, or reverse relationship of the manager property. This means that if you add a manager to a user, the user automatically becomes one of the reports of that manager.

Note the ldapAttribute defined in the relationship object (fr-idm-managed-user-manager in this case). Your DS schema must include this attribute, and an object class that contains this attribute. Relationship attributes in the DS schema must use the Name and JSON with id syntax.

The following example shows the DS schema definition for the IDM manager property:

attributeTypes: ( 1.3.6.1.4.1.36733.2.3.1.69
  NAME 'fr-idm-managed-user-manager'
  DESC 'Reference to a users manager'
  SINGLE-VALUE
  SYNTAX 1.3.6.1.4.1.36733.2.1.3.12
  EQUALITY nameAndOptionalCaseIgnoreJsonIdEqualityMatch
  X-STABILITY 'Internal' )

If you define a relationship in the managed object configuration and you do not define that relationship as a reference or reverse reference in the repository configuration (repo.ds.json ), you will be able to query the relationships, but filtering and sorting on those queries will not work. This is the case when you define relationship objects in the admin UI—the relationship is defined only in the managed object configuration and not in the repository configuration.

In this case, queries such as the following are not supported:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/_id/managedOrgs?_pageSize=50&_sortKeys=_id&_totalPagedResultsPolicy=ESTIMATE&_queryFilter=true"

This restriction includes delegated admin privilege filters.

Access Data Objects

Access data objects using scripts

IDM’s uniform programming model means that all objects are queried and manipulated in the same way, using the Resource API. The URL or URI that is used to identify the target object for an operation depends on the object type. For more information about scripts and the objects available to scripts, refer to Scripting.

You can use the Resource API to obtain managed, system, configuration, and repository objects, as follows:

val = openidm.read("managed/organization/mysampleorg")
val = openidm.read("system/mysystem/account")
val = openidm.read("config/custom/mylookuptable")
val = openidm.read("repo/custom/mylookuptable")

For information about constructing an object ID, refer to URI Scheme.

You can update entire objects with the update() function, as follows:

openidm.update("managed/organization/mysampleorg", rev, object)
openidm.update("system/mysystem/account", rev, object)

You can apply a partial update to a managed or system object by using the patch() function:

openidm.patch("managed/organization/mysampleorg", rev, value)

The create(), delete(), and query() functions work the same way.

Access data objects using the REST API

IDM provides RESTful access to data objects through the ForgeRock Common REST API. To access objects over REST, you can use a browser-based REST client, such as the Simple REST Client for Chrome, or RESTClient for Firefox. Alternatively, you can use the curl command-line utility.

For a comprehensive overview of the REST API, refer to the REST API reference.

To obtain a managed object through the REST API, depending on your security settings and authentication configuration, perform an HTTP GET on the corresponding URL, for example http://localhost:8080/openidm/managed/organization/mysampleorg.

By default, the HTTP GET returns a JSON representation of the object.

In general, you can map any HTTP request to the corresponding openidm.method call. The following example shows how the parameters provided in an openidm.query request correspond with the key-value pairs that you would include in a similar HTTP GET request:

Reading an object using the Resource API:

openidm.query("managed/user", { "_queryFilter": "true" }, ["userName","sn"])

Reading an object using the REST API:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user?_queryFilter=true&_fields=userName,sn"

Access data objects by remote proxy

You can proxy REST requests to a remote IDM or Identity Cloud instance using the openidm/external/idm/instanceName endpoint.

For more information on determining the exact value for instanceName, refer to How to determine the value for instanceName.

This functionality lets you treat any other IDM or Identity Cloud instance as a resource within the one you are managing. You can then use it in a sync mapping, call actions on it, use it within scripts, or use it in any other way that you might use a resource in IDM. You can call any endpoint in the remote IDM system using this proxy.

A few situations where this feature may be useful include:

  • Situations where some, but not all, data needs to be migrated from an older version to a newer release.

  • Situations where a development or testing environment has data that needs to be synced into the production environment.

  • Situations where data is deployed in geographically diverse data centers and changes need to be kept in sync with one another.

  • Situations where a new instance needs to sync data between existing on-premises and cloud instances.

This feature does not support liveSync/implicit sync from the remote IDM resources. This means that you will be limited to using recon when it comes to pulling data from a remote system.

If requests sent to the source server include an X-Requested-With header, the value of the header will be set to RemoteIDMProxy.

How to determine the value for instanceName

The instanceName is a fragment of the external configuration’s name. You can determine the value for instanceName using REST or the filesystem:

Using the filesystem

  1. Go to /path/to/openidm/conf/.

  2. Locate the file named external.idm-instanceName.json.

    For example, a file named external.idm-name1.json would be available as a remote system at the openidm/external/idm/name1 endpoint.

Using REST

  1. Get the configurations:

    Request
    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --request GET \
    "https://localhost:8443/openidm/config/"
  2. Locate the external configuration:

    Return
    {
      "_id": "",
      "configurations": [
        ...
        {
          "_id": "emailTemplate/welcome",
          "pid": "emailTemplate.212e...f7a",
          "factoryPid": "emailTemplate"
        },
        ...
        {
          "_id": "external.idm/name1", (1)
          "pid": "external.idm.29cd...f4a",
          "factoryPid": "external.idm"
        },
        ...
      ]
    }
    1 In this example, the external configuration "_id": "external.idm/name1" would be available as a remote system at the openidm/external/idm/name1 endpoint.

Prerequisites

To connect to a remote instance over SSL, you must import the remote instance’s server certificate into your local instance’s truststore. For example:

keytool \
-import \
-alias fr-platform \
-keystore security/truststore \
-file ~/fr-platform.pem

Mapping

To use the remote IDM proxy in a synchronization mapping, add the following to your sync.json file or individual mapping file (updating the values as necessary):

{
  "name" : "onprem_user_to_fidc_alpha_user",
  "source" : "external/idm/65/managed/user",
  "target" : "external/idm/fidc/managed/alpha_user"
}

Authentication

Authentication against the remote IDM instance is supported via basic authentication, or bearer token authentication when IDM is configured to use rsFilter. The authentication strategy determines some of the parameters required for the request.

Property Required? Definition

enabled

No

The enable state of the service. Default is true.

scope

No

The requested OAuth2 scope(s).

scopeDelimiter

No

The scope delimiter to use. Defaults to space.

authtype

Yes

The authentication strategy to use. Either basic or bearer.

instanceUrl

Yes

The URL of the remote instance to relay the request to.

userName

With basic auth

The basic authentication user name.

password

With basic auth

The basic authentication password.

clientId

With bearer auth

The clientId used to request an access token from the token endpoint.

clientSecret

With bearer auth

The client secret used to request an access token from the token endpoint.

tokenEndpoint

With bearer auth

The OAuth2 token endpoint.

Examples

Basic authentication

{
  "enabled" : true,
  "authType" : "basic",
  "instanceUrl" : "https://localhost:8443/openidm/",
  "userName" : "openidm-admin",
  "password" : "openidm-admin"
}

Bearer/Oauth2 authentication

{
  "enabled" : true,
  "authType" : "bearer",
  "instanceUrl" : "https://fr-platform.iam.example.com/openidm/",
  "clientId" : "idm-provisioning",
  "clientSecret" : "password",
  "scope" : [ ],
  "tokenEndpoint" : "https://fr-platform.iam.example.com/am/oauth2/realms/root/access_token",
  "scopeDelimiter" : " "
}

REST request

Request
curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--insecure \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'https://localhost:8443/openidm/external/idm/platform/managed/user?_queryFilter=true'
Return
{
  "result": [
    {
      "_id": "95b2b43c-621e-4bca-8a97-efc768f17751",
      "_rev": "00000000f20217df",
      "userName": "testUser",
      "accountStatus": "active",
      "givenName": "Test",
      "sn": "User",
      "mail": "testUser@test.com"
    }
  ],
  "resultCount": 1,
  "pagedResultsCookie": null,
  "totalPagedResultsPolicy": "NONE",
  "totalPagedResults": -1,
  "remainingPagedResults": -1
}

Script

openidm.query("external/idm/fidc/managed/alpha_user", {"_queryFilter": "userName eq 'bjensen'"});

Define and call data queries

An advanced query model enables you to define queries and to call them over the REST or Resource API. The following types of queries are supported, on both managed, and system objects:

  • Common filter expressions

  • Parameterized, or predefined queries

  • Native query expressions

For limits on queries in progressive profiling, refer to Custom Progressive Profile Conditions.

Queries on object array properties (JDBC)

Support for queries on object array properties requires the following:

  • A JDBC repository with generic object mapping. Queries on arrays are not supported with explicit mappings. If you need to convert from explicitly mapped objects to generic, refer to Convert an Explicit Mapped Object to a Hybrid Mapped Object (JDBC).

  • For PostgreSQL only, you must configure array fields. Additional information about PostgreSQL JSON functions.

  • For JDBC repositories other than PostgreSQL, the array property must be configured as searchable. If you add additional properties as searchable after the initial install/migration of IDM, run the /path/to/openidm/bin/defaults/script/update/rewriteObjects.js script, specifying the new objectPaths of properties to make searchable:

    curl \
    --header "Content-Type: application/json" \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header 'X-OpenIDM-NoSession: true' \
    --request POST \
    --data-raw '{
      "type": "text/javascript",
      "file": "/path/to/openidm/bin/defaults/script/update/rewriteObjects.js",
      "globals": {
        "rewriteConfig": {
          "queryFilter": "true",
          "pageSize": 1000,
          "objectPaths": [
            "repo/config",
            "repo/internal/usermeta",
            "repo/managed/role",
            "repo/managed/user",
            "repo/reconprogressstate",
            "repo/relationships",
            "repo/scheduler/triggers"
          ]
        }
      }
    }' \
    "http://localhost:8080/openidm/script/?_action=eval"
  • Do not use array fields in a sortKey.

Special characters in queries

JavaScript query invocations are not subject to the same URL-encoding requirements as GET requests. Because JavaScript supports the use of single quotes, it is not necessary to escape the double quotes from most examples in this guide. Make sure to protect against pulling in data that could contain special characters, such as double-quotes ("). The following example shows one method of handling special characters:

"correlationQuery" : {
  "type" : "text/javascript",
  "source" : "var qry = {'_queryFilter': org.forgerock.util.query.QueryFilter.equalTo('uid', source.userName).toString()}; qry"
}

Common filter expressions

The ForgeRock REST API defines common filter expressions that enable you to form arbitrary queries using a number of supported filter operations. This query capability is the standard way to query data if no predefined query exists, and is supported for all managed and system objects.

Common filter expressions are useful in that they do not require knowledge of how the object is stored and do not require additions to the repository configuration.

Common filter expressions are called with the _queryFilter keyword. The following example uses a common filter expression to retrieve managed user objects whose user name is Smith:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
'http://localhost:8080/openidm/managed/user?_queryFilter=userName+eq+"smith"'

The filter is URL encoded in this example. The corresponding filter using the resource API would be:

openidm.query("managed/user", { "_queryFilter" : '/userName eq "smith"' });

Note that, this JavaScript invocation is internal and is not subject to the same URL-encoding requirements that a GET request would be. Also, because JavaScript supports the use of single quotes, it is not necessary to escape the double quotes in this example.

Parameterized queries

You can access managed objects in JDBC repositories using custom parameterized queries. Define these queries in your JDBC repository configuration, (repo.*.json), and call them by their _queryId.

  • Parameterized queries are not supported for system objects, or for DS repositories.

  • All internal queries are filtered queries. Internal queries that reference a queryId are translated to filtered queries.

A typical query definition is as follows:

"query-all-ids" : "SELECT objectid FROM ${_dbSchema}.${_table} LIMIT ${int:_pageSize} OFFSET ${int:_pagedResultsOffset}",

To call this query, you would reference its ID, as follows:

?_queryId=query-all-ids

The following example calls query-all-ids over the REST interface:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
"http://localhost:8080/openidm/managed/user?_queryId=query-all-ids"

In repo.jdbc.json, the queries configuration object has a property, validInRelationshipQuery, which is an array specifying the IDs of queries that use relationships. If you define parameterized queries that you expect to use as part of a relationship query, you must add the query ID to this array. If no query IDs are specified or if the property is absent, relationship information is not returned in query results, even if requested. For more information about relationships, refer to Relationships between objects.

Native query expressions

Native query expressions are supported for system objects only, and can be called directly.

You should only use native queries in situations where common query filters or parameterized queries are insufficient. For example, native queries are useful if the query needs to be generated dynamically.

The query expression is specific to the target resource and uses the native query language of that system resource.

Native queries are made using the _queryExpression keyword.

Construct queries

The openidm.query function lets you query managed and system objects. The query syntax is openidm.query(id, params), where id specifies the object on which the query should be performed, and params provides the parameters that are passed to the query (the _queryFilter). For example:

var equalTo = org.forgerock.util.query.QueryFilter.equalTo;
queryParams = {
    "_queryFilter": equalTo("uid", value).toString()
};
openidm.query("managed/user", queryParams)

Over the REST interface, the query filter is specified as _queryFilter=filter, for example:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=userName+eq+"Smith"'

In _queryFilter expressions, string values must use double-quotes. Numeric and boolean expressions should not use quotes.

When called over REST, you must URL encode the filter expression. The following examples show the filter expressions using the resource API and the REST API, but do not show the URL encoding, to make them easier to read.

For generic mappings, any fields that are included in the query filter (for example userName in the previous query), must be explicitly defined as searchable, if you have set the global searchableDefault to false. For more information, refer to Improving Generic Mapping Search Performance (JDBC).

The filter expression is constructed from the building blocks shown in this section. In these expressions the simplest json-pointer is a field of the JSON resource, such as userName or id. A JSON pointer can, however, point to nested elements.

You can also use the negation operator (!) in query construction. For example, a _queryFilter=!(userName+eq+"jdoe") query would return every userName except for jdoe.

Comparison expressions

You can use comparison query filters for objects and object array properties that:

Equal a specified value

This is the associated JSON comparison expression: json-pointer eq json-value.

Example 1
"_queryFilter" : '/givenName eq "Dan"'

The following REST call returns the user name and given name of all managed users whose first name (givenName) is "Dan":

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=givenName+eq+"Dan"&_fields=userName,givenName'
{
  "result": [
    {
      "givenName": "Dan",
      "userName": "dlangdon"
    },
    {
      "givenName": "Dan",
      "userName": "dcope"
    },
    {
      "givenName": "Dan",
      "userName": "dlanoway"
    }
  ],
  ...
}
Example 2
"_queryFilter" : "/stringArrayField eq 'foo'"

The following REST call returns role entries where a value within the stringArrayField array equals "foo":

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/role?_queryFilter=stringArrayField+eq+"foo"'
{
  "result": [
    {
      "_id": "admin2",
      "_rev": "0",
      "name": "admin2",
      "stringArrayField": [
        "foo",
        "bar"
      ]
    }
  ],
  ...
}
Contain a specified value

This is the associated JSON comparison expression: json-pointer co json-value.

Example
"_queryFilter" : '/givenName co "Da"'

The following REST call returns the user name and given name of all managed users whose first name (givenName) contains "Da":

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=givenName+co+"Da"&_fields=userName,givenName'
{
  "result": [
    {
      "givenName": "Dave",
      "userName": "djensen"
    },
    {
      "givenName": "David",
      "userName": "dakers"
    },
    {
      "givenName": "Dan",
      "userName": "dlangdon"
    },
    {
      "givenName": "Dan",
      "userName": "dcope"
    },
    {
      "givenName": "Dan",
      "userName": "dlanoway"
    },
    {
      "givenName": "Daniel",
      "userName": "dsmith"
    },
    ...
  ],
  "resultCount": 10,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}
Start with a specified value

This is the associated JSON comparison expression: json-pointer sw json-value.

Example
"_queryFilter" : '/sn sw "Jen"'

The following REST call returns the user names of all managed users whose last name (sn) starts with "Jen":

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=sn+sw+"Jen"&_fields=userName'
{
  "result": [
    {
      "userName": "bjensen"
    },
    {
      "userName": "djensen"
    },
    {
      "userName": "cjenkins"
    },
    {
      "userName": "mjennings"
    }
  ],
  "resultCount": 4,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}
Are less than a specified value

This is the associated JSON comparison expression: json-pointer lt json-value.

Example
"_queryFilter" : '/employeeNumber lt 5000'

The following REST call returns the user names of all managed users whose employeeNumber is lower than 5000:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=employeeNumber+lt+5000&_fields=userName,employeeNumber'
{
  "result": [
    {
      "employeeNumber": 4907,
      "userName": "jnorris"
    },
    {
      "employeeNumber": 4905,
      "userName": "afrancis"
    },
    {
      "employeeNumber": 3095,
      "userName": "twhite"
    },
    {
      "employeeNumber": 3921,
      "userName": "abasson"
    },
    {
      "employeeNumber": 2892,
      "userName": "dcarter"
    },
    ...
  ],
  "resultCount": 4999,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}
Are less than or equal to a specified value

This is the associated JSON comparison expression: json-pointer le json-value.

Example
"_queryFilter" : '/employeeNumber le 5000'

The following REST call returns the user names of all managed users whose employeeNumber is 5000 or less:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=employeeNumber+le+5000&_fields=userName,employeeNumber'
{
  "result": [
    {
      "employeeNumber": 4907,
      "userName": "jnorris"
    },
    {
      "employeeNumber": 4905,
      "userName": "afrancis"
    },
    {
      "employeeNumber": 3095,
      "userName": "twhite"
    },
    {
      "employeeNumber": 3921,
      "userName": "abasson"
    },
    {
      "employeeNumber": 2892,
      "userName": "dcarter"
    },
    ...
  ],
  "resultCount": 5000,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}
Are greater than a specified value

This is the associated JSON comparison expression: json-pointer gt json-value

Example
"_queryFilter" : '/employeeNumber gt 5000'

The following REST call returns the user names of all managed users whose employeeNumber is higher than 5000:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=employeeNumber+gt+5000&_fields=userName,employeeNumber'
{
  "result": [
    {
      "employeeNumber": 5003,
      "userName": "agilder"
    },
    {
      "employeeNumber": 5011,
      "userName": "bsmith"
    },
    {
      "employeeNumber": 5034,
      "userName": "bjensen"
    },
    {
      "employeeNumber": 5027,
      "userName": "cclarke"
    },
    {
      "employeeNumber": 5033,
      "userName": "scarter"
    },
    ...
  ],
  "resultCount": 1458,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}
Are greater than or equal to a specified value

This is the associated JSON comparison expression: json-pointer ge json-value.

Example
"_queryFilter" : '/employeeNumber ge 5000'

The following REST call returns the user names of all managed users whose employeeNumber is 5000 or greater:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=employeeNumber+ge+5000&_fields=userName,employeeNumber'
{
  "result": [
    {
      "employeeNumber": 5000,
      "userName": "agilder"
    },
    {
      "employeeNumber": 5011,
      "userName": "bsmith"
    },
    {
      "employeeNumber": 5034,
      "userName": "bjensen"
    },
    {
      "employeeNumber": 5027,
      "userName": "cclarke"
    },
    {
      "employeeNumber": 5033,
      "userName": "scarter"
    },
    ...
  ],
  "resultCount": 1457,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}

Although specific system endpoints also support EndsWith and ContainsAllValues queries, such queries are not supported for managed objects and have not been tested with all supported ICF connectors.

Presence expressions

The following examples show how you can build filters using a presence expression, shown as pr. The presence expression is a filter that returns all records with a given attribute.

A presence expression filter evaluates to true when a json-pointer pr matches any object in which the json-pointer is present, and contains a non-null value. Consider the following expression:

"_queryFilter" : '/mail pr'

The following REST call uses that expression to return the mail addresses for all managed users with a mail property:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=mail+pr&_fields=mail'
{
  "result": [
    {
      "mail": "jdoe@exampleAD.com"
    },
    {
      "mail": "bjensen@example.com"
    }
  ],
  "resultCount": 2,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}

Depending on the connector, you can apply the presence filter on system objects. The following query returns the email address of all users in a CSV file who have the email attribute in their entries:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/system/csvfile/account?_queryFilter=email+pr&_fields=email'
{
  "result": [
    {
      "_id": "bjensen",
      "email": "bjensen@example.com"
    },
    {
      "_id": "scarter",
      "email": "scarter@example.com"
    }
  ],
  "resultCount": 2,
  "pagedResultsCookie": "MA%3D%3D",
  "totalPagedResultsPolicy": "NONE",
  "totalPagedResults": -1,
  "remainingPagedResults": -1
}

Not all connectors support the presence filter. In most cases, you can replicate the behavior of the presence filter with an "equals" (eq) query such as _queryFilter=email+eq"*"

Literal expressions

A literal expression is a boolean:

  • true matches any object in the resource.

  • false matches no object in the resource.

For example, you can list the _id of all managed objects as follows:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=true&_fields=_id'
{
  "result": [
    {
      "_id": "d2e29d5f-0d74-4d04-bcfe-b1daf508ad7c"
    },
    {
      "_id": "709fed03-897b-4ff0-8a59-6faaa34e3af6"
    }
  ],
  "resultCount": 2,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}

In expression clause

IDM provides limited support for the in expression clause. You can use this clause for queries on singleton string properties or arrays. The in query expression is not supported through the admin UI or for use by delegated administrators.

The in operator is shorthand for multiple OR conditions.

The following example command includes escaped characters. For readability, the non-escaped URL syntax is:

http://localhost:8080/openidm/managed/user?_pageSize=1000&_fields=userName&_queryFilter=/userName in '["user4a","user3a"]'
curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user?_pageSize=1000&_fields=userName&_queryFilter=userName%20in%20'%5B%22user4a%22%2C%22user3a%22%5D'"
{
  "result": [
    {
      "_id": "e32f9a3d-0039-4cb0-82d7-347cb808672e",
      "_rev": "000000000ae18357",
      "userName": "user3a"
    },
    {
      "_id": "120625c5-cfe7-48e7-b66a-6a0a0f9d2901",
      "_rev": "000000005ad98467",
      "userName": "user4a"
    }
  ],
  "resultCount": 2,
  "pagedResultsCookie": null,
  "totalPagedResultsPolicy": "NONE",
  "totalPagedResults": -1,
  "remainingPagedResults": -1
}

Filter expanded relationships

You can use _queryFilter to directly filter expanded relationships from a collection, such as authzRoles. The following example queries the manager-int authorization role of a user:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user/b70293db-8743-45a7-9215-1ca8fd8a0073/authzRoles?_queryFilter=name+eq+'manager-int'&_fields=*"
{
  "result": [
    {
      "_id": "b1d78144-7029-4135-8e73-85efe0a40b6b",
      "_rev": "00000000d4b8ab97",
      "_ref": "internal/role/c0a38233-c0f2-477d-8f18-f5485b7d002f",
      "_refResourceCollection": "internal/role",
      "_refResourceId": "c0a38233-c0f2-477d-8f18-f5485b7d002f",
      "_refProperties": {
        "_grantType": "",
        "_id": "b1d78144-7029-4135-8e73-85efe0a40b6b",
        "_rev": "00000000d4b8ab97"
      },
      "name": "manager-int",
      "description": "manager-int-desc",
      "temporalConstraints": null,
      "condition": null,
      "privileges": null
    }
  ],
  "resultCount": 1,
  "pagedResultsCookie": null,
  "totalPagedResultsPolicy": "NONE",
  "totalPagedResults": -1,
  "remainingPagedResults": -1
}

You can use _queryFilter on fields within _refProperties when using DS as your repository. This functionality is not available if you are using a JDBC repository.

Complex expressions

You can combine expressions using the boolean operators and, or, and ! (not). The following example queries managed user objects located in London, with last name Jensen:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user/?_queryFilter=city+eq+"London"and+sn+eq"Jensen"&_fields=userName,givenName,sn'
{
  "result": [
    {
      "sn": "Jensen",
      "givenName": "Clive",
      "userName": "cjensen"
    },
    {
      "sn": "Jensen",
      "givenName": "Dave",
      "userName": "djensen"
    },
    {
      "sn": "Jensen",
      "givenName": "Margaret",
      "userName": "mjensen"
    }
  ],
  "resultCount": 3,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}

Filter objects in arrays

Use query grouping to perform your query on properties within an array. For example, to query effectiveRoles for users who have the testManagedRole, check the _refResourceId inside the effectiveRoles array:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user/?_queryFilter=/effectiveRoles\[/_refResourceId+eq+"testManagedRole"]&_fields=userName,givenName,sn,effectiveRoles'
{
  "result": [
    {
      "_id": "917bc052-ef39-4add-ae05-0a278e2de9c0",
      "_rev": "200bc5d6-7cc1-4648-a854-3137f3d9c103-1565",
      "userName": "scarter",
      "sn": "Carter",
      "givenName": "Steven",
      "effectiveRoles": [
        {
          "_refResourceCollection": "managed/role",
          "_refResourceId": "testManagedRole",
          "_ref": "managed/role/testManagedRole"
        }
      ]
    },
    {
      "_id": "aca0042c-9f4c-4ad5-8cf7-aca0adeb3470",
      "_rev": "200bc5d6-7cc1-4648-a854-3137f3d9c103-1545",
      "userName": "jdoe",
      "sn": "Doe",
      "givenName": "John",
      "effectiveRoles": [
        {
          "_refResourceCollection": "managed/role",
          "_refResourceId": "testManagedRole",
          "_ref": "managed/role/testManagedRole"
        }
      ]
    }
  ],
  "resultCount": 2,
  "pagedResultsCookie": null,
  "totalPagedResultsPolicy": "NONE",
  "totalPagedResults": -1,
  "remainingPagedResults": -1
}

Because curl uses brackets ([], {}) for processing, you need to escape your brackets with a \. This may be unnecessary in cases where you are using a different method to call IDM.

This syntax is only available when using DS or PostgreSQL as your repository.

When using a PostgreSQL repository and querying an array, properties that are a string, boolean, number, or object are supported. However, arrays are not supported (you can’t filter on an array within an array).

Page query results

The common filter query mechanism supports paged query results for managed objects, and for some system objects, depending on the system resource. There are two ways to page objects in a query:

  • Using a cookie based on the value of a specified sort key.

  • Using an offset that specifies how many records should be skipped before the first result is returned.

These methods are implemented with the following query parameters:

_pagedResultsCookie

Opaque cookie used by the server to keep track of the position in the search results. The format of the cookie is a base-64 encoded version of the value of the unique sort key property. The value of the returned cookie is URL-encoded to prevent values such as + from being incorrectly translated.

You cannot page results without sorting them (using the _sortKeys parameter). If you do not specify a sort key, the _id of the record is used as the default sort key. At least one of the specified sort key properties must be a unique value property, such as _id.

For paged searches on generic mappings, you should sort on the _id property, because this is the only property that is stored outside of the JSON blob. If you sort on something other than _id, the search will incur a performance hit because IDM effectively has to pull the entire result set, and then sort it.

The server provides the cookie value on the first request. You should then supply the cookie value in subsequent requests until the server returns a null cookie, meaning that the final page of results has been returned.

The _pagedResultsCookie parameter is supported only for filtered queries, that is, when used with the _queryFilter parameter. You cannot use the _pagedResultsCookie with a _queryId.

The _pagedResultsCookie and _pagedResultsOffset parameters are mutually exclusive, and cannot be used together.

Paged results are enabled only if the _pageSize is a non-zero integer.

_pagedResultsOffset

Specifies the index within the result set of the number of records to be skipped before the first result is returned. The format of the _pagedResultsOffset is an integer value. When the value of _pagedResultsOffset is greater than or equal to 1, the server returns pages, starting after the specified index.

This request assumes that the _pageSize is set, and not equal to zero.

For example, if the result set includes 10 records, the _pageSize is 2, and the _pagedResultsOffset is 6, the server skips the first 6 records, then returns 2 records, 7 and 8. The _remainingPagedResults value would be 2, the last two records (9 and 10) that have not yet been returned.

If the offset points to a page beyond the last of the search results, the result set returned is empty.

_pageSize

An optional parameter indicating that query results should be returned in pages of the specified size. For all paged result requests other than the initial request, a cookie should be provided with the query request.

The default behavior is not to return paged query results. If set, this parameter should be an integer value, greater than zero.

When a _pageSize is specified, and non-zero, the server calculates the totalPagedResults, in accordance with the totalPagedResultsPolicy, and provides the value as part of the response. If a count policy is specified (_totalPagedResultsPolicy=EXACT, The totalPagedResults returns the total result count. If no count policy is specified in the query, or if _totalPagedResultsPolicy=NONE, result counting is disabled, and the server returns a value of -1 for totalPagedResults. The following example shows a query that requests two results with a totalPagedResultsPolicy of EXACT:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/managed/user?_queryFilter=true&_pageSize=2&_totalPagedResultsPolicy=EXACT"
{
  "result": [
    {
      "_id": "adonnelly",
      "_rev": "0",
      "userName": "adonnelly",
      "givenName": "Abigail",
      "sn": "Donnelly",
      "telephoneNumber": "12345678",
      "active": "true",
      "mail": "adonnelly@example.com",
      "accountStatus": "active",
      "effectiveRoles": [],
      "effectiveAssignments": []
    },
    {
      "_id": "bjensen",
      "_rev": "0",
      "userName": "bjensen",
      "givenName": "Babs",
      "sn": "Jensen",
      "telephoneNumber": "12345678",
      "active": "true",
      "mail": "bjensen@example.com",
      "accountStatus": "active",
      "effectiveRoles": [],
      "effectiveAssignments": []
    }
  ],
  "resultCount": 2,
  "pagedResultsCookie": "eyIvX2lkIjoiYm11cnJheSJ9",
  "totalPagedResultsPolicy": "EXACT",
  "totalPagedResults": 22,
  "remainingPagedResults": -1
}

The totalPagedResults and _remainingPagedResults parameters are not supported for all queries. Where they are not supported, their returned value is always -1. In addition, counting query results using these parameters is not currently supported for a ForgeRock Directory Services (DS) repository.

Requesting the total result count (with _totalPagedResultsPolicy=EXACT) incurs a performance cost on the query.

Queries that return large data sets will have a significant impact on heap requirements, particularly if they are run in parallel with other large data requests. To avoid out of memory errors, analyze your data requirements, set the heap configuration appropriately, and modify access controls to restrict requests on large data sets.

Sort query results

For common filter query expressions, you can sort the results of a query using the _sortKeys parameter. This parameter takes a comma-separated list as a value and orders the way in which the JSON result is returned, based on this list.

The _sortKeys parameter is not supported for predefined queries.

When using DS as a repo:

  • Pagination using _pageSize is recommended if you intend to use _sortKeys. If you do not paginate your query, the data you are querying must be indexed in DS.

  • When viewing data that is persisted in DS and sorted by un-indexed _sortKeys, the _pageSize parameter must be less than or equal to the index-entry-limit as configured in DS (default value is 4000).

For more information about how to set up indexes in DS, refer to Indexes in the DS Configuration Guide.

The following query returns all users with the givenName Dan, and sorts the results alphabetically, according to surname (sn):

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/system/ldap/account?_queryFilter=givenName+eq+"Dan"&_fields=givenName,sn&_sortKeys=sn'
{
  "result": [
    {
      "sn": "Cope",
      "givenName": "Dan"
    },
    {
      "sn": "Langdon",
      "givenName": "Dan"
    },
    {
      "sn": "Lanoway",
      "givenName": "Dan"
    }
  ],
  "resultCount": 3,
  "pagedResultsCookie": null,
  "remainingPagedResults": -1
}

When you query a relationship field, fields that belong to the related object are not available as _sortKeys. For example, if you query a list of a manager’s reports, you cannot sort by the reports' last names. This is because the available _sortKeys are based on the object being queried, which, in the case of relationships, is actually a list of references to other objects, not the objects themselves.

Recalculate virtual property values in queries

For managed objects IDM includes an onRetrieve script hook that enables you to recalculate property values when an object is retrieved as the result of a query. To use the onRetrieve trigger, the query must include the executeOnRetrieve parameter, for example:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
'http://localhost:8080/openidm/managed/user?_queryFilter=sn+eq+"Jensen"&executeOnRetrieve=true'

If a query includes executeOnRetrieve, the query recalculates virtual property values, based on the current state of the system. The result of the query will be the same as a read on a specific object, because reads always recalculate virtual property values.

If a query does not include executeOnRetrieve, the query returns the virtual properties of an object, based on the value that is persisted in the repository. Virtual property values are not recalculated.

For performance reasons, executeOnRetrieve is false by default.

Virtual properties that use queryConfig for calculation instead of an onRetrieve script are not recalculated by executeOnRetrieve. These properties are recalculated only when there is a change (such as adding or removing a role affecting effectiveRoles, or a temporal constraint being triggered or changed).

Upload files to the server

IDM provides a generic file upload service that lets you upload and save files either to the filesystem or to the repository. The service uses the multipart/form-data Content-Type to accept file content, store it, and return that content when it is called over the REST interface.

To configure the file upload service, add one or more file-description.json files to your project’s conf directory, where description provides an indication of the purpose of the upload service. For example, you might create a file-images.json configuration file to handle uploading image files. Each file upload configuration file sets up a separate instance of the upload service. The description in the filename also specifies the endpoint at which the file service will be accessible over REST. In the previous example, file-images.json, the service would be accessible at the endpoint openidm/file/images.

A sample file upload service configuration file is available in the /path/to/openidm/samples/example-configurations/conf directory. The configuration is as follows:

{
    "enabled" : true,
    "fileHandler" : {
        "type" : file handler type,
        "root" : directory
    }
}

The service supports two file handlersfile and repo. The file handlers are configured as follows:

  • "type" : "file" specifies that the uploaded content will be stored in the filesystem. If you use the file type, you must specify a root property to indicate the directory (relative to the IDM installation directory) in which uploaded content is stored. In the following example, uploaded content is stored in the /path/to/openidm/images directory:

    {
        "enabled" : true,
        "fileHandler" : {
            "type" : "file",
            "root" : "images"
        }
    }

    You cannot use the file upload service to access any files outside the configured root directory.

    If root is configured to be an empty string, do not grant access to the file upload service to end users. When type is configured as file, ensure that root is configured to be a directory.
  • "type" : "repo" specifies that the uploaded content will be stored in the repository. The root property does not apply to the repository file handler so the configuration is as follows:

    {
        "enabled" : true,
        "fileHandler" : {
            "type" : "repo"
        }
    }

The file upload service performs a multi-part CREATE operation. Each upload request includes two --form options. The first option indicates that the uploaded file content will be converted to a base 64-encoded string and inserted into the JSON object as a field named content with the following structure:

{
    "content" : {
        "$ref" : "cid:filename#content"
    }
}

The second --form option specifies the file to be uploaded, and the file type. The request loads the entire file into memory, so file size will be constrained by available memory.

You can upload any mime type using this service; however, you must specify a safelist of mime types that can be retrieved over REST. If you specify a mime type that is not in the safelist during retrieval of the file, the response content defaults to application/json. To configure the list of supported mime types, specify a comma-separated list as the value of the org.forgerock.json.resource.http.safemimetypes property in the conf/system.properties file. For example:

org.forgerock.json.resource.http.safemimetypes=application/json,application/pkix-cert,application/x-pem-file

You can only select from the following list:

  • image/*

  • text/plain

  • text/css

  • text/csv

  • application/json

  • application/pkix-cert

  • application/x-pem-file

The following request uploads an image (PNG) file named test.png to the filesystem. The file handler configuration file provides the REST endpoint. In this case openidm/file/images references the configuration in the file-images.json file:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--form 'json={"content" : {"$ref" : "cid:test#content"}};type=application/json' \
--form 'test=@test.png;type=image/png' \
--request PUT \
"http://localhost:8080/openidm/file/images/test.png"
{
  "_id": "test.png",
  "content": "aW1hZ2UvcG5n"
}

Note that the resource ID is derived directly from the upload filename — system-generated IDs are not supported.

The following request uploads a stylesheet (css) file named test.css to the same location on the filesystem as the previous request:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--form 'json={"content" : {"$ref" : "cid:test#content"}};type=application/json' \
--form '@test.css;type=text/css' \
--request PUT \
"http://localhost:8080/openidm/file/images/test.css"
{
  "_id": "test.css",
  "content": "aW1hZ2UvY3N2"
}

Files uploaded to the repository are stored as JSON objects in the openidm.files table. The following request uploads the same image (PNG) file (test.png) to the repository:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--form 'json={"content" : {"$ref" : "cid:test#content"}};type=application/json' \
--form 'test=@test.png;type=image/png' \
--request PUT \
"http://localhost:8080/openidm/file/repo/test.png"
{
  "_id": "test.png",
  "_rev": "00000000970b4454",
  "content": "aW1hZ2UvcG5n"
}

Note that the preceding example assumes the following file upload service configuration (in file-repo.json:

{
    "enabled" : true,
    "fileHandler" : {
        "type" : "repo"
    }
}

The file type is not stored with the file. By default, a READ on uploaded file content returns the content as a base 64-encoded string within the JSON object. For example:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/file/repo/test.png"
{
  "_id": "test.png",
  "_rev": "00000000970b4454",
  "content": "aW1hZ2UvcG5n"
}

Your client can retrieve the file in the correct format by specifying the content and mimeType parameters in the read request. For example:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request GET \
"http://localhost:8080/openidm/file/repo/test.css?_fields=content&_mimeType=text/css"

To delete uploaded content, send a DELETE request as follows:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--request DELETE \
"http://localhost:8080/openidm/file/repo/test.png"
{
  "_id": "test.png",
  "_rev": "00000000970b4454",
  "content": "aW1hZ2UvcG5n"
}

Appendix A: Data models and objects reference

You can customize a variety of objects that can be addressed via a URL or URI. IDM can perform a common set of functions on these objects, such as CRUDPAQ (create, read, update, delete, patch, action, and query).

Depending on how you intend to use them, different object types are appropriate.

Object Types
Object Type Intended Use Special Functionality

Managed objects

Serve as targets and sources for synchronization, and to build virtual identities.

Provide appropriate auditing, script hooks, declarative mappings and so forth in addition to the REST interface.

Configuration objects

Ideal for look-up tables or other custom configuration, which can be configured externally like any other system configuration.

Adds file view, REST interface, and so forth

Repository objects

The equivalent of arbitrary database table access. Appropriate for managing data purely through the underlying data store or repository API.

Persistence and API access

System objects

Representation of target resource objects, such as accounts, but also resource objects such as groups.

 

Audit objects

Houses audit data in the repository.

 

Links

Defines a relation between two objects.

 

Managed objects reference

A managed object is an object that represents the identity-related data managed by IDM. Managed objects are stored in the IDM repository. All managed objects are JSON-based data structures.

Managed object schema

IDM provides a default schema for typical managed object types, such as users and roles, but does not control the structure of objects that you store in the repository. You can modify or extend the schema for the default object types, and you can set up a new managed object type for any item that can be collected in a data set.

The _rev property of a managed object is reserved for internal use, and is not explicitly part of its schema. This property specifies the revision of the object in the repository. This is the same value that is exposed as the object’s ETag through the REST API. The content of this attribute is not defined. No consumer should make any assumptions of its content beyond equivalence comparison. This attribute may be provided by the underlying data store.

Schema validation is performed by the policy service and can be configured according to the requirements of your deployment.

Properties can be defined to be strictly derived from other properties within the object. This allows computed and composite values to be created in the object. Such properties are named virtual properties. The value of a virtual property is computed only when that property is retrieved.

Data consistency

Single-object operations are consistent within the scope of the operation performed, limited by the capabilities of the underlying data store. Bulk operations have no consistency guarantees. IDM does not expose any transactional semantics in the managed object access API.

For information on conditional header access through the REST API, refer to Conditional Operations.

Managed object triggers

Triggers are user-definable functions that validate or modify object or property state.

State triggers

Managed objects are resource-oriented. A set of triggers is defined to intercept the supported request methods on managed objects. Such triggers are intended to perform authorization, redact, or modify objects before the action is performed. The object being operated on is in scope for each trigger, meaning that the object is retrieved by the data store before the trigger is fired.

If retrieval of the object fails, the failure occurs before any trigger is called. Triggers are executed before any optimistic concurrency mechanisms are invoked. The reason for this is to prevent a potential attacker from getting information about an object (including its presence in the data store) before authorization is applied.

onCreate

Called upon a request to create a new object. Throwing an exception causes the create to fail.

postCreate

Called after the creation of a new object is complete.

onRead

Called upon a request to retrieve a whole object or portion of an object. Throwing an exception causes the object to not be included in the result. This method is also called when lists of objects are retrieved via requests to its container object; in this case, only the requested properties are included in the object. Allows for uniform access control for retrieval of objects, regardless of the method in which they were requested.

onUpdate

Called upon a request to store an object. The oldObject and newObject variables are in-scope for the trigger. The oldObject represents a complete object, as retrieved from the data store. The trigger can elect to change newObject properties. If, as a result of the trigger, the values of the oldObject and newObject are identical (that is, update is reverted), the update ends prematurely, but successfully. Throwing an exception causes the update to fail.

postUpdate

Called after an update request is complete.

onDelete

Called upon a request to delete an object. Throwing an exception causes the deletion to fail.

postDelete

Called after an object is deleted.

onSync

Called when a managed object is changed, and the change triggers an implicit synchronization operation. The implicit synchronization operation is triggered by calling the sync service, which attempts to go through all the configured managed-system mappings. The sync service returns either a response or an error. For both the response and the error, the script that is referenced by the onSync hook is called.

You can use this hook to inject business logic when the sync service either fails or succeeds to synchronize all applicable mappings. For an example of how the onSync hook is used to revert partial successful synchronization operations, refer to Synchronization Failure Compensation.

Object storage triggers

An object-scoped trigger applies to an entire object. Unless otherwise specified, the object itself is in scope for the trigger.

onValidate

Validates an object prior to its storage in the data store. If an exception is thrown, the validation fails and the object is not stored.

onStore

Called just prior to when an object is stored in the data store. Typically used to transform an object just prior to its storage (for example, encryption).

Property storage triggers

A property-scoped trigger applies to a specific property within an object. Only the property itself is in scope for the trigger. No other properties in the object should be accessed during execution of the trigger. Unless otherwise specified, the order of execution of property-scoped triggers is intentionally left undefined.

onValidate

Validates a given property value after its retrieval from and prior to its storage in the data store. If an exception is thrown, the validation fails and the property is not stored.

onRetrieve

Called on all requests that return a single object: read, create, update, patch, and delete.

onRetrieve is called on queries only if executeOnRetrieve is set to true in the query request parameters. If executeOnRetrieve is not passed, or if it is false, the query returns previously persisted values of the requested fields. This behavior avoids performance problems when executing the script on all results of a query.

onStore

Called before an object is stored in the data store. Typically used to transform a given property before its object is stored.

Storage trigger sequences

Triggers are executed in the following order:

Object Retrieval Sequence
  1. Retrieve the raw object from the data store

  2. The executeOnRetrieve boolean is used to check whether property values should be recalculated. The sequence continues if the boolean is set to true.

  3. Call object onRetrieve trigger

  4. Per-property within the object, call property onRetrieve trigger

Object Storage Sequence
  1. Per-property within the object:

    • Call property onValidate trigger

    • Call object onValidate trigger

  2. Per-property trigger within the object:

    • Call property onStore trigger

    • Call object onStore trigger

    • Store the object with any resulting changes to the data store

Managed object encryption

Sensitive object properties can be encrypted prior to storage, typically through the property onStore trigger. The trigger has access to configuration data, which can include arbitrary attributes that you define, such as a symmetric encryption key. Such attributes can be decrypted during retrieval from the data store through the property onRetrieve trigger.

Managed object configuration

Configuration of managed objects is provided through an array of managed object configuration objects.

{
  "objects": [ managed-object-config object, ... ]
}
objects

array of managed-object-config objects, required

Specifies the objects that the managed object service manages.

Managed-object-config object properties

Specifies the configuration of each managed object.

{
  "name"      : string,
  "actions"   : script object,
  "onCreate"  : script object,
  "onDelete"  : script object,
  "onRead"    : script object,
  "onRetrieve": script object,
  "onStore"   : script object,
  "onSync"    : script object,
  "onUpdate"  : script object,
  "onValidate": script object,
  "postCreate": script object,
  "postDelete": script object,
  "postUpdate": script object,
  "schema"    : {
    "id"        : urn,
    "icon"      : string,
    "mat-icon"  : string,
    "order"     : [ list of properties],
    "properties": { property-configuration objects },
    "$schema"   : "http://json-schema.org/draft-03/schema",
    "title"     : "User",
    "viewable"  : true
  }
}
name

string, required

The name of the managed object. Used to identify the managed object in URIs and identifiers.

actions

script object, optional

A custom script that initiates an action on the managed object. For more information, refer to Register custom scripted actions.

onCreate

script object, optional

A script object to trigger when the creation of an object is being requested. The object to be created is provided in the root scope as an object property. The script can change the object. If an exception is thrown, the create aborts with an exception.

onDelete

script object, optional

A script object to trigger when the deletion of an object is being requested. The object being deleted is provided in the root scope as an object property. If an exception is thrown, the deletion aborts with an exception.

onRead

script object, optional

A script object to trigger when the read of an object is being requested. The object being read is provided in the root scope as an object property. The script can change the object. If an exception is thrown, the read aborts with an exception.

onRetrieve

script object, optional

A script object to trigger when an object is retrieved from the repository. The object that was retrieved is provided in the root scope as an object property. The script can change the object. If an exception is thrown, then object retrieval fails.

onStore

script object, optional

A script object to trigger when an object is about to be stored in the repository. The object to be stored is provided in the root scope as an object property. The script can change the object. If an exception is thrown, then object storage fails.

onSync

script object, optional

A script object to trigger when a change to a managed object triggers an implicit synchronization operation. The script has access to the syncResults object, the request object, the state of the object before the change (oldObject) and the state of the object after the change (newObject). The script can change the object.

onUpdate

script object, optional

A script object to trigger when an update to an object is requested. The old value of the object being updated is provided in the root scope as an oldObject property. The new value of the object being updated is provided in the root scope as a newObject property. The script can change the newObject. If an exception is thrown, the update aborts with an exception.

onValidate

script object, optional

A script object to trigger when the object requires validation. The object to be validated is provided in the root scope as an object property. If an exception is thrown, the validation fails.

postCreate

script object, optional

A script object to trigger after an object is created, but before any targets are synchronized.

postDelete

script object, optional

A script object to trigger after a delete of an object is complete, but before any further synchronization. The value of the deleted object is provided in the root scope as an oldObject property.

postUpdate

script object, optional

A script object to trigger after an update to an object is complete, but before any targets are synchronized. The value of the object before the update is provided in the root scope as an oldObject property. The value of the object after the update is provided in the root scope as a newObject property.

schema

json-schema object, optional

The schema to use to validate the structure and content of the managed object, and how the object is displayed in the UI. The schema-object format is defined by the JSON Schema specification.

The schema property includes the following additional elements:

icon

string, optional

The name of the Font Awesome icon to display for this object in the UI. Only applies to standalone IDM.

mat-icon

string, optional

The name of the Material Design Icon to display for this object in the UI. Only applies to IDM as part of the ForgeRock Identity Platform.

id

urn, optional

The URN of the managed object, for example, urn:jsonschema:org:forgerock:openidm:managed:api:Role.

order

list of properties, optional

The order in which properties of this managed object are displayed in the UI.

properties

list of property configuration objects, optional

A list of property specifications. For more information, refer to Property Configuration Properties.

$schema

url, optional

Link to the JSON schema specification.

title

string, optional

The title of this managed object in the UI.

viewable

boolean, optional

Whether this object is visible in the UI.

Property configuration properties

Each managed object property, identified by its property-name, can have the following configurable properties:

"property-name" : {
  "description"     : string,
  "encryption"      : property-encryption object,
  "isPersonal"      : boolean true/false,
  "isProtected"     : boolean true/false,
  "isVirtual"       : boolean true/false,
  "items"           : {
     "id"                  : urn,
     "properties"          : property-config object,
     "resourceCollection"  : property-config object,
     "reversePropertyName" : string,
     "reverseRelationship" : boolean true/false,
     "title"               : string,
     "type"                : string,
     "validate"            : boolean true/false,
  },
  "onRetrieve"      : script object,
  "onStore"         : script object,
  "onValidate"      : script object,
  "pattern"         : string,
  "policies"        : policy object,
  "required"        : boolean true/false,
  "returnByDefault" : boolean true/false,
  "scope"           : string,
  "searchable"      : boolean true/false,
  "secureHash"      : property-hash object,
  "title"           : string,
  "type"            : data type,
  "usageDescription": string,
  "userEditable"    : boolean true/false,
  "viewable"        : boolean true/false,
}
description

string, optional

A brief description of the property.

encryption

property-encryption object, optional

Specifies the configuration for encryption of the property in the repository. If omitted or null, the property is not encrypted.

isPersonal

boolean, true/false

Designed to highlight personally identifying information. By default, isPersonal is set to true for userName and postalAddress.

isProtected

boolean, true/false

Specifies whether reauthentication is required if the value of this property changes.

isVirtual

boolean, true/false

Specifies whether the property takes a static value, or whether its value is calculated dynamically as the result of a script.

The most recently calculated value of a virtual property is persisted by default. The persistence of virtual property values allows IDM to compare the new value of the property against the last calculated value, and therefore to detect change events during synchronization.

Virtual property values are not persisted by default if you are using an explicit mapping.

items

property-configuration object, optional

For array type properties, defines the elements in the array. items can include the following sub-properties:

id

urn, optional

The URN of the property, for example, urn:jsonschema:org:forgerock:openidm:managed:api:Role:members:items.

properties

property configuration object, optional

A list of properties, and their configuration, that make up this items array. For example, for a relationship type property:

"properties" : {
    "_ref" : {
        "description" : "References a relationship from a managed object",
        "type" : "string"
    },
    "_refProperties" : {
        "description" : "Supports metadata within the relationship",
        ...
    }
}
resourceCollection

property configuration object, optional

The collection of resources (objects) on which this relationship is based (for example, managed/user objects).

reversePropertyName

string, optional

For relationship type properties, specifies the corresponding property name in the case of a reverse relationship. For example, a roles property might have a reversePropertyName of members.

reverseRelationship

boolean, true or false.

For relationship type properties, specifies whether the relationship exists in both directions.

title

string, optional

The title of array items, as displayed in the UI, for example Role Members Items.

type

string, optional

The array type, for example relationship.

validate

boolean, true/false

For reverse relationships, specifies whether the relationship should be validated.

onRetrieve

script object, optional

A script object to trigger once a property is retrieved from the repository. That property may be one of two related variables: property and propertyName. The property that was retrieved is provided in the root scope as the propertyName variable; its value is provided as the property variable. If an exception is thrown, then object retrieval fails.

onStore

script object, optional

A script object to trigger when a property is about to be stored in the repository. That property may be one of two related variables: property and propertyName. The property that was retrieved is provided in the root scope as the propertyName variable; its value is provided as the property variable. If an exception is thrown, then object storage fails.

onValidate

script object, optional

A script object to trigger when the property requires validation. The value of the property to be validated is provided in the root scope as the property property. If an exception is thrown, validation fails.

pattern

string, optional

Any specific pattern to which the value of the property must adhere. For example, a property whose value is a date might require a specific date format. Patterns specified here must follow regular expression syntax.

policies

policy object, optional

Any policy validation that must be applied to the property.

required

boolean, true/false

Specifies whether the property must be supplied when an object of this type is created.

returnByDefault

boolean, true/false

For virtual properties, specifies whether the property will be returned in the results of a query on an object of this type if it is not explicitly requested. Virtual attributes are not returned by default.

scope

string, optional

Specifies whether the property should be filtered from HTTP/external calls. The value can be either "public" or "private". "private" indicates that the property should be filtered, "public" indicates no filtering. If no value is set, the property is assumed to be public and thus not filtered.

searchable

boolean, true/false

Specifies whether this property can be used in a search query on the managed object. A searchable property is visible in the End User UI. False by default.

secureHash

property-hash object, optional

Specifies the configuration for hashing of the property value in the repository. If omitted or null, the property is not hashed.

title

string, required

A human-readable string, used to display the property in the UI.

type

data type, required

The data type for the property value; can be String, Array, Boolean, Number, Object, or Resource Collection.

usageDescription

string, optional

Designed to help end users understand the sensitivity of a property such as a telephone number.

userEditable

boolean, true/false

Specifies whether users can edit the property value in the UI. This property applies in the context of the End User UI, in which users are able to edit certain properties of their own accounts. False by default.

viewable

boolean, true/false

Specifies whether this property is viewable in the object’s profile in the UI. True by default.

Script Object Properties

{
  "type"  : "text/javascript",
  "source": string
}
type

string, required

IDM supports "text/javascript" and "groovy".

source, file

string, required (only one, source or file is required)

Specifies the source code of the script to be executed (if the keyword is "source"), or a pointer to the file that contains the script (if the keyword is "file").

Property Encryption Object

{
  "cipher": string,
  "key"   : string
}
cipher

string, optional

The cipher transformation used to encrypt the property. If omitted or null, the default cipher of "AES/CBC/PKCS5Padding" is used.

key

string, required

The alias of the key in the IDM cryptography service keystore used to encrypt the property.

Property Hash Object

{
    "algorithm" : string,
    "type" : string
}
algorithm

string, required

The algorithm that should be used to hash the value.

For a list of supported hash algorithms, refer to Salted Hash Algorithms.

type

string, optional

The type of hashing. Currently only salted hash is supported. If this property is omitted or null, the default "salted-hash" is used.

Custom managed objects

Managed objects are inherently fully user definable and customizable. Like all objects, managed objects can maintain relationships to each other in the form of links. Managed objects are intended for use as targets and sources for synchronization operations to represent domain objects, and to build up virtual identities. The name managed objects comes from the intention that IDM stores and manages these objects, as opposed to system objects that are present in external systems.

IDM can synchronize and map directly between external systems (system objects), without storing intermediate managed objects. Managed objects are appropriate, however, as a way to cache the data—for example, when mapping to multiple target systems, or when decoupling the availability of systems—to more fully report and audit on all object changes during reconciliation, and to build up views that are different from the original source, such as transformed and combined or virtual views. Managed objects can also be allowed to act as an authoritative source if no other appropriate source is available.

Other object types exist for other settings that should be available to a script, such as configuration or look-up tables that do not need audit logging.

Set up a managed object type

To set up a managed object, you define the object in your project’s managed object configuration. This simple example adds a foobar object declaration after the user object type:

{
    "objects": [
        {
            "name": "user"
        },
        {
            "name": "foobar"
        }
    ]
}

Manipulate managed objects declaratively

By mapping an object to another object, either an external system object or another internal managed object, you automatically tie the object life cycle and property settings to the other object. For more information, refer to Resource mapping.

Manipulate managed objects programmatically

You can address managed objects as resources using URLs or URIs with the managed/ prefix. This works whether you address the managed object internally as a script running in IDM or externally through the REST interface.

You can use all resource API functions in script objects for create, read, update, delete operations, and also for arbitrary queries on the object set, but not currently for arbitrary actions. For more information, refer to Scripting.

IDM supports concurrency through a multi version concurrency control (MVCC) mechanism. Each time an object changes, IDM assigns it a new revision.

Objects can be arbitrarily complex as long as they use supported types, such as maps, lists, numbers, strings, and booleans as defined in JSON.

Create objects

The following script example creates an object type.

openidm.create("managed/foobar", "myidentifier", mymap)
Update objects

The following script example updates an object type.

var expectedRev = origMap._rev
openidm.update("managed/foobar/myidentifier", expectedRev, mymap)

The MVCC mechanism requires that expectedRev be set to the expected revision of the object to update. You obtain the revision from the object’s _rev property. If something else changes the object concurrently, IDM rejects the update, and you must either retry or inspect the concurrent modification.

Patch objects

You can partially update a managed or system object using the patch method, which changes only the specified properties of the object.

The following script example updates an object type.

openidm.patch("managed/foobar/myidentifier", rev, value)

The patch method supports a revision of "null", which effectively disables the MVCC mechanism, that is, changes are applied, regardless of revision. In the REST interface, this matches the If-Match: "*" condition supported by patch. Alternatively, you can omit the "If-Match: *" header.

For managed objects, the API supports patch by query, so the caller does not need to know the identifier of the object to change.

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--header "Content-Type: application/json" \
--request POST \
--data '[
  {
    "operation": "replace",
    "field": "/password",
    "value": "Passw0rd"
  }
]' \
"http://localhost:8080/openidm/managed/user?_action=patch&_queryFilter=userName+eq+'DDOE'"
Delete objects

The following script example deletes an object type.

var expectedRev = origMap._rev
openidm.delete("managed/foobar/myidentifier", expectedRev)

The MVCC mechanism requires that expectedRev be set to the expected revision of the object to update. You obtain the revision from the object’s _rev property. If something else changes the object concurrently, IDM rejects deletion, and you must either retry or inspect the concurrent modification.

Read objects

The following script example reads an object type.

val = openidm.read("managed/foobar/myidentifier")
Query object sets

You can query managed objects using common query filter syntax. The following script example queries managed user objects whose userName is smith.

var qry = {
    "_queryFilter" : "/userName eq \"smith\""
};
val = openidm.query("managed/user", qry);

For more information, refer to Define and call data queries.

Access managed objects through the REST API

IDM exposes all managed object functionality through the REST API unless you configure a policy to prevent such access. In addition to the common REST functionality of create, read, update, delete, patch, and query, the REST API also supports patch by query. For more information, refer to the REST API reference.

IDM requires authentication to access the REST API. The authentication configuration is specified in your project’s conf/authentication.json file. The default authorization filter script is openidm/bin/defaults/script/router-authz.js. For more information, refer to Authorization and roles.

Configuration objects

IDM provides an extensible configuration to allow you to leverage regular configuration mechanisms.

Unlike native the IDM configuration, which is interpreted automatically and can start new services, IDM stores custom configuration objects and makes them available to your code through the API.

For an introduction to the standard configuration objects, refer to Server configuration.

When To use custom configuration objects

Configuration objects are ideal for metadata and settings that need not be included in the data to reconcile. Use configuration objects for data that does not require audit logs, and does not serve directly as a target or source for mappings.

Although you can set and manipulate configuration objects programmatically and manually, configuration objects are expected to change slowly, through both manual file updates and programmatic updates. To store temporary values that can change frequently and that you do not expect to be updated by configuration file changes, custom repository objects might be more appropriate.

Custom configuration object naming conventions

By convention custom configuration objects are added under the reserved context, config/custom.

You can choose any name under config/context. Be sure, however, to choose a value for context that does not clash with future IDM configuration names.

Mapping configuration objects To configuration files

If you have not disabled the file-based view for configuration, you can view and edit all configuration including custom configuration in openidm/conf/*.json files. The configuration maps to a file named context-config-name.json, where context for custom configuration objects is custom by convention, and config-name is the configuration object name. A configuration object named escalation thus maps to a file named conf/custom-escalation.json.

IDM detects and automatically picks up changes to the file.

IDM also applies changes made through APIs to the file.

By default, IDM stores configuration objects in the repository. The file view is an added convenience aimed to help you in the development phase of your project.

Configuration objects file and REST payload formats

By default, IDM maps configuration objects to JSON representations.

IDM represents objects internally in plain, native types like maps, lists, strings, numbers, booleans, null. The object model is restricted to simple types so that mapping objects to external representations is easy.

The following example shows a representation of a configuration object with a look-up map.

{
    "CODE123" : "ALERT",
    "CODE889" : "IGNORE"
}

In the JSON representation, maps are represented with braces ({ }), and lists are represented with brackets ([ ]). Objects can be arbitrarily complex, as in the following example.

{
    "CODE123" : {
        "email" : ["sample@sample.com", "john.doe@somedomain.com"],
        "sms" : ["555666777"]
    }
    "CODE889" : "IGNORE"
}

Accessing configuration objects through the REST API

You can list all available configuration objects, including system and custom configurations, using an HTTP GET on /openidm/config.

The _id property in the configuration object provides the link to the configuration details with an HTTP GET on /openidm/config/id-value. By convention, the id-value for a custom configuration object called escalation is custom/escalation.

IDM supports REST mappings for create, read, update, delete, patch, and query of configuration objects.

Accessing configuration objects programmatically

You can address configuration objects as resources using the URL or URI config/ prefix both internally and also through the REST interface. The resource API provides script object functions for create, read, update, query, and delete operations.

IDM supports concurrency through a multi version concurrency control mechanism. Each time an object changes, IDM assigns it a new revision.

Objects can be arbitrarily complex as long as they use supported types, such as maps, lists, numbers, strings, and booleans.

Creating objects

The following script example creates an object type.

openidm.create("config/custom", "myconfig", mymap)

Updating objects

The following script example updates a custom configuration object type.

openidm.update("config/custom/myconfig", mymap)

Deleting objects

The following script example deletes a custom configuration object type.

openidm.delete("config/custom/myconfig")

Reading objects

The following script example reads an object type.

val = openidm.read("config/custom/myconfig")

System objects

System objects are pluggable representations of objects on external systems. They follow the same RESTful resource based design principles as managed objects. There is a default implementation for the ICF framework, which allows any connector object to be represented as a system object.

Audit objects

Audit objects contain audit data selected for local storage in repository.

Link objects define relations between source objects and target objects, usually relations between managed objects and system objects. The link relationship is established by provisioning activity that either results in a new account on a target system, or a reconciliation or synchronization scenario that takes a LINK action.