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
andldap2
.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 theou=People
subtree.A mapping from IDM managed users to the LDAP user objects at the
system/ldap2/account
endpoint. This endpoint represents theou=Customers
subtree.
Both mappings include an explicit mapping from
ldapPassword
andldap2Password
touserPassword
in the standard property mappings. Because these passwords are encrypted, a transform script is defined which usesopenidm.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 thesystem/ldap/accounts
target. This field is subject to the standard policies associated with thepassword
field of a managed user. In addition, theldapPassword
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 thesystem/ldap2/accounts
target. This field is subject to the standard policies associated with thepassword
field of a managed user. In addition, theldap2Password
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 fieldsldapPassword
, andldap2Password
.
router.json
A scripted filter on
managed/user
andpolicy/managed/user
that populates the values of the additional password fields with the value of the mainpassword
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
andonUpdate-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 mainpassword
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 standardonCreate
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 itsadditionalFiles
list, so that the policy service loads the policy definition. The new policy takes ahistoryLength
parameter, which indicates the number of historical values to enforce the policy on. This number must not exceed thehistorySize
specified in theonCreate
andonUpdate
scripts.The
ldapPassword
andldap2Password
fields in the managed user schema have been updated with the policy. For the purposes of this sample thehistorySize
has been set to 2 forldapPassword
and to 4 forldap2Password
.
LDAP Server Configuration
Set up DS using
/path/to/openidm/samples/multiple-passwords/data/Example.ldif
.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
andou=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.
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
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.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".
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 inou=Customers
.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
Patch jdoe's managed user entry (
5ce188f6-252b-429e-aad1-4d8754d77de5
) to change hisldapPassword
: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..." } } }, ... }
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
or3
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.
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 containsTTestw0rd3
,TTestw0rd2
,TTestw0rd1
, andTTestw0rd
, in that order.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.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.