Store Multiple Passwords For Managed Users

This sample demonstrates how to set up multiple passwords for managed users and how to synchronize separate passwords to different external resources.

Note

You cannot run this sample through the Admin UI. To make the sample work with the Admin UI, set the viewable and required fields of the password property in the conf/managed.json file as follows:

"password" : {
    "title" : "Password",
    "type" : "string",
    "viewable" : true,
...

Configure the Multiple Passwords Sample

This sample assumes the following scenario:

  • The managed/user repository is the source system.

  • There are two target LDAP servers — ldap and ldap2.

    For the purposes of this sample, the two servers are represented by two separate organizational units on a single ForgeRock Directory Services (DS) instance.

  • Managed user objects have two additional password fields, each mapped to one of the two LDAP servers.

  • Both LDAP servers have a requirement for a password history policy, but the history size differs for the two policies.

    The sample shows how to extend the password history policy, described in "Creating a Password History Policy", to apply to multiple password fields.

  • The value of a managed user's password field is used by default for the additional passwords unless the CREATE, UPDATE, or PATCH requests on the managed user explicitly contain a value for these additional passwords.

The sample includes several customized configuration files in the samples/multiple-passwords/conf/ directory. These customizations are crucial to the sample functionality, and are described in detail in the following list:

provisioner.openicf-ldap.json

Configures the connection to the first LDAP directory.

provisioner.openicf-ldap2.json

Configures the connection to the second LDAP directory.

sync.json

Provides the mappings from the IDM managed user repository to the respective LDAP servers. The file includes two mappings:

  • A mapping from IDM managed users to the LDAP user objects at the system/ldap/account endpoint. This endpoint represents the ou=People subtree.

  • A mapping from IDM managed users to the LDAP user objects at the system/ldap2/account endpoint. This endpoint represents the ou=Customers subtree.

Both mappings include an explicit mapping from ldapPassword and ldap2Password to userPassword in the standard property mappings. Because these passwords are encrypted, a transform script is defined which uses openidm.decrypt() to set the value on the target object.

managed.json

Contains a customized schema for managed users that includes the additional password fields.

This file has been customized as follows:

  • The schema includes an ldapPassword field that is mapped to the accounts in the system/ldap/accounts target. This field is subject to the standard policies associated with the password field of a managed user. In addition, the ldapPassword must contain two capital letters instead of the usual one capital letter requirement.

  • The schema includes an ldap2Password field that is mapped to the accounts in the system/ldap2/accounts target. This field is subject to the standard policies associated with the password field of a managed user. In addition, the ldap2Password must contain two numbers instead of the usual one number requirement.

  • A custom password history policy ("policyId" : "is-new") applies to each of the two mapped password fields ldapPassword, and ldap2Password.

router.json

A scripted filter on managed/user and policy/managed/user that populates the values of the additional password fields with the value of the main password field if the additional fields are not included in the request content.

The sample includes the following customized scripts in the script directory:

  • onCreate-user-custom.js and onUpdate-user-custom.js are used for validation of the password history policy when a user is created or updated.

  • pwpolicy.js is an additional policy script for the password history policy.

  • set-additional-passwords.js populates the values of the additional password fields with the value of the main password field if the additional fields are not included in the request content.

Password History Policy

The sample includes a custom password history policy. Although the sample demonstrates the history of password attributes only, you can use this policy to enforce history validation on any managed object property.

The following configuration changes set up the password history policy:

  • A fieldHistory property is added to managed users. The value of this field is a map of field names to a list of historical values for that field. These lists of values are used by the policy to determine if a new value has previously been used.

    The fieldHistory property is not accessible over REST by default, and cannot be modified.

  • The onCreate-user-custom.js script performs the standard onCreate tasks for a managed user object but also stores the initial value of each of the fields for which IDM should keep a history. The script is passed the following configurable properties:

    • historyFields — a list of the fields to store history on.

    • historySize — the number of historical fields to store.

  • The onUpdate-user-custom.js script compares the old and new values of the historical fields on update events to determine if the values have changed. When a new value is detected, it is stored in the list of historical values for that field.

    This script also contains logic to deal with the comparison of encrypted field values. The script is passed the following configurable properties:

    • historyFields — a list of the fields to store history on.

    • historySize — the number of historical fields to store.

  • The pwpolicy.js script contains the additional policy definition for the password history policy. This script compares the new field value with the list of historical values for each field.

    The policy configuration (policy.json) references this script in its additionalFiles list, so that the policy service loads the policy definition. The new policy takes a historyLength parameter, which indicates the number of historical values to enforce the policy on. This number must not exceed the historySize specified in the onCreate and onUpdate scripts.

  • The ldapPassword and ldap2Password fields in the managed user schema have been updated with the policy. For the purposes of this sample the historySize has been set to 2 for ldapPassword and to 4 for ldap2Password.

LDAP Server Configuration

  1. Set up DS using /path/to/openidm/samples/multiple-passwords/data/Example.ldif.

  2. Perform an ldapsearch on the LDAP directory, and take note of the organizational units:

    /path/to/opendj/bin/ldapsearch \
    --port 1636 \
    --useSSL \
    --usePkcs12TrustStore /path/to/opendj/config/keystore \
    --trustStorePasswordFile /path/to/opendj/config/keystore.pin \
    --hostname localhost \
    --baseDN "dc=example,dc=com" \
    --bindDN uid=admin \
    --bindPassword password \
    "ou=*" \
    ou
    dn: ou=People,dc=example,dc=com
    ou: People
    
    dn: ou=Customers,dc=example,dc=com
    ou: people
    ou: Customers

    The organizational units, ou=People and ou=Customers, represent the two different target LDAP systems that our mappings point to.

Show Multiple Accounts

This section starts IDM with the sample configuration, then creates a user with multiple passwords, adhering to the different policies in the configured password policy. The section tests that the user was synchronized to two separate LDAP directories, with the different required passwords, and that the user can bind to each of these LDAP directories.

  1. Prepare IDM as described in "Prepare IDM", then start the server with the configuration for the multiple passwords sample:

    cd /path/to/openidm/
    ./startup.sh -p samples/multiple-passwords
  2. Create a user, jdoe, providing individual values for each of the different password fields, that comply with the three different password policies:

    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 '{
      "userName": "jdoe",
      "givenName": "John",
      "sn": "Doe",
      "displayName": "John Doe",
      "mail": "john.doe@example.com",
      "password": "Secretpw1",
      "ldapPassword": "S3cretPw",
      "ldap2Password": "Secr3tpw1"
    }' \
    "http://localhost:8080/openidm/managed/user?_action=create"
    {
      "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
      "_rev": "00000000d2d76089",
      "userName": "jdoe",
      "givenName": "John",
      "sn": "Doe",
      "displayName": "John Doe",
      "mail": "john.doe@example.com",
      "ldapPassword": {
        "$crypto": {
          "type": "x-simple-encryption",
          "value": {
            "cipher": "AES/CBC/PKCS5Padding",
            "stableId": "openidm-sym-default",
            "salt": "lkackh...",
            "data": "T0mljk...",
            "keySize": 16,
            "purpose": "idm.password.encryption",
            "iv": "ehSMbdNn...",
            "mac": "PssPOsW..."
          }
        }
      },
      "ldap2Password": {
        "$crypto": {
          "type": "x-simple-encryption",
          "value": {
            "cipher": "AES/CBC/PKCS5Padding",
            "stableId": "openidm-sym-default",
            "salt": "lSzMTU54...",
            "data": "UWlQo5Ws...",
            "keySize": 16,
            "purpose": "idm.password.encryption",
            "iv": "ehSMbdN...",
            "mac": "PssPOs..."
          }
        }
      },
      "accountStatus": "active",
      "effectiveRoles": [],
      "effectiveAssignments": [],
      "roles": []
    }

    The user has been created with three different passwords that comply with three distinct password policies. The passwords have been encrypted as defined in the managed.json file.

    Note that in this example, the user has been created with ID 5ce188f6-252b-429e-aad1-4d8754d77de5. You will need the user ID when you update the entry later in this procedure.

  3. As a result of implicit synchronization, two separate LDAP accounts should have been created for user jdoe on our two simulated LDAP servers. For more information about implicit synchronization, see "Types of Synchronization".

  4. Query the IDs in the LDAP directory 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/system/ldap/account?_queryId=query-all-ids"
    {
      "result": [
        {
          "_id": "00452010-a164-4065-9f84-3e4636a3ee20",
        },
        {
          "_id": "e5b35587-2d7c-4faa-b3e5-962f5a4ada5c",
        }
      ],
      ...
    }

    Note that jdoe has two entries—one in ou=People and one in ou=Customers.

  5. To verify the passwords were propagated correctly, perform an LDAP search, bound using each of the jdoe accounts, against the rootDSE.

    Note

    For the following commands, make sure to enter 2 or 3 at the following prompt:

    Do you trust this server certificate?
    
      1) No
      2) Yes, for this session only
      3) Yes, also add it to a truststore
      4) View certificate details
    
    Enter choice: [1]: 2
    /path/to/opendj/bin/ldapsearch \
    --hostname localhost \
    --port 1636 \
    --useSSL \
    --bindDN uid=jdoe,ou=People,dc=example,dc=com \
    --bindPassword S3cretPw \
    --searchScope base \
    --baseDN "" "(objectClass=*)"
    dn:
    objectClass: top
    objectClass: ds-root-dse
    /path/to/opendj/bin/ldapsearch \
    --hostname localhost \
    --port 1636 \
    --useSSL \
    --bindDN uid=jdoe,ou=Customers,dc=example,dc=com \
    --bindPassword Secr3tpw1 \
    --searchScope base \
    --baseDN "" "(objectClass=*)"
    dn:
    objectClass: top
    objectClass: ds-root-dse
  6. Patch jdoe's managed user entry (5ce188f6-252b-429e-aad1-4d8754d77de5) to change his ldapPassword:

    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": "ldapPassword",
      "value": "TTestw0rd"
    } ]' \
    "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
    {
      "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
      "_rev": "000000001298f6a6",
      "userName": "jdoe",
      "givenName": "John",
      "sn": "Doe",
      "displayName": "John Doe",
      ...
      "ldapPassword": {
        "$crypto": {
          "type": "x-simple-encryption",
          "value": {
            "cipher": "AES/CBC/PKCS5Padding",
            "stableId": "openidm-sym-default",
            "salt": "Vlco8e...",
            "data": "INj9lk...",
            "keySize": 16,
            "purpose": "idm.password.encryption",
            "iv": "ehSMbdNn...",
            "mac": "PssPOsW..."
          }
        }
      },
      ...
    }
  7. To verify the password change propagated correctly, perform an LDAP search, bound using jdoe from the People organizational unit, against the rootDSE.

    Note

    For the following command, make sure to enter 2 or 3 at the following prompt:

    Do you trust this server certificate?
    
      1) No
      2) Yes, for this session only
      3) Yes, also add it to a truststore
      4) View certificate details
    
    Enter choice: [1]: 2
    /path/to/opendj/bin/ldapsearch \
    --hostname localhost \
    --port 1636 \
    --useSSL \
    --bindDN uid=jdoe,ou=People,dc=example,dc=com \
    --bindPassword TTestw0rd \
    --searchScope base \
    --baseDN "" "(objectClass=*)"
    dn:
    objectClass: top
    objectClass: ds-root-dse

