Chapter 10. Configuring Synchronization

Table of Contents
10.1. Types of Synchronization
10.2. Managing Reconciliation Over REST
10.3. Triggering LiveSync Over REST
10.4. Flexible Data Model
10.5. Basic Data Flow Configuration
10.6. Synchronization Situations and Actions
10.7. Asynchronous Reconciliation
10.8. Configuring Case Sensitivity for Data Stores
10.9. Reconciliation Optimization
10.10. Correlation Queries
10.11. Advanced Data Flow Configuration
10.12. Scheduling Synchronization

One of the core services of OpenIDM is synchronizing identity data from different resources. This chapter explains what you must know to get started configuring OpenIDM's flexible synchronization mechanism, and illustrates the concepts with examples.

10.1. Types of Synchronization

Synchronization happens either when OpenIDM receives a change directly, or when OpenIDM discovers a change on an external resource.

For direct changes to OpenIDM, OpenIDM immediately pushes updates to all external resources configured to receive the updates. A direct change can originate not only as a write request through the REST interface, but also as an update resulting from reconciliation with another resource.

OpenIDM discovers changes on external resources through reconciliation and through LiveSync.

Reconciliation

In identity management, reconciliation is the process of bidirectional synchronization of objects between different data stores. Reconciliation applies mainly to user objects, although OpenIDM can reconcile any objects, including groups and roles.

To perform reconciliation, OpenIDM analyzes both source and target systems to uncover the differences that it must reconcile. Reconciliation can therefore be a heavyweight process. When working with large data sets, finding all changes can be more work than processing the changes.

Reconciliation is, however, thorough. It recognizes system error conditions and catches changes that might be missed by the more lightweight LiveSync mechanism. Reconciliation therefore serves as the basis for compliance and reporting functionality.

LiveSync

LiveSync performs the same job as reconciliation. LiveSync relies on a change log on the external resource to determine which objects have changed.

LiveSync is intended to react quickly to changes as they happen. LiveSync is however a best effort mechanism that, in some cases, can miss changes.

Furthermore, not all resources provide the change log mechanism that LiveSync requires. The change log provides OpenIDM with a list of objects that have changed since the last request, so that OpenIDM does not need to scan all objects for changes. OpenDJ and Active Directory both provide the change log required for LiveSync.

To determine what to synchronize, and how to carry out synchronization, OpenIDM relies on mappings that you configure. LiveSync relies on the set of mappings that you configure once per OpenIDM server. Reconciliation allows you to configure specific mappings for a particular reconciliation job.

You must trigger OpenIDM to poll for changes on external resources, usually by scheduling reconciliation or LiveSync, as described in Scheduling Tasks and Events. Alternatively, you can manage reconciliation and LiveSync over the REST interface, as described in the following sections.

10.2. Managing Reconciliation Over REST

You can trigger, cancel, and monitor reconciliation operations over REST, using the REST endpoint http://localhost:8080/openidm/recon.

10.2.1. Triggering a Reconciliation Run

The following example triggers a reconciliation operation based on the systemLdapAccounts_managedUser mapping. The mapping is defined in the file conf/sync.json.

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request POST
 "http://localhost:8080/openidm/recon?_action=recon&mapping=systemLdapAccounts_managedUser"
    

By default, an assigned reconciliation run ID is returned immediately when the reconciliation operation is initiated. Clients can make subsequent calls to the reconciliation service, using this reconciliation run ID to query its state and to call operations on it.

For example, the reconciliation run initiated previously would return something similar to the following:

{"_id":"0890ad62-4738-4a3f-8b8e-f3c83bbf212e"}

To have the entire reconciliation run complete before the reconciliation run ID is returned, set the waitForCompletion property to true when the reconciliation is initiated. For example:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request POST
 "http://localhost:8080/openidm/recon?_action=recon&
 mapping=systemLdapAccounts_managedUser&waitForCompletion=true"
    

10.2.2. Canceling a Reconciliation Run

You can cancel a reconciliation run by sending a REST call with the cancel action, specifying the reconciliation run ID. For example, the following call cancels the reconciliation run initiated in the previous section:

$curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request POST
 "http://localhost:8080/openidm/recon/0890ad62-4738-4a3f-8b8e-f3c83bbf212e?_action=cancel"
    

The output for a reconciliation cancelation request is similar to the following:

    {"_id":"0890ad62-4738-4a3f-8b8e-f3c83bbf212e",
     "action":"cancel",
     "status":"SUCCESS"}
    

If you specified that the call should wait for completion before the ID is returned, you can obtain the reconciliation run ID from the list of active reconciliations, as described in the following section.

10.2.3. Listing Reconciliation Runs

You can display a list of reconciliation processes that have completed, and those that are in progress, by running a RESTful GET on "http://localhost:8080/openidm/recon". The following example displays all reconciliation runs.

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request GET
 "http://localhost:8080/openidm/recon"
    

The output of such a request is similar to the following, with one item for each reconciliation run.

{"reconciliations":[
 {"_id":"d3040cc9-ec2e-41b8-86c4-72393087a626",
 "mapping":"systemLdapAccounts_managedUser",
 "state":"SUCCESS",
 "stage":"COMPLETED_SUCCESS",
 "stageDescription":"reconciliation completed.",
 "progress":{
   "source":{
      "existing":{
         "processed":1001,
         "total":"1001"
      }
   },
   "target":{
      "existing":{
         "processed":1001,
         "total":"1001"
      },
      "created":0
   },
   "links":{
      "existing":{
         "processed":1001,
         "total":"1001"
      },
      "created":0
   }
 },
 "started":"2012-11-18T08:48:00.031Z",
 "ended":"2012-11-18T08:48:00.160Z"},
    

Each reconciliation run has the following properties:

_id

The ID of the reconciliation run.

mapping

The name of the mapping, defined in the conf/sync.json file.

state

The high level state of the reconciliation run. Values can be as follows:

  • ACTIVE

    The reconciliation run is in progress.

  • CANCELED

    The reconciliation run was successfully canceled.

  • FAILED

    The reconciliation run was terminated because of failure.

  • SUCCESS

    The reconciliation run completed successfully.

stage

The current stage of the reconciliation run's progress. Values can be as follows:

  • ACTIVE_INITIALIZED

    The initial stage, when a reconciliation run is first created.

  • ACTIVE_QUERY_ENTRIES

    Querying the source, target and possibly link sets to reconcile.

  • ACTIVE_RECONCILING_SOURCE

    Reconciling the set of IDs retrieved from the mapping source.

  • ACTIVE_RECONCILING_TARGET

    Reconciling any remaining entries from the set of IDs retrieved from the mapping target, that were not matched or processed during the source phase.

  • ACTIVE_LINK_CLEANUP

    Checking whether any links are now unused and should be cleaned up.

  • ACTIVE_PROCESSING_RESULTS

    Post-processing of reconciliation results.

  • ACTIVE_CANCELING

    Attempting to abort a reconciliation run in progress.

  • COMPLETED_SUCCESS

    Successfully completed processing the reconciliation run.

  • COMPLETED_CANCELED

    Completed processing because the reconciliation run was aborted.

  • COMPLETED_FAILED

    Completed processing because of a failure.

stageDescription

A description of the stages described previously.

progress

The progress object has the following structure (annotated here with comments):

"progress":{
      "source":{             // Progress on the set of existing entries in the mapping source
         "existing":{
            "processed":1001,
            "total":"1001"   // Total number of entries in source set, if known, “?” otherwise
         }
      },
      "target":{             // Progress on the set of existing entries in the mapping target
         "existing":{
            "processed":1001,
            "total":"1001"   // Total number of entries in target set, if known, “?” otherwise
         },
         "created":0         // New entries that were created
      },
      "links":{              // Progress on the set of existing links between source and target
         "existing":{
            "processed":1001,
            "total":"1001"   // Total number of existing links, if known, “?” otherwise
         },
         "created":0         // Denotes new links that were created
      }
},
       

10.2.4. Querying the Reconciliation Audit Log

Reconciliation operations are logged in the file /path/to/openidm/audit/recon.csv and in the repository. You can read and query the reconciliation audit logs over the REST interface as outlined in the following examples.

By default all audit/recon query responses are formatted based on the entryType of the entry. Fields that are not required for the specific entry type are stripped away from the response. For example, a summary entry would not need to include a null targetObjectId field, as this field would not make sense in the context of a summary. You can specify that this auto-formatting be disabled and return the full entry for all entry types. To disable entry formatting, include formatted=false as a query parameter in the request.

To return all reconciliation operations logged in the audit log, run a RESTful GET on the audit/recon endpoint. For example:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request GET
 "http://localhost:8080/openidm/audit/recon"

The following code sample shows an extract of the audit log after the first reconciliation operation in Sample 1.

{
  "entries": [
    {
      "rootActionId": "d0578abf-f38e-4ede-a7dc-5ee9eaa8ce53",
      "messageDetail": null,
      "message": "Reconciliation initiated by openidm-admin",
      "timestamp": "2013-05-08T07:58:33.296Z",
      "reconId": "5cf09dfa-e85c-4d52-ab6c-8ba7c2e3d34f",
      "entryType": "start",
      "_id": "11381e20-3679-469d-a71c-c557c2bd091e",
      "status": "SUCCESS",
      "exception": "",
      "mapping": "systemXmlfileAccounts_managedUser"
    },
    {
      "messageDetail": null,
      "rootActionId": "d0578abf-f38e-4ede-a7dc-5ee9eaa8ce53",
      "situation": "ABSENT",
      "actionId": "86995423-8a43-4fc7-9c3c-9e450e0234cb",
      "targetObjectId": "managed/user/scarter",
      "action": "CREATE",
      "entryType": "",
      "_id": "9f59bb8a-31c6-41af-8f27-02094391ba0c",
      "reconId": "5cf09dfa-e85c-4d52-ab6c-8ba7c2e3d34f",
      "status": "SUCCESS",
      "exception": "",
      "reconciling": "source",
      "ambiguousTargetObjectIds": "",
      "timestamp": "2013-05-08T07:58:33.791Z",
      "message": null,
      "sourceObjectId": "system/xmlfile/account/scarter"
    },
    {
      "messageDetail": null,
      "rootActionId": "d0578abf-f38e-4ede-a7dc-5ee9eaa8ce53",
      "situation": "ABSENT",
      "actionId": "dea9b5c5-7a75-4cab-b8e4-176bea0a94a6",
      "targetObjectId": "managed/user/bjensen",
      "action": "CREATE",
      "entryType": "",
      "_id": "4fd285ef-a409-4875-abd0-5d70965fe172",
      "reconId": "5cf09dfa-e85c-4d52-ab6c-8ba7c2e3d34f",
      "status": "SUCCESS",
      "exception": "",
      "reconciling": "source",
      "ambiguousTargetObjectIds": "",
      "timestamp": "2013-05-08T07:58:33.793Z",
      "message": null,
      "sourceObjectId": "system/xmlfile/account/bjensen"
      },
      {
        "rootActionId": "d0578abf-f38e-4ede-a7dc-5ee9eaa8ce53",
        "messageDetail": {
          "ended": "2013-05-08T07:58:33.813Z",
          "started": "2013-05-08T07:58:33.294Z",
          "situationSummary": {
            "SOURCE_MISSING": 0,
            "FOUND": 0,
            "SOURCE_IGNORED": 0,
            "UNQUALIFIED": 0,
            "UNASSIGNED": 0,
            "TARGET_IGNORED": 0,
            "CONFIRMED": 0,
            "AMBIGUOUS": 0,
            "ABSENT": 2,
            "MISSING": 0
          },
          "progress": {
            "links": {
              "created": 2,
              "existing": {
                "total": "0",
                "processed": 0
              }
            },
            "target": {
              "created": 2,
              "existing": {
                "total": "0",
                "processed": 0
              }
            },
            "source": {
              "existing": {
                "total": "2",
                "processed": 2
              }
            }
          },
          "stageDescription": "reconciling target entries",
          "stage": "ACTIVE_RECONCILING_TARGET",
          "state": "ACTIVE",
          "mapping": "systemXmlfileAccounts_managedUser"
        },
        "message": "SOURCE_IGNORED: 0 MISSING: 0 FOUND: 0 AMBIGUOUS: 0 UNQUALIFIED: 0
           CONFIRMED: 0 SOURCE_MISSING: 0 ABSENT: 2 TARGET_IGNORED: 0 UNASSIGNED: 0 ",
        "timestamp": "2013-05-08T07:58:33.813Z",
        "reconId": "5cf09dfa-e85c-4d52-ab6c-8ba7c2e3d34f",
        "entryType": "summary",
        "_id": "a8a81f9f-fa8f-49f4-a0d6-c88b5fc4be2a",
        "status": "SUCCESS",
        "exception": "",
        "mapping": "systemXmlfileAccounts_managedUser"
      }
    ]
}

Most of the fields in this audit log are self-explanatory. Each distinct reconciliation operation is identified by its reconId. Each entry in the log is identified by a unique _id. The first log entry indicates the status for the complete reconciliation operation. Successive entries indicate the status for each record affected by the reconciliation.

To obtain information on a specific audit log entry, include its entry _id in the endpoint. For example:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request GET
 "http://localhost:8080/openidm/audit/recon/9f59bb8a-31c6-41af-8f27-02094391ba0c"

The following sample output shows the results of a read operation on a specific reconciliation audit entry.

{
 "messageDetail": null,
 "rootActionId": "d0578abf-f38e-4ede-a7dc-5ee9eaa8ce53",
 "situation": "ABSENT",
 "actionId": "86995423-8a43-4fc7-9c3c-9e450e0234cb",
 "targetObjectId": "managed/user/scarter",
 "action": "CREATE",
 "entryType": "",
 "_id": "9f59bb8a-31c6-41af-8f27-02094391ba0c",
 "reconId": "5cf09dfa-e85c-4d52-ab6c-8ba7c2e3d34f",
 "status": "SUCCESS",
 "exception": "",
 "reconciling": "source",
 "ambiguousTargetObjectIds": "",
 "timestamp": "2013-05-08T07:58:33.791Z",
 "message": null,
 "sourceObjectId": "system/xmlfile/account/scarter"
}

To query the audit log for a particular reconciliation operation, use the audit-by-recon-id keyword, specifying the reconciliation ID, as follows:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request GET
 "http://localhost:8080/openidm/audit/recon/?_queryId=audit-by-recon-id&reconId=<reconID>"

Output similar to the following is returned, for the specified reconciliation operation:

{
  "start": {
    "rootActionId": "3c098d6f-a5e5-483c-a8ce-82911a10b0a9",
    "messageDetail": null,
    "message": "Reconciliation initiated by openidm-admin",
    "timestamp": "2013-05-14T08:20:41.343Z",
    "reconId": "1ef8e7b6-33dc-4f92-810a-b51913508a68",
    "entryType": "start",
    "_id": "ce58ef39-425d-4711-94b9-a9c77df23001",
    "status": "SUCCESS",
    "exception": "",
    "mapping": "systemXmlfileAccounts_managedUser"
  },
  "result": [
    {
      "messageDetail": null,
      "rootActionId": "3c098d6f-a5e5-483c-a8ce-82911a10b0a9",
      "situation": "ABSENT",
      "actionId": "1a391fe8-201b-4f59-ad05-92ee804488a8",
      "targetObjectId": "managed/user/scarter",
      "action": "CREATE",
      "entryType": "",
      "_id": "6dc0a18a-826d-487d-a29f-5cd8d2f55465",
      "reconId": "1ef8e7b6-33dc-4f92-810a-b51913508a68",
      "status": "SUCCESS",
      "exception": "",
      "reconciling": "source",
      "ambiguousTargetObjectIds": "",
      "timestamp": "2013-05-14T08:20:41.763Z",
      "message": null,
      "sourceObjectId": "system/xmlfile/account/scarter"
    },
    {
      "messageDetail": null,
      "rootActionId": "3c098d6f-a5e5-483c-a8ce-82911a10b0a9",
      "situation": "ABSENT",
      "actionId": "0aaba292-1dd3-4e98-a0e2-04bec9ae5209",
      "targetObjectId": "managed/user/bjensen",
      "action": "CREATE",
      "entryType": "",
      "_id": "1cda457e-54e2-451b-8a40-ef93dec7e60c",
      "reconId": "1ef8e7b6-33dc-4f92-810a-b51913508a68",
      "status": "SUCCESS",
      "exception": "",
      "reconciling": "source",
      "ambiguousTargetObjectIds": "",
      "timestamp": "2013-05-14T08:20:41.760Z",
      "message": null,
      "sourceObjectId": "system/xmlfile/account/bjensen"
    }
  ],
  "summary": {
    "rootActionId": "3c098d6f-a5e5-483c-a8ce-82911a10b0a9",
    "messageDetail": {
      "ended": "2013-05-14T08:20:41.783Z",
      "started": "2013-05-14T08:20:41.342Z",
      "situationSummary": {
        "SOURCE_MISSING": 0,
        "FOUND": 0,
        "SOURCE_IGNORED": 0,
        "UNQUALIFIED": 0,
        "UNASSIGNED": 0,
        "TARGET_IGNORED": 0,
        "CONFIRMED": 0,
        "AMBIGUOUS": 0,
        "ABSENT": 2,
        "MISSING": 0
      },
      "progress": {
        "links": {
          "created": 2,
          "existing": {
            "total": "0",
            "processed": 0
          }
        },
        "target": {
          "created": 2,
          "existing": {
            "total": "0",
            "processed": 0
          }
        },
        "source": {
          "existing": {
            "total": "2",
            "processed": 2
          }
        }
      },
      "stageDescription": "reconciling target entries",
      "stage": "ACTIVE_RECONCILING_TARGET",
      "state": "ACTIVE",
      "mapping": "systemXmlfileAccounts_managedUser"
    },
    "message": "SOURCE_IGNORED: 0 MISSING: 0 FOUND: 0 AMBIGUOUS: 0 UNQUALIFIED: 0
       CONFIRMED: 0 SOURCE_MISSING: 0 ABSENT: 2 TARGET_IGNORED: 0 UNASSIGNED: 0 ",
    "timestamp": "2013-05-14T08:20:41.783Z",
    "reconId": "1ef8e7b6-33dc-4f92-810a-b51913508a68",
    "entryType": "summary",
    "_id": "94c5893c-4ea8-4464-aff2-5533d6369722",
    "status": "SUCCESS",
    "exception": "",
    "mapping": "systemXmlfileAccounts_managedUser"
  }
}

Note that the response structure of a read request on audit/recon differs from the response structure of a query by recon-id. The read response contains a flat list of entries. The query response separates the summary and start event type entries. Other entries are listed under the result entry.

To query the audit log for a specific reconciliation situation, use the audit-by-recon-id-situation keyword, specifying the reconciliation ID and the situation that you want to query. For example, the following query returns all ABSENT records found during the specified reconciliation operation:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request GET
 "http://localhost:8080/openidm/audit/recon?_queryId=audit-by-recon-id-situation&situation=
 ABSENT&reconId=fd2a59df-1fcd-444d-97ca-2c8a7ec6dc6c"

Output similar to the following is returned, with one entry for each record that matches the situation queried:

{
  "result": [
    {
      "messageDetail": null,
      "rootActionId": "3c098d6f-a5e5-483c-a8ce-82911a10b0a9",
      "situation": "ABSENT",
      "actionId": "1a391fe8-201b-4f59-ad05-92ee804488a8",
      "targetObjectId": "managed/user/scarter",
      "action": "CREATE",
      "entryType": "",
      "_id": "6dc0a18a-826d-487d-a29f-5cd8d2f55465",
      "reconId": "1ef8e7b6-33dc-4f92-810a-b51913508a68",
      "status": "SUCCESS",
      "exception": "",
      "reconciling": "source",
      "ambiguousTargetObjectIds": "",
      "timestamp": "2013-05-14T08:20:41.763Z",
      "message": null,
      "sourceObjectId": "system/xmlfile/account/scarter"
    },
    {
      "messageDetail": null,
      "rootActionId": "3c098d6f-a5e5-483c-a8ce-82911a10b0a9",
      "situation": "ABSENT",
      "actionId": "0aaba292-1dd3-4e98-a0e2-04bec9ae5209",
      "targetObjectId": "managed/user/bjensen",
      "action": "CREATE",
      "entryType": "",
      "_id": "1cda457e-54e2-451b-8a40-ef93dec7e60c",
      "reconId": "1ef8e7b6-33dc-4f92-810a-b51913508a68",
      "status": "SUCCESS",
      "exception": "",
      "reconciling": "source",
      "ambiguousTargetObjectIds": "",
      "timestamp": "2013-05-14T08:20:41.760Z",
      "message": null,
      "sourceObjectId": "system/xmlfile/account/bjensen"
    }
  ]
}

The activity logs track all operations on internal (managed) and external (system) objects. Entries in the activity log contain identifiers for the reconciliation or synchronization action that triggered the activity, and for the original caller and the relationships between related actions.

You can access the activity logs over REST with the following call:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request GET
 "http://localhost:8080/openidm/audit/activity"

The following extract of the activity log shows the last entry in the log, which was a password change for user bjensen.

{
  "entries": [
    {
      "rootActionId": "3c098d6f-a5e5-483c-a8ce-82911a10b0a9",
      "action": "create",
      "objectId": "managed/user/bjensen",
      "_rev": "0",
      "_id": "22ef6d20-bd84-4267-9db8-745825a46ad1",
      "timestamp": "2013-05-14T08:20:41.712Z",
      "message": null,
      "activityId": "aa964020-b343-4d23-8e00-745ed4b79f50",
      "parentActionId": "0aaba292-1dd3-4e98-a0e2-04bec9ae5209",
      "requester": "openidm-admin",
      "rev": "0",
      "passwordChanged": true,
      "status": "SUCCESS",
      "before": null,
      "changedFields": [
        "/password"
      ],
      "after": {
        "securityAnswer": {
          "$crypto": {
            "type": "x-simple-encryption",
            "value": {
              "key": "openidm-sym-default",
              "iv": "c6gZKz4sSL2YNTBwwFXIzw==",
              "cipher": "AES/CBC/PKCS5Padding",
              "data": "7FATEBtuLlLfMXGokfLrtJ2rXPEsE1YTIXSoGQCum4w="
            }
          }
        },
       ...

To return activity information for a specific action, include the _id of the action in the endpoint, for example:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request GET
 "http://localhost:8080/openidm/audit/activity/22ef6d20-bd84-4267-9db8-745825a46ad1"

Results similar to the following are returned:

{
  "rootActionId": "3c098d6f-a5e5-483c-a8ce-82911a10b0a9",
  "action": "create",
  "objectId": "managed/user/bjensen",
  "timestamp": "2013-05-14T08:20:41.712Z",
  "message": null,
  "activityId": "aa964020-b343-4d23-8e00-745ed4b79f50",
  "after": {
    "securityAnswer": {
      "$crypto": {
        "type": "x-simple-encryption",
        "value": {
          "key": "openidm-sym-default",
          "iv": "c6gZKz4sSL2YNTBwwFXIzw==",
          "cipher": "AES/CBC/PKCS5Padding",
          "data": "7FATEBtuLlLfMXGokfLrtJ2rXPEsE1YTIXSoGQCum4w="
        }
      }
    },
    "stateProvince": "",
    "userName": "bjensen@example.com",
    "roles": "openidm-authorized",
    "description": "Created By XML1",
    "accountStatus": "active",
    "password": {
      "$crypto": {
        "type": "x-simple-encryption",
        "value": {
          "key": "openidm-sym-default",
          "iv": "bqhRyLW1lI+KZROcpgyukg==",
          "cipher": "AES/CBC/PKCS5Padding",
          "data": "qO8A76GqNqftVVwOlasyPw=="
        }
      }
    },
    "securityQuestion": "1",
    "givenName": "Barbara",
    "address2": "",
    "lastPasswordAttempt": "Tue May 14 2013 10:20:41 GMT+0200 (SAST)",
    "address1": "",
    "familyName": "Jensen",
    "passwordAttempts": "0",
    "country": "",
    "city": "",
    "_rev": "0",
    "lastPasswordSet": "",
    "postalCode": "",
    "phoneNumber": "1234567",
    "_id": "bjensen",
    "email": "bjensen@example.com"
  },
  "changedFields": [
    "/password"
  ],
  "_id": "22ef6d20-bd84-4267-9db8-745825a46ad1",
  "_rev": "0",
  "parentActionId": "0aaba292-1dd3-4e98-a0e2-04bec9ae5209",
  "requester": "openidm-admin",
  "rev": "0",
  "passwordChanged": true,
  "status": "SUCCESS",
  "before": null
}

Each action in the activity log has a rootActionId and a parentActionId. The rootActionId is the ID that was assigned to the incoming or initiating request. The parentActionId is the ID that is associated with the overall action. So, for example, if an HTTP request invokes a script that changes a user's password, the HTTP request is assigned the rootActionId and the action taken by the script is assigned the parentActionId. You can query the activity log for the details of a specific action by including the parentActionId in the query. For example:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request GET
 "http://localhost:8080/openidm/audit/activity?_queryId=audit-by-activity-parent-action&
           parentActionId=90ce7473-98c7-4747-bfbb-32bfa3ce5d92"

The following sample output shows the result of a query that requests details of the password change for bjensen.

{
  "result": [
    {
      "rootActionId": "3c098d6f-a5e5-483c-a8ce-82911a10b0a9",
      "action": "create",
      "objectId": "managed/user/bjensen",
      "_rev": "0",
      "_id": "22ef6d20-bd84-4267-9db8-745825a46ad1",
      "timestamp": "2013-05-14T08:20:41.712Z",
      "message": null,
      "activityId": "aa964020-b343-4d23-8e00-745ed4b79f50",
      "parentActionId": "0aaba292-1dd3-4e98-a0e2-04bec9ae5209",
      "requester": "openidm-admin",
      "rev": "0",
      "passwordChanged": true,
      "status": "SUCCESS",
      "before": null,
      "changedFields": [
        "/password"
      ],
      "after": {
        "securityAnswer": {
          "$crypto": {
            "type": "x-simple-encryption",
            "value": {
              "key": "openidm-sym-default",
              "iv": "c6gZKz4sSL2YNTBwwFXIzw==",
              "cipher": "AES/CBC/PKCS5Padding",
              "data": "7FATEBtuLlLfMXGokfLrtJ2rXPEsE1YTIXSoGQCum4w="
            }
          }
        },
        "stateProvince": "",
        "userName": "bjensen@example.com",
        "roles": "openidm-authorized",
        "description": "Created By XML1",
        "accountStatus": "active",
        "password": {
          "$crypto": {
            "type": "x-simple-encryption",
            "value": {
              "key": "openidm-sym-default",
              "iv": "bqhRyLW1lI+KZROcpgyukg==",
              "cipher": "AES/CBC/PKCS5Padding",
              "data": "qO8A76GqNqftVVwOlasyPw=="
            }
          }
        },
        "securityQuestion": "1",
        "givenName": "Barbara",
        "address2": "",
        "lastPasswordAttempt": "Tue May 14 2013 10:20:41 GMT+0200 (SAST)",
        "address1": "",
        "familyName": "Jensen",
        "passwordAttempts": "0",
        "country": "",
        "city": "",
        "_rev": "0",
        "lastPasswordSet": "",
        "postalCode": "",
        "phoneNumber": "1234567",
        "_id": "bjensen",
        "email": "bjensen@example.com"
      }
    }
  ]
}

Note

For audit logs in the repository, you can define custom queries using the parameterized query mechanism. For more information, see the section on Parameterized Queries

For more information about the entries in these logs, see the chapter that covers Using Audit Logs.

10.3. Triggering LiveSync Over REST

The ability to trigger LiveSync operations over REST, or by using the resource API, enables you to use an external scheduler to trigger a LiveSync operation, rather than using the OpenIDM scheduling mechanism.

There are two ways in which to trigger a LiveSync operation over REST.

  • Use the _action=liveSync parameter directly on the resource. This is the recommended method. The following example calls a LiveSync operation on the user accounts in an external LDAP system.

    $ curl
     --header "X-OpenIDM-Username: openidm-admin"
     --header "X-OpenIDM-Password: openidm-admin"
     --request POST
     "http://localhost:8080/openidm/system/ldap/account?_action=liveSync"
                  
  • Target the system endpoint and supply a source parameter to identify the object that should be synchronized. This method matches the scheduler configuration and can therefore be used to test schedules before they are implemented.

    The following example calls the same LiveSync operation as the previous example.

    $ curl
     --header "X-OpenIDM-Username: openidm-admin"
     --header "X-OpenIDM-Password: openidm-admin"
     --request POST
     "http://localhost:8080/openidm/system?_action=liveSync&source=system/ldap/account"
                    

A successful LiveSync operation returns the following response:

{
    "_id": "SYSTEMLDAPACCOUNT",
    "_rev": "0",
    "connectorData": {
        "syncToken": 1,
        "nativeType": "integer"
    }
}
        

You should not run two identical LiveSync operations simultaneously - you must ensure that the first operation has completed before a second similar operation is launched.

To troubleshoot a LiveSync operation that has not succeeded, you can include an optional parameter (detailedFailure) to return additional information. For example:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --request POST
 "http://localhost:8080/openidm/system/ldap/account?_action=liveSync&detailedFailure=true"
        

Note

The first time that a LiveSync operation is called, no synchronization token exists in the database to establish which changes have already been processed. The default LiveSync behavior is to locate the last existing entry in the change log, and to store that entry in the database as the current starting position from which changes should be applied. This behavior prevents LiveSync from processing changes that might already have been processed during an initial data load. Subsequent LiveSync operations will pick up and process any new changes.

Typically, in setting up LiveSync on a new system, you would load the data initially (by using reconciliation, for example) and then enable LiveSync, starting from that base point.

10.4. Flexible Data Model

Identity management software tends to favor either a meta-directory data model, where all data are mirrored in a central repository, or a virtual data model, where only a minimum set of attributes are stored centrally, and most are loaded on demand from the external resources in which they are stored. The meta-directory model offers fast access at the risk of getting out-of-date data. The virtual model guarantees fresh data, but pays for that guarantee in terms of performance.

OpenIDM leaves the data model choice up to you. You determine the right trade offs for a particular deployment. OpenIDM does not hard code any particular schema or set of attributes stored in the repository. Instead, you define how external system objects map onto managed objects, and OpenIDM dynamically updates the repository to store the managed object attributes that you configure.

You can, for example, choose to follow the data model defined in the Simple Cloud Identity Management (SCIM) specification. The following object represents a SCIM user.

{
    "userName": "james1",
    "familyName": "Berg",
    "givenName": "James",
    "email": [
        "james1@example.com"
    ],
    "description": "Created by OpenIDM REST.",
    "password": "asdfkj23",
    "displayName": "James Berg",
    "phoneNumber": "12345",
    "employeeNumber": "12345",
    "userType": "Contractor",
    "title": "Vice President",
    "active": true
}

Note

Avoid using the dash character ( - ) in property names, like last-name, as dashes in names make JavaScript syntax more complex. If you cannot avoid the dash, then write source['last-name'] instead of source.last-name in java script.

10.5. Basic Data Flow Configuration

Data flow for synchronization involves the following elements:

  • Connector configuration files (conf/provisioner-*.json), with one file per external resource.

  • Synchronization mappings file (conf/sync.json), with one file per OpenIDM instance.

  • A links table that OpenIDM maintains in its repository.

  • The scripts required to check objects and manipulate attributes.

10.5.1. Connector Configuration Files

Connector configuration files map external resource objects to OpenIDM objects, and are described in detail in the chapter on Connecting to External Resources. Connector configuration files are named openidm/conf/provisioner.resource-name.json, where resource-name reflects the connector technology and external resource, such as openicf-xml.

An excerpt from an example connector configuration follows. The example shows the name for the connector and two attributes of an account object type. In the attribute mapping definitions, the attribute name is mapped from the nativeName, the attribute name used on the external resource, to the attribute name used in OpenIDM. Thus the example shows that the sn attribute in LDAP is mapped to lastName in OpenIDM. The homePhone attribute can have multiple values.

{
    "name": "MyLDAP",
    "objectTypes": {
        "account": {
            "lastName": {
                "type": "string",
                "required": true,
                "nativeName": "sn",
                "nativeType": "string"
            },
            "homePhone": {
                "type": "array",
                "items": {
                    "type": "string",
                    "nativeType": "string"
                },
                "nativeName": "homePhone",
                "nativeType": "string"
            }
        }
    }
}

In order for OpenIDM to access external resource objects and attributes, the object and its attributes must match the connector configuration. Note that the connector file only maps external resource objects to OpenIDM objects. To construct attributes and to manipulate their values, you use the synchronization mappings file.

10.5.2. Synchronization Mappings File

The synchronization mappings file (openidm/conf/sync.json) represents the core configuration for OpenIDM synchronization.

The sync.json file describes a set of mappings. Each mapping specifies how attributes from source objects correspond to attributes on target objects. The source and target indicate the direction for the data flow, so you must define a separate mapping for each data flow. For example, if you want data flows from an LDAP server to the repository and also from the repository to the LDAP server, you must define two separate mappings.

You identify external resource sources and targets as system/name/object-type, where name is the name used in the connector configuration file, and object-type is the object defined in the connector configuration file list of object types. For objects in OpenIDM's internal repository, you use managed/object-type, where object-type is defined in openidm/conf/managed.json. The name for the mapping by convention is set to a string of the form source_target, as shown in the following example.

{
    "mappings": [
        {
            "name": "systemLdapAccounts_managedUser",
            "source": "system/MyLDAP/account",
            "target": "managed/user",
            "properties": [
                {
                    "target": "familyName",
                    "source": "lastName"
                },
                {
                    "target": "homePhone",
                    "source": "homePhone"
                },
                {
                    "target": "phoneExtension",
                    "default": "0047"
                },
                {
                    "target": "mail",
                    "comment": "Set mail if non-empty.",
                    "source": "email",
                    "condition": {
                        "type": "text/javascript",
                        "source": "(object.email != null)"
                    }
                },
                {
                    "target": "displayName",
                    "source": "",
                    "transform": {
                        "type": "text/javascript",
                        "source": "source.lastName +', ' + source.firstName;"
                    }
                }
            ]
        }
    ]
}

In this example, the source is the external resource, MyLDAP, and the target is OpenIDM's repository, specifically the managed user objects. The properties reflect OpenIDM attribute names. For example, the mapping has the attribute lastName defined in the MyLDAP connector configuration file mapped to familyName in the OpenIDM managed user object. Notice that the attribute names come from the connector configuration, rather than the external resource itself.

You can create attributes on the target as part of the mapping. In the preceding example, a phoneExtension attribute with a default value of 0047 is created on the target. The "default" property can also be used to specify the value to assign to the target property if the "source" property and the "transform" script yield a null value. If no value is specified, the default value is null.

You can also set up conditions under which OpenIDM maps attributes as shown for the email attribute in the example. By default, OpenIDM synchronizes all attributes. In the example, the mail attribute is set only if the script for the condition returns true.

OpenIDM also enables you to transform attributes. In the example, the value of the displayName attribute is set using a combination of the lastName and firstName attribute values from the source. For transformations, the source property is optional. However, the source object is only available when you specify the source property. Therefore, in order to use source.lastName and source.firstName to calculate the displayName, the example specifies "source" : "".

To add a flow from the repository to MyLDAP, you would define a mapping with source managed/user and target system/MyLDAP/account, named for example managedUser_systemLdapAccounts.

The following image shows the paths to objects in the OpenIDM namespace.

OpenIDM namespace and object paths

OpenIDM stores managed objects in the repository, and exposes them under /openidm/managed. System objects on external resources are exposed under /openidm/system.

By default, OpenIDM synchronizes all objects that match those defined in the connector configuration for the resource. Many connectors allow you to limit the scope of objects that the connector accesses. For example, the LDAP connector allows you to specify base DNs and LDAP filters so that you do not need to access every entry in the directory. OpenIDM also allows you to filter what is considered a valid source or valid target for synchronization by using JavaScript code. To apply these filters, use the validSource, and validTarget properties in your mapping.

validSource

A script that determines if a source object is valid to be mapped. The script yields a boolean value: true indicates that the source object is valid; false can be used to defer mapping until some condition is met. In the root scope, the source object is provided in the "source" property. If the script is not specified, then all source objects are considered valid.

{
    "validSource": {
        "type": "text/javascript",
        "source": "source.ldapPassword != null"
    }
}
validTarget

A script, used during reconciliation's second phase, that determines if a target object is valid to be mapped. The script yields a boolean value: true indicates that the target object is valid; false indicates that the target object should not be included in reconciliation. In the root scope, the source object is provided in the "target" property. If the script is not specified, then all target objects are considered valid for mapping.

{
    "validTarget": {
        "type": "text/javascript",
        "source": "target.employeeType == 'internal'"
    }
}

During synchronization, your scripts always have access to a source object and a target object. Examples already shown in this section use source.attributeName to retrieve attributes from the source objects. Your scripts can also write to target attributes using target.attributeName syntax.

{
    "onUpdate": {
        "type": "text/javascript",
        "source": "if (source.email != null) {target.mail = source.email;}"
    }
}

See the Scripting Reference appendix for more on scripting.

By default, all mappings participate in automatic synchronization operations. You can prevent a specific mapping from participating in automatic synchronization by setting the "enableSync" property of that mapping to false. In the following example, automatic synchronization is disabled. This means that changes to objects in the internal repository are not automatically propagated to the LDAP directory. To propagate changes to the LDAP directory, reconciliation must be launched manually.

{
    "mappings" : [
        {
            "name" : "managedUser_systemLdapAccounts",
            "source" : "managed/user",
            "target" : "system/ldap/account",
            "enableSync" : false,
             ....
}
   

10.5.3. Using Encrypted Values

OpenIDM supports reversible encryption of attribute values for managed objects. Attribute values to encrypt include passwords, authentication questions, credit card numbers, and social security numbers. If passwords are already encrypted on the external resource, they are generally excluded from the synchronization process. For more information, see Managing Passwords.

You configure encryption in the managed object configuration (in the openidm/conf/managed.json file). The following example shows a managed object configuration that encrypts and decrypts securityAnswer, ssn, and password attributes using the default symmetric key, and additional scripts for extra passwords.

{
    "objects": [
        {
            "name": "user",
            "properties": [
                {
                    "name": "securityAnswer",
                    "encryption": {
                        "key": "openidm-sym-default"
                    }
                },
                {
                    "name": "ssn",
                    "encryption": {
                        "key": "openidm-sym-default"
                    }
                },
                {
                    "name": "password",
                    "encryption": {
                        "key": "openidm-sym-default"
                    }
                }
            ],
            "onStore": {
                "type": "text/javascript",
                "file": "script/encryptExtraPassword.js"
            },
            "onRetrieve": {
                "type": "text/javascript",
                "file": "script/decryptExtraPassword.js"
            }
        }
    ]
}

Do not use the default symmetric key, openidm-sym-default, in production. See the chapter on Securing and Hardening OpenIDM for instructions on adding your own symmetric key.

10.5.4. Restricting HTTP Access to Sensitive Data

You can protect specific sensitive data stored in the repository by marking the corresponding properties as "private". Private data, whether it is encrypted or not, is not accessible over the REST interface. Properties that are marked as private are removed from an object when that object is retrieved over REST.

To mark a property as private, set its "scope" to "private" in the conf/managed.json file.

The following extract of the managed.json file shows how HTTP access is prevented on the password and securityAnswer properties.

       "properties" : [
           {
               "name" : "securityAnswer",
               "encryption" : {
                   "key" : "openidm-sym-default"
               },
               "scope" : "private"
           },
           {
               "name" : "password",
               "encryption" : {
                   "key" : "openidm-sym-default"
               },
               "scope" : "private"
   

A potential caveat with using private properties is that such properties are removed if an object is updated by using an HTTP PUT request. A PUT request replaces the entire object in the repository. Because properties that are marked as private are ignored in HTTP requests, these properties are effectively removed from the object when the update is done. To work around this limitation, do not use PUT requests if you have configured private properties. Instead, use a PATCH request to update only those properties that need to be changed.

For example, to update the familyName of user joe, replace only the familyName and not the entire user object, as follows:

$ curl
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 --header "Content-Type: application/json"
 --request POST
 --data '[{"replace":"familyName","value": "Brown"}]'
 "http://localhost:8080/openidm/managed/user?_action=patch&_queryId=for-userName&uid=joe"
   

Note

The filtering of private data applies only to direct HTTP read and query calls on managed objects. No automatic filtering is done for internal callers, and the data that these callers choose to expose.

10.5.5. Constructing and Manipulating Attributes

OpenIDM enables you to construct and manipulate attributes using scripts that are triggered when an object is created (onCreate), updated (onUpdate), or deleted (onDelete), or when a link is created (onLink), or removed (onUnlink).

The following example derives a DN for an LDAP entry when the entry is created in the internal repository.

{
    "onCreate": {
        "type": "text/javascript",
        "source":
            "target.dn = 'uid=' + source.uid + ',ou=people,dc=example,dc=com'"
    }
}

10.5.6. Reusing Links

When two mappings exist to synchronize the same objects bidirectionally, you can use the links property in one mapping to have OpenIDM use the same internally managed link for both mappings. Otherwise, if no links property is specified, OpenIDM maintains a link for each mapping.

The following excerpt shows two mappings, one from MyLDAP accounts to managed users, and another from managed users to MyLDAP accounts. In the second mapping, the link property tells OpenIDM to reuse the links created in the first mapping, rather than create new links.

{
    "mappings": [
        {
            "name": "systemMyLDAPAccounts_managedUser",
            "source": "system/MyLDAP/account",
            "target": "managed/user"
        },
        {
            "name": "managedUser_systemMyLDAPAccounts",
            "source": "managed/user",
            "target": "system/MyLDAP/account",
            "links": "systemMyLDAPAccounts_managedUser"
        }
    ]
}

10.6. Synchronization Situations and Actions

During synchronization, OpenIDM categorizes objects by situation. Situations are characterized by whether an object exists on a source or target system, whether OpenIDM has registered a link between the source object and the target object, and whether the object is considered valid. OpenIDM takes action depending on the situation.

You can define actions for particular situations in the policies section of a synchronization mapping, as shown in the following excerpt.

{
    "policies": [
        {
            "situation": "CONFIRMED",
            "action": "UPDATE"
        },
        {
            "situation": "FOUND",
            "action": "IGNORE"
        },
        {
            "situation": "ABSENT",
            "action": "CREATE"
        },
        {
            "situation": "AMBIGUOUS",
            "action": "IGNORE"
        },
        {
            "situation": "MISSING",
            "action": "IGNORE"
        },
        {
            "situation": "UNQUALIFIED",
            "action": "IGNORE"
        },
        {
            "situation": "UNASSIGNED",
            "action": "IGNORE"
        }
    ]
}

If you do not define a policy for a particular situation, OpenIDM takes the default action for the situation.

The situations and their corresponding actions are described in the following sections.

10.6.1. Synchronization Situations

OpenIDM performs a reconciliation action in two phases:

  1. Source reconciliation, where OpenIDM accounts for source objects and associated links based on the configured mapping.

  2. Target reconciliation, where OpenIDM iterates over the target objects that were not processed in the first phase.

During reconciliation OpenIDM builds three lists, assigning values to the objects to reconcile.

  1. All valid objects from the source

    OpenIDM assigns valid source objects qualifies=1. Invalid objects, including those not found in the source system, and those filtered out by the script specified validSource property, are assigned qualifies=0.

  2. All records from the appropriate link table

    Objects with corresponding links in the link table of the repository are assigned link=1. Objects without corresponding links are assigned link=0.

  3. All valid objects on the target system

    Objects found in the target system are assigned target=1. Objects that are not found in the target system are assigned target=0.

Based on the values assigned to objects during source reconciliation, OpenIDM assigns situations, listed here with their default actions.

"CONFIRMED" (qualifies=1, link=1, target=1)

The mapping qualifies for a target object, and a link to an existing target object was found. Detected during change events and reconciliation. Default action: "UPDATE".

"FOUND" (qualifies=1, link=0, target=1)

The mapping qualifies for a target object, there is no link to a target object, and there is a single target object, correlated by the logic found in the correlationQuery. Detected during change events and reconciliation. Default action: "UPDATE".

"ABSENT" (qualifies=1, link=0, target=0)

The mapping qualifies for a target object, there is no link to a target object, and there is no correlated target object found. Detected during change events and reconciliation. Default action: "CREATE".

"AMBIGUOUS" (qualifies=1, link=0, target>1)

The mapping qualifies for a target object, there is no link to a target object, but there is more than one correlated target object. Detected during source object changes and reconciliation. Default action: "EXCEPTION".

"MISSING" (qualifies=1, link=1, target=0)

The mapping qualifies for a target object, and there is a qualified link to a target object, but the target object is missing. Only detected during reconciliation and source object changes in synchronous mappings. Default action: "EXCEPTION".

"UNQUALIFIED" (qualifies=0, link=0 or 1, target=1 or >1)

The mapping is not qualified for a source object. One or more targets are found through the correlation logic. Detected during change events and reconciliation. Default action: "DELETE".

"TARGET_IGNORED" (qualifies=0, link=0 or 1, target=1)

The mapping is not qualified for a source object. One or more targets are found through the correlation logic. Detected only during source object changes. Default action: "IGNORE".

"SOURCE_IGNORED" (qualifies=0, link=0, target=0)

The source object is unqualified (by validSource), no link, no target is found. Detected during source object changes and reconciliation. Default action: "IGNORE".

"LINK_ONLY" (qualifies=n/a, link=1, target=0)

The source may or may not be qualified, a link is found, but no target object is found. Detected only during source object changes. Default action: "EXCEPTION".

"ALL_GONE" (qualifies=n/a, link=0, no previous object available)

The source may or may not be qualified, no link is found, and the server is unable to correlate, as the source cannot supply the deleted object. Detected only during source object changes. Default action: "IGNORE".

Based on the values assigned to objects during target reconciliation, OpenIDM assigns situations, listed here with their default actions.

"TARGET_IGNORED" (qualifies=0)

During target reconciliation the target becomes unqualified by the "validTarget" script. Only detected during reconciliation. Default action: "IGNORE"

"UNASSIGNED" (qualifies=1, link=0)

A target object exists for which there is no link. Only detected during reconciliation. Default action: "EXCEPTION".

"CONFIRMED" (qualifies=1, link=1, source=1)

The mapping qualifies for a target object, and a link to a source object exists. Detected only during reconciliation. Default action: "UPDATE".

"UNQUALIFIED" (qualifies=0, link=1, source=1, but source does not qualify)

The mapping is not qualified (by validTarget) for a target object, and there is a link from an existing source object where the source exists. Detected during change events and reconciliation. Default action: "DELETE".

SOURCE_MISSING (qualifies=1, link=1, source=0)

The target qualifies and a link is found. But the source object is missing. Detected during change events and reconciliation. Default action: "DELETE".

The following sections reiterate in detail how OpenIDM assigns situations during each of the two synchronization phases.

10.6.2. Source Reconciliation

OpenIDM starts reconciliation and LiveSync by reading a list of objects from the resource. For reconciliation, the list includes all objects available through the connector. For LiveSync, the list contains only changed objects. The connector can filter objects out of the list, too. You can filter objects out of the list by using the validSource property.

OpenIDM then iterates over the list, checking each entry against the validSource filter, classifying objects according to their situations as described in Section 10.6.1, “Synchronization Situations”. OpenIDM uses the list of links for the current mapping to classify objects. Finally, OpenIDM executes the action configured for the situation.

The following table shows how OpenIDM assigns the appropriate situation during source reconciliation, depending on whether a valid source exists (Source Qualifies), whether a link with the appropriate type exists in the repository (Link Exists), and how many target objects are found, either based on links or correlation results.

Table 10.1. Resolving Source Reconciliation Situations
Source Qualifies?Link Exists?Target Objects Found[a]Situation
YesNoYesNo01> 1
 X   X TARGET_IGNORED
 X X X SOURCE_MISSING
 X X  XUNQUALIFIED
 XX X  UNQUALIFIED
 XX  X TARGET_IGNORED
 XX   XUNQUALIFIED
X  XX  ABSENT
X  X X FOUND
X  X  XAMBIGUOUS
X X X  MISSING
X X  X CONFIRMED

[a] If no link exists for the source object, then OpenIDM executes a correlation query. If no previous object is available, OpenIDM cannot correlate.


10.6.3. Target Reconciliation

During source reconciliation, OpenIDM cannot detect situations where no source object exists, such as the UNASSIGNED situation. When no source object exists, OpenIDM detects the situation during the second reconciliation phase, target reconciliation. During target reconciliation, OpenIDM iterates over all target objects that do not have a representation on the source, checking each object against the validTarget filter, determining the appropriate situation, and executing the action configured for the situation.

The following table shows how OpenIDM assigns the appropriate situation during target reconciliation, depending on whether a valid target exists (Target Qualifies), whether a link with an appropriate type exists in the repository (Link Exists), whether a source object exists (Source Exists), and whether the source object qualifies (Source Qualifies). Not all situations assigned during source reconciliation are assigned during target reconciliation.

Table 10.2. Resolving Target Reconciliation Situations
Target Qualifies?Link Exists?Source Exists?Source Qualifies?Situation
YesNoYesNoYesNoYesNo
 X      TARGET_IGNORED
X  X X  UNASSIGNED
X X X X CONFIRMED
X X X  XUNQUALIFIED
X X  X  SOURCE_MISSING

10.6.4. Situations Specific to Automatic Synchronization and LiveSync

Certain situations occur only during automatic synchronization (when OpenIDM pushes changes made in the repository out to external systems) and LiveSync (when OpenIDM polls external system change logs for changes and updates the repository).

The following table shows the situations that pertain only to automatic sync and LiveSync, when records are deleted from the source or target resource.

Table 10.3. Resolving Automatic Sync and LiveSync Delete Situations
Source Qualifies?Link Exists?Target Objects Found[a]Situation
YesNoYesNo01> 1
N/AN/AX X  LINK_ONLY
N/AN/A XX  ALL_GONE
X  X  XAMBIGUOUS
 X X  XUNQUALIFIED

[a] If no link exists for the source object, then OpenIDM executes a correlation query. If no previous object is available, OpenIDM cannot correlate.


10.6.5. Synchronization Actions

Once OpenIDM has assigned a situation to an object, OpenIDM takes the actions configured in the mapping. If no action is configured, then OpenIDM takes the default action for the situation. OpenIDM supports the following actions.

"CREATE"

Create and link a target object.

"UPDATE"

Link and update a target object.

"DELETE"

Delete and unlink the target object.

"LINK"

Link the correlated target object.

"UNLINK"

Unlink the linked target object.

"EXCEPTION"

Flag the link situation as an exception.

Do not use this action for LiveSync mappings.

"IGNORE"

Do not change the link or target object state.

10.6.6. Providing a Script as an Action

In addition to the static synchronization actions described in the previous section, you can provide a script that is run in specific synchronization situations. The following extract of a sample sync.json file specifies that when a synchronization operation assesses an entry as ABSENT, the workflow named managedUserApproval is invoked. The parameters for the workflow are passed in as properties of the action parameter.

{
    "situation" : "ABSENT",
    "action" : {
        "workflowName" : "managedUserApproval",
        "type" : "text/javascript",
        "file" : "bin/defaults/script/workflow/workflow.js"
    }
}
      

The variables available to these scripts are described in Variables Available in Scripts in the Scripting Appendix.

10.7. Asynchronous Reconciliation

Reconciliation can work in tandem with workflows to provide additional business logic to the reconciliation process. You can define scripts to determine the action that should be taken for a particular reconciliation situation. A reconciliation process can launch a workflow after it has assessed a situation, and then perform the reconciliation, or some other action.

For example, you might want a reconciliation process to assess new user accounts that need to be created on a target resource. However, new user account creation might require some kind of approval from a manager before the accounts are actually created. The initial reconciliation process can assess the accounts that need to be created, launch a workflow to request management approval for those accounts, and then relaunch the reconciliation process to create the accounts, once the management approval has been received.

In this scenario, the defined script returns IGNORE for new accounts and the reconciliation engine does not continue processing the given object. The script then initiates an asynchronous process which calls back and completes the reconciliation process at a later stage.

A sample configuration for this scenario is available in openidm/samples/sample9, and described in Sample 9 - Asynchronous Reconciliation Using Workflows in the Installation Guide.

Configuring asynchronous reconciliation involves the following steps:

  1. Create the workflow definition file (.bar file) and place it in the openidm/workflow directory. For more information about creating workflows, see Integrating Business Processes and Workflows.

  2. Modify the conf/sync.json file for the situation or situations that should call the workflow. Reference the workflow name in the configuration for that situation.

    For example, the following sync.json extract calls the managedUserApproval workflow if the situation is assessed as ABSENT:

                    {
                        "situation" : "ABSENT",
                        "action" : {
                            "workflowName" : "managedUserApproval",
                            "type" : "text/javascript",
                            "file" : "bin/defaults/script/workflow/workflow.js"
                        }
                    },
           
  3. In the sample configuration, the workflow calls a second, asynchronous reconciliation process as a final step.

10.8. Configuring Case Sensitivity for Data Stores

By default, OpenIDM is case-sensitive, which means that case is taken into account when comparing IDs during reconciliation. For data stores that are case-insensitive, such as OpenDJ, IDs and links that are created by a reconciliation process may be stored with a different case to the way in which they are stored in the OpenIDM repository. Such a situation can cause problems during a reconciliation operation, as the links for these IDs may not match.

For such data stores, you can configure OpenIDM to ignore case during reconciliation operations. With case sensitivity turned off in OpenIDM, for those specific mappings, comparisons are done without regard to case.

To specify that data stores are not case-sensitive, set the "sourceIdsCaseSensitive" or "targetIdsCaseSensitive" property to false in the mapping for those links. For example, if the LDAP data store is case-insensitive, set the mapping from the LDAP store to the managed user repository as follows:

"mappings" : [
{
"name" : "systemLdapAccounts_managedUser",
"source" : "system/ldap/account",
"sourceIdsCaseSensitive" : false,
"target" : "managed/user",
"properties" : [
...
   

If a mapping inherits links by using the "links" property, it is not necessary to set case sensitivity, because the mapping uses the setting of the referred links.

Note that configuring OpenIDM to be case-insensitive when comparing links does not make the OpenICF provisioner case-insensitive when it requests data. For example, if a user entry is stored with the ID testuser and you make a request for http://localhost:8080/openidm/managed/TESTuser, most provisioners will filter out the match because of the difference in case, and will indicate that the record is not found. To prevent the provisioner from performing this secondary filtering, set the enableFilteredResultsHandler property to false in the provisioner configuration. For example:

"resultsHandlerConfig" :
{
    "enableFilteredResultsHandler":false,
},
   

Caution

Do not disable the filtered results handler for the CSV file connector. The CSV file connector does not perform filtering so if you disable the filtered results handler for this connector, the full CSV file will be returned for every request.

10.9. Reconciliation Optimization

By default, reconciliation is configured to function in an optimized way. Some of these optimizations might, however, be unsuitable for your environment. The following sections describe the optimizations and how they can be configured.

10.9.1. Correlating Empty Target Sets

To optimize a reconciliation operation, the reconciliation process does not attempt to correlate source objects to target objects if the set of target objects is empty when the correlation is started. This considerably speeds up the process the first time the reconciliation is run. You can change this behavior for a specific mapping by adding the correlateEmptyTargetSet property to the mapping definition and setting it to true. For example:

{
    "mappings": [
        {
            "name"                     : "systemMyLDAPAccounts_managedUser",
            "source"                   : "system/MyLDAP/account",
            "target"                   : "managed/user",
            "correlateEmptyTargetSet"  : true
        },
    ]
}

Be aware that this setting will have a performance impact on the reconciliation process.

10.9.2. Prefetching Links

All links are queried at the start of a correlation and the results of that query are used. You can disable the prefetching of links, so that the correlation process looks up each link in the database as it processes each source or target object. You can disable the prefetching of links by adding the prefetchLinks property to the mapping, and setting it to false, for example:

{
    "mappings": [
        {
            "name": "systemMyLDAPAccounts_managedUser",
            "source": "system/MyLDAP/account",
            "target": "managed/user"
            "prefetchLinks" : false
        }
    ]
}

Be aware that this setting will have a performance impact on the reconciliation process.

10.9.3. Parallel Reconciliation Threads

By default, reconciliation is executed in a multi-threaded manner, that is, numerous threads are dedicated to the same reconciliation run. Multithreading generally improves reconciliation run performance. The default number of threads for a single reconciliation run is ten (plus the main reconciliation thread). Under normal circumstances, you should not need to change this number, however the default might not be appropriate in the following situations:

  • The hardware has many cores and supports more concurrent threads. As a rule of thumb for performance tuning, start with setting the thread number to two times the number of cores.

  • The source or target is an external system with high latency or slow response times. Threads may then spend considerable time waiting for a response from the external system. Increasing the available threads enables the system to prepare or continue with additional objects.

To change the number of threads, set the taskThreads property in the conf/sync.json file, for example:

    "mappings" : [
        {
            "name" : "systemXmlfileAccounts_managedUser",
            "source" : "system/xmlfile/account",
            "target" : "managed/user",
            "taskThreads" : 20
            ...
         }
    ]
}

A value of 0 specifies that reconciliation is run on the main reconciliation thread, that is, in a serial manner.

10.10. Correlation Queries

Every time OpenIDM creates an object through synchronization, it creates a link between the source and target objects. OpenIDM then uses the link to determine the object's situation during later synchronization operations.

Initial, bulk synchronization operations can involve correlating many objects that exist both on source and target systems. In this case, OpenIDM uses correlation queries to find target objects that already exist, and that correspond to source objects. For the target objects that match a correlation query, OpenIDM needs only to create a link, rather than a new target object.

Correlation queries run against target resources. The query syntax therefore depends on the target system, and is either specific to the data store underlying the OpenIDM repository, or to OpenICF query capabilities.

10.10.1. Managed Object as Correlation Query Target

Queries on managed objects in the repository must be defined in the configuration file for the repository, which is either openidm/conf/repo.orientdb.json, or openidm/conf/repo.jdbc.json.

The following example shows a correlation query defined in openidm/conf/repo.orientdb.json.

"for-userName" : "SELECT * FROM ${unquoted:_resource} WHERE userName = ${uid}"

By default, a ${value} token replacement is assumed to be a quoted string. If the value is not a quoted string, use the unquoted: prefix, as shown above.

The following correlation query example shows the JavaScript to call the query defined for OrientDB. The _queryId property value matches the name of the query specified in openidm/conf/repo.orientdb.json, for-userName. The source.name value replaces ${uid} in the query. OpenIDM replaces ${unquoted:_resource} in the query with the name of the table that holds managed objects.

{
  "correlationQuery": {
    "type": "text/javascript",
    "source":
      "var query = {'_queryId' : 'for-userName', 'uid' : source.name}; query;"
  }
}

The query can return zero or more objects, so the situation OpenIDM assigns to the source object depends on the number of target objects returned.

With a JDBC-based repository, the query defined in openidm/conf/repo.jdbc.json is more complex due to how the tables are indexed. The correlation query you define in openidm/conf/sync.json is the same, however.

10.10.2. System Object as Correlation Query Target

Correlation queries on system objects access the connector. The connector then executes the query on the external resource.

Your correlation query JavaScript must return a map holding a generic query with the following elements.

  • A condition such as "Equals"

  • The naming attribute to compare on the system object. In the example that follows, the naming attribute is uid.

  • The value from the source object to use in the search filter. You set this as the value of the value property, which takes an array. In the example that follows, the value to use in the search filter is source.userName.

varmap={"query": {"Equals": {"field": "uid", "values": [ source.userName ]}}};
map;

10.11. Advanced Data Flow Configuration

Section 10.5, “Basic Data Flow Configuration” shows how to trigger scripts when objects are created and updated. Other situations require you to trigger scripts in response to other synchronization actions. For example, you might not want OpenIDM to delete a managed user directly when an external account is deleted, but instead unlink the objects and deactivate the user in another resource. (Alternatively, you might delete the object in OpenIDM but nevertheless execute a script.) The following example shows a more advanced mapping configuration.

{
    "mappings": [
        {
            "name": "systemLdapAccount_managedUser",
            "source": "system/ldap/account",
            "target": "managed/user",
            "validSource": {
                "type": "text/javascript",
                "file": "script/isValid.js"
            },
            "correlationQuery": {
                "type": "text/javascript",
                "file": "script/ldapCorrelationQuery.js"
            },
            "properties": [
                {
                    "source": "uid",
                    "transform": {
                        "type": "text/javascript",
                        "source": "source.toLowerCase()"
                    },
                    "target": "userName"
                },
                {
                    "source": "",
                    "transform": {
                        "type": "text/javascript",
                        "source": "if (source.myGivenName)
                            {source.myGivenName;} else {source.givenName;}"
                    },
                    "target": "givenName"
                },
                {
                    "source": "",
                    "transform": {
                        "type": "text/javascript",
                        "source": "if (source.mySn)
                            {source.mySn;} else {source.sn;}"
                    },
                    "target": "familyName"
                },
                {
                    "source": "cn",
                    "target": "fullname"
                },
                {
                    "comment": "Multi-valued in LDAP, single-valued in AD.
                        Retrieve first non-empty value.",
                    "source": "title",
                    "transform": {
                        "type": "text/javascript",
                        "file": "script/getFirstNonEmpty.js"
                    },
                    "target": "title"
                },
                {
                    "condition": {
                        "type": "text/javascript",
                        "source": "var clearObj = openidm.decrypt(object);
                            ((clearObj.password != null) &&
                            (clearObj.ldapPassword != clearObj.password))"
                    },
                    "transform": {
                        "type": "text/javascript",
                        "source": "source.password"
                    },
                    "target": "__PASSWORD__"
                }
            ],
            "onCreate": {
                "type": "text/javascript",
                "source": "target.ldapPassword = null;
                    target.adPassword = null;
                    target.password = null;
                    target.ldapStatus = 'New Account'"
            },
            "onUpdate": {
                "type": "text/javascript",
                "source": "target.ldapStatus = 'OLD'"
            },
            "onUnlink": {
                "type": "text/javascript",
                "file": "script/triggerAdDisable.js"
            },
            "policies": [
                {
                    "situation": "CONFIRMED",
                    "action": "UPDATE"
                },
                {
                    "situation": "FOUND",
                    "action": "UPDATE"
                },
                {
                    "situation": "ABSENT",
                    "action": "CREATE"
                },
                {
                    "situation": "AMBIGUOUS",
                    "action": "EXCEPTION"
                },
                {
                    "situation": "MISSING",
                    "action": "EXCEPTION"
                },
                {
                    "situation": "UNQUALIFIED",
                    "action": "UNLINK"
                },
                {
                    "situation": "UNASSIGNED",
                    "action": "EXCEPTION"
                }
            ]
        }
    ]
}

The following list shows all the properties that you can use as hooks in mapping configurations to call scripts.

Triggered by Situation

onCreate, onUpdate, onDelete, onLink, onUnlink

Object Filter

vaildSource, validTarget

Correlating Objects

correlationQuery

Triggered on Reconciliation

result

Scripts Inside Properties

condition, transform

Your scripts can get data from any connected system at any time by using the openidm.read(id) function, where id is the identifier of the object to read.

The following example reads a managed user object from the repository.

repoUser = openidm.read("managed/user/ddoe);

The following example reads an account from an external LDAP resource.

externalAccount = openidm.read("system/ldap/account/uid=ddoe,ou=People,dc=example,dc=com");

Note that the query targets a DN rather than a UID, as it did in the previous example. The attribute that is used for the _id is defined in the connector configuration file and, in this example, is set to "uidAttribute" : "dn". Although it is possible to use a DN (or any unique attribute) for the _id, as a best practice, you should use an attribute that is both unique and immutable.

10.12. Scheduling Synchronization

You can schedule synchronization operations, such as LiveSync and reconciliation, using cron-like syntax.

This section describes scheduling for reconciliation and LiveSync, however, you can also use OpenIDM's scheduler service to schedule any other event by supplying a link to a script file, in which that event is defined. For information about scheduling other events, and for a deeper understanding of the OpenIDM scheduler service, see Scheduling Tasks and Events.

10.12.1. Configuring Scheduled Synchronization

Each scheduled reconciliation and LiveSync task requires a schedule configuration file in opendidm/conf. By convention, files are named openidm/conf/schedule-schedule-name .json, where schedule-name is a logical name for the scheduled synchronization operation, such as reconcile_systemXmlAccounts_managedUser.

Schedule configuration files have the following format:

{
 "enabled"       : true,
 "persisted"     : false,
 "type"          : "cron",
 "startTime"     : "(optional) time",
 "endTime"       : "(optional) time",
 "schedule"      : "cron expression",
 "misfirePolicy" : "optional, string",
 "timeZone"      : "(optional) time zone",
 "invokeService" : "service identifier",
 "invokeContext" : "service specific context info"
}

For an explanation of each of these properties, see Scheduling Tasks and Events.

To schedule a reconciliation or LiveSync task, set the invokeService property to either "sync" (for reconciliation) or "provisioner" for LiveSync.

The value of the invokeContext property depends on the type of scheduled event. For reconciliation, the properties are set as follows:

{
    "invokeService": "sync",
    "invokeContext": {
        "action": "reconcile",
        "mapping": "systemLdapAccount_managedUser"
    }
}

The "mapping" is either referenced by its name in the openidm/conf/sync.json file, or defined inline by using the "mapping" property, as shown in the example in Alternative Mappings.

For LiveSync, the properties are set as follows:

{
    "invokeService": "provisioner",
    "invokeContext": {
        "action": "liveSync",
        "source": "system/OpenDJ/__ACCOUNT__"
    }
}

The "source" property follows OpenIDM's convention for a pointer to an external resource object and takes the form system/resource-name/ object-type.

10.12.2. Alternative Mappings

Mappings for synchronization are usually stored in openidm/conf/sync.json for reconciliation, LiveSync, and for pushing changes made to managed objects to external resources. You can, however, provide alternative mappings for scheduled reconciliation by adding the mapping to the schedule configuration instead of referencing a mapping in sync.json.

{
    "enabled": true,
    "type": "cron",
    "schedule": "0 08 16 * * ?",
    "invokeService": "sync",
    "invokeContext": {
        "action": "reconcile",
        "mapping": {
            "name": "CSV_XML",
            "source": "system/Ldap/account",
            "target": "managed/user",
            "properties": [
                {
                    "source": "firstname",
                    "target": "firstname"
                },
                ...
            ],
            "policies": [...]
        }
    }
}