Show the Password History Policy

This section demonstrates the password history policy by patching jdoe's managed user entry, changing his ldapPassword multiple times.

  1. Send the following patch requests, changing the value of jdoe's ldapPassword each time:

    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": "ldapPassword",
      "value": "TTestw0rd1"
    } ]' \
    "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
    {
      "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
      "_rev": "00000000a92657c7",
      "userName": "jdoe",
      "givenName": "John",
      "sn": "Doe",
      "displayName": "John Doe",
      "mail": "john.doe@example.com",
      ...
      "ldapPassword": {
        "$crypto": {
          "type": "x-simple-encryption",
          "value": {
            "cipher": "AES/CBC/PKCS5Padding",
            "stableId": "openidm-sym-default",
            "salt": "TjolL7...",
            "data": "Unbalo...",
            "keySize": 16,
            "purpose": "idm.password.encryption",
            "iv": "ehSMbdNn...",
            "mac": "PssPOsW..."
          }
        }
      },
      ...
    }
    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": "ldapPassword",
      "value": "TTestw0rd2"
    } ]' \
    "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
    {
      "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
      "_rev": "00000000dc6160c8",
      "userName": "jdoe",
      "givenName": "John",
      "sn": "Doe",
      "displayName": "John Doe",
      ...
      "ldapPassword": {
        "$crypto": {
          "type": "x-simple-encryption",
          "value": {
            "cipher": "AES/CBC/PKCS5Padding",
            "stableId": "openidm-sym-default",
            "salt": "Ynio9n...",
            "data": "R0ol2b...",
            "keySize": 16,
            "purpose": "idm.password.encryption",
            "iv": "ehSMbdNn...",
            "mac": "PssPOsW..."
          }
        }
      },
      ...
    }
    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": "ldapPassword",
      "value": "TTestw0rd3"
    } ]' \
    "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
    {
      "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
      "_rev": "00000000a92657c7",
      "userName": "jdoe",
      "givenName": "John",
      "sn": "Doe",
      "displayName": "John Doe",
      ...
      "ldapPassword": {
        "$crypto": {
          "type": "x-simple-encryption",
          "value": {
            "cipher": "AES/CBC/PKCS5Padding",
            "stableId": "openidm-sym-default",
            "salt": "9kilajT...",
            "data": "Hnkja98...",
            "keySize": 16,
            "purpose": "idm.password.encryption",
            "iv": "ehSMbdNn...",
            "mac": "PssPOsW..."
          }
        }
      },
      ...
    }

    User jdoe now has a history of ldapPassword values, that contains TTestw0rd3, TTestw0rd2, TTestw0rd1, and TTestw0rd, in that order.

  2. The history size for the ldapPassword policy is set to 2. To demonstrate the history policy, attempt to patch jdoe's entry with a password value that was used in his previous 2 password changes: TTestw0rd2:

    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": "ldapPassword",
      "value": "TTestw0rd2"
    } ]' \
    "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
    {
      "code": 403,
      "reason": "Forbidden",
      "message": "Failed policy validation",
      "detail": {
        "result": false,
        "failedPolicyRequirements": [
          {
            "policyRequirements": [
              {
                "policyRequirement": "IS_NEW"
              }
            ],
            "property": "ldapPassword"
          }
        ]
      }
    }

    The password change fails the IS_NEW policy requirement.

  3. Change jdoe's ldapPassword to a value not used in the previous two updates:

    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": "ldapPassword",
      "value": "TTestw0rd"
    } ]' \
    "http://localhost:8080/openidm/managed/user/5ce188f6-252b-429e-aad1-4d8754d77de5"
    {
      "_id": "5ce188f6-252b-429e-aad1-4d8754d77de5",
      "_rev": "00000000792afa08",
      "userName": "jdoe",
      "givenName": "John",
      "sn": "Doe",
      "displayName": "John Doe",
      ...
      "ldapPassword": {
        "$crypto": {
          "type": "x-simple-encryption",
          "value": {
            "cipher": "AES/CBC/PKCS5Padding",
            "stableId": "openidm-sym-default",
            "salt": "Ivmal5...",
            "data": "0mkywe...",
            "keySize": 16,
            "purpose": "idm.password.encryption",
            "iv": "ehSMbdNn...",
            "mac": "PssPOsW..."
          }
        }
      },
      ...
    }

    The password change succeeds.

Read a different version of :