Configuration Examples

Examples in this documentation depend on features activated in the ds-evaluation setup profile.

Custom Object

The example REST to LDAP mapping configuration file, config/rest2ldap/endpoints/api/example-v1.json, works well with the ds-evaluation setup profile. For most deployments, you must customize the mapping to expose additional information.

This example demonstrates how to configure an additional mapping for a custom LDAP object called affiliateObject, whose affiliate attribute references user entries. This demonstration extends a server set up with the ds-evaluation profile. The mappings will also work if you are using the REST to LDAP gateway.

To begin, add the schema for the custom LDAP object, and an example LDAP entry with the object class:

$ ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN uid=admin \
 --bindPassword password << EOF
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( affiliate-oid  NAME 'affiliate' SUP distinguishedName X-SCHEMA-FILE '99-example-mods.ldif' )
-
add: objectClasses
objectClasses: ( affiliateObject-oid NAME 'affiliateObject' SUP top STRUCTURAL MUST cn MAY ( description $ affiliate ) X-SCHEMA-FILE '99-example-mods.ldif' )
EOF

$ ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN "uid=kvaughan,ou=people,dc=example,dc=com" \
 --bindPassword bribery << EOF
dn: ou=affiliates,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: affiliates

dn: cn=My Affiliates,ou=affiliates,dc=example,dc=com
objectClass: top
objectClass: affiliateObject
cn: My Affiliates
affiliate: uid=bjensen,ou=People,dc=example,dc=com
affiliate: uid=kvaughan,ou=People,dc=example,dc=com
EOF

For details, see LDAP Schema and Add.

In the REST to LDAP mapping configuration file, define an affiliates collection subresource, and an examples:affiliates:1.0 resource type that describes the objects in the affiliates collection. This causes the REST API to add an /api/affiliates path next to /api/users and /api/groups. Under /api/affiliates, REST clients access the affiliate JSON resources.

The following definition for the affiliates collection subresource belongs in the set of subResources in the mapping configuration file:

// This sub-resource definition maps JSON resources under /api/affiliates
// to LDAP entries under ou=affiliates,dc=example,dc=com.
"affiliates" : {
    "type": "collection",
    "dnTemplate": "ou=affiliates,dc=example,dc=com",
    "resource": "examples:affiliates:1.0",
    "namingStrategy": {
        "type": "clientDnNaming",
        "dnAttribute": "cn"
    }
}

The parser for the REST to LDAP mapping configuration file is lenient. It lets you include comments in the JSON, although the JSON standard does not allow comments.

The following definition for the examples:affiliates:1.0 resource type belongs in the set of example-v1 resource types in the mapping configuration file:

// An "affiliate" resource includes property mappings for an "affiliateObject",
// which is identified by the "cn" attribute. The affiliate DNs reference person entries.
// Rather than return DNs in JSON resources, a mapper returns identifiers and display names.
"examples:affiliates:1.0": {
    "superType": "frapi:opendj:rest2ldap:object:1.0",
    "objectClasses": [ "affiliateObject" ],
    "properties": {
        "displayName": {
            "type": "simple",
            "ldapAttribute": "cn",
            "isRequired": true,
            "writability": "createOnly"
        },
        "affiliate": {
            "type": "reference",
            "resourcePath": "../../users",
            "ldapAttribute": "affiliate",
            "isMultiValued": true
        },
        "description": {
            "type": "simple"
        }
    }
}

Alternatively, download example-v1.json to replace the existing file.

Replace the directory configuration file, config/rest2ldap/endpoints/api/example-v1.json, with the updated mapping configuration file. If you have not already done so, enable HTTP access. For details, see Configure HTTP User APIs. The following example forces the Rest2ldap endpoint to reread its configuration:

$ dsconfig \
 set-http-endpoint-prop \
 --hostname localhost \
 --port 4444 \
 --bindDN uid=admin \
 --bindPassword password \
 --endpoint-name "/api" \
 --set enabled:false \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --no-prompt

$ dsconfig \
 set-http-endpoint-prop \
 --hostname localhost \
 --port 4444 \
 --bindDN uid=admin \
 --bindPassword password \
 --endpoint-name "/api" \
 --set enabled:true \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --no-prompt

With the updated REST to LDAP mapping configuration loaded, REST clients can access affiliates:

$ curl \
 --cacert ca-cert.pem \
 --user bjensen:hifalutin \
 --silent \
 "https://localhost:8443/api/affiliates/my%20affiliates?_fields=affiliate/id&_prettyPrint=true"

{
  "_id" : "My Affiliates",
  "_rev" : "<revision>",
  "affiliate" : [ {
    "_id" : "bjensen",
    "_rev" : "<revision>"
  }, {
    "_id" : "kvaughan",
    "_rev" : "<revision>"
  } ]
}

Per-Server Password Policies

This example demonstrates how to add a per-server password policy over REST. Per-server password policies are set in the server configuration, and not replicated. You must create them on each replica.

The password policy in this example includes:

  • A password history setting to retain the last five password values.

  • The same password storage scheme as the default password policy.

  • The default random password generator.

  • The default length-based password validator.

  • The default dictionary password validator, which is available, but not enabled by default.

With the default password policy, a user can change their password to password. A password policy with the default dictionary validator would not allow this:

$ curl \
 --request POST \
 --cacert ca-cert.pem \
 --user bjensen:hifalutin \
 --header "Content-Type: application/json" \
 --data '{"oldPassword": "hifalutin", "newPassword": "password"}' \
 --silent \
 "https://localhost:8443/api/users/bjensen?_action=modifyPassword&dryRun=true&passwordQualityAdvice=true"

{}

Update the server configuration to enable the password policy for all Example.com users:

  1. Enable the default dictionary password validator:

    $ curl \
     --request PATCH \
     --user admin:password \
     --data '[{"operation": "replace", "field": "/enabled", "value": true}]' \
     --cacert ca-cert.pem \
     --header "Content-Type: application/json" \
     --silent \
     "https://localhost:8443/admin/config/password-validators/Dictionary"
  2. Add the password policy:

    $ curl \
     --request POST \
     --user admin:password \
     --data '{
      "_id": "Per-Server Password Policy",
      "_schema": "password-policy",
      "password-attribute": "userPassword",
      "default-password-storage-scheme": [{"_id": "PBKDF2-HMAC-SHA256"}],
      "password-generator": { "_id": "Random Password Generator" },
      "password-validator": [{"_id": "Dictionary"}, {"_id": "Length-Based Password Validator"}],
      "password-history-count": 5
     }' \
     --cacert ca-cert.pem \
     --header "Content-Type: application/json" \
     --silent \
     "https://localhost:8443/admin/config/password-policies/"
  3. Assign the password policy to users:

    The following command adds a virtual attribute that assigns the password policy to all Example.com users:

    $ curl \
     --request POST \
     --user admin:password \
     --data '{
      "_id": "Password Policy Virtual Attribute",
      "_schema": "user-defined-virtual-attribute",
      "enabled": true,
      "base-dn": [ "ou=people,dc=example,dc=com" ],
      "filter": [ "(objectClass=person)" ],
      "attribute-type": "ds-pwp-password-policy-dn",
      "value": [ "cn=Per-Server Password Policy,cn=Password Policies,cn=config" ]
     }' \
     --cacert ca-cert.pem \
     --header "Content-Type: application/json" \
     --silent \
     "https://localhost:8443/admin/config/virtual-attributes/"

Check that the new policy does not let a user change their password to password:

$ curl \
 --request POST \
 --cacert ca-cert.pem \
 --user bjensen:hifalutin \
 --header "Content-Type: application/json" \
 --data '{"oldPassword": "hifalutin", "newPassword": "password"}' \
 --silent \
 "https://localhost:8443/api/users/bjensen?_action=modifyPassword&dryRun=true&passwordQualityAdvice=true"

{
  "code" : 400,
  "reason" : "Bad Request",
  "message" : "Constraint Violation: The provided new password failed the validation checks defined in the server: The provided password contained a word from the server's dictionary",
  "detail" : {
    "passwordQualityAdvice" : {
      "passingCriteria" : [ {
        "type" : "length-based",
        "parameters" : {
          "min-password-length" : 6,
          "max-password-length" : 0
        }
      } ],
      "failingCriteria" : [ {
        "type" : "dictionary",
        "parameters" : {
          "case-sensitive-validation" : false,
          "min-substring-length" : 5,
          "test-reversed-password" : true,
          "check-substrings" : true
        }
      } ]
    }
  }
}

For details on password policy settings, see Per-Server Password Policies.

Subentry Password Policies

This example demonstrates how to configure REST to LDAP to manage Internet-Draft subentry password policies. This demonstration extends a server set up with the ds-evaluation profile. The mappings will also work if you are using the REST to LDAP gateway.

To begin, edit the default REST to LDAP mapping file, config/rest2ldap/endpoints/api/example-v1.json.

Define an /api/subentries endpoint that exposes LDAP subentries over REST, including password policies, by adding the following under subResources:

// This subresource definition maps JSON resources under /api/subentries
// to subentries under dc=example,dc=com.
"subentries" : {
  "type": "collection",
  "dnTemplate": "dc=example,dc=com",
  "resource": "examples:subentry:1.0",
  "namingStrategy": {
    "type": "clientDnNaming",
    "dnAttribute": "cn"
  }
}

Define the subentry and password policy under the other example-v1 resource types:

// A subentry resource maps to a subentry object.
"examples:subentry:1.0": {
  "superType": "frapi:opendj:rest2ldap:object:1.0",
  "isAbstract": true,
  "objectClasses": [ "top", "subentry" ],
  "properties": {
    "_id": {
      "type": "simple",
      "ldapAttribute": "cn",
      "isRequired": true,
      "writability": "createOnly"
    },
    "subtreeSpecification": {
      "type": "simple"
    }
  }
},
// A passwordPolicy resource maps to a subentry password policy object.
// This mapping uses the LDAP attribute names,
// except for passwordValidator which maps to the validator in the configuration.
"examples:passwordPolicy:1.0": {
  "superType": "examples:subentry:1.0",
  "objectClasses": [ "pwdPolicy", "pwdValidatorPolicy", "subentry" ],
  "properties": {
    "pwdAttribute": {
      "type": "simple",
      "isRequired": true
    },
    "pwdAllowUserChange": {
      "type": "simple"
    },
    "pwdCheckQuality": {
      "type": "simple"
    },
    "pwdExpireWarning": {
      "type": "simple"
    },
    "pwdFailureCountInterval": {
      "type": "simple"
    },
    "pwdGraceAuthNLimit": {
      "type": "simple"
    },
    "pwdInHistory": {
      "type": "simple"
    },
    "pwdLockout": {
      "type": "simple"
    },
    "pwdLockoutDuration": {
      "type": "simple"
    },
    "pwdMaxAge": {
      "type": "simple"
    },
    "pwdMaxFailure": {
      "type": "simple"
    },
    "pwdMinAge": {
      "type": "simple"
    },
    "pwdMinLength": {
      "type": "simple"
    },
    "pwdMustChange": {
      "type": "simple"
    },
    "pwdSafeModify": {
      "type": "simple"
    },
    "passwordValidator" : {
      "type": "reference",
      "baseDn": "cn=Password Validators,cn=config",
      "ldapAttribute": "ds-cfg-password-validator",
      "primaryKey": "cn",
      "isMultiValued": true,
      "mapper": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "simple",
            "ldapAttribute": "cn",
            "isRequired": true
          }
        }
      }
    }
  }

In LDAP, you can edit user entries to assign password policies. For REST to LDAP, add corresponding password policy properties to the frapi:opendj:rest2ldap:user:1.0 resource type:

// Administrators can read the current password policy.
"pwdPolicy": {
  "type": "reference",
  "baseDn": "..,..",
  "ldapAttribute": "pwdPolicySubentry",
  "primaryKey": "cn",
  "searchFilter": "(&(objectclass=subentry)(objectclass=pwdPolicy))",
  "mapper": {
    "type": "object",
    "properties": {
      "_id": {
        "type": "simple",
        "ldapAttribute": "cn",
        "writability": "readOnlyDiscardWrites"
      }
    }
  }
},
// Administrators can set a new password policy.
"newPwdPolicy": {
  "type": "reference",
  "baseDn": "..,..",
  "ldapAttribute": "ds-pwp-password-policy-dn",
  "primaryKey": "cn",
  "searchFilter": "(&(objectclass=subentry)(objectclass=pwdPolicy))",
  "mapper": {
    "type": "object",
    "properties": {
      "_id": {
        "type": "simple",
        "ldapAttribute": "cn",
        "isRequired": true
      }
    }
  }
},

Alternatively, download subentries.json to replace the existing example-v1.json file.

Replace the directory configuration file, config/rest2ldap/endpoints/api/example-v1.json, with the updated mapping configuration file. If you have not already done so, enable HTTP access. For details, see Configure HTTP User APIs. The following example forces the Rest2ldap endpoint to reread its configuration:

$ dsconfig \
 set-http-endpoint-prop \
 --hostname localhost \
 --port 4444 \
 --bindDN uid=admin \
 --bindPassword password \
 --endpoint-name "/api" \
 --set enabled:false \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --no-prompt

$ dsconfig \
 set-http-endpoint-prop \
 --hostname localhost \
 --port 4444 \
 --bindDN uid=admin \
 --bindPassword password \
 --endpoint-name "/api" \
 --set enabled:true \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --no-prompt

With the augmented mapping in place, grant access for password administrators to edit and assign password policies:

  • Grant the necessary privileges to edit subentry password policies:

    • The subentry-write privilege lets password administrators edit subentries.

    • The config-read privilege lets password administrators reference password validator entries stored in the server configuration.

  • Grant access to assign password policies, letting password administrators read users' pwdPolicySubentry attributes and write users' ds-pwp-password-policy-dn attributes.

  • Grant access to assign password policies, letting password administrators write the subentry attributes, ds-pwp-password-validator and subtreeSpecification.

For this demonstration, grant access to the administrator user Kirsten Vaughan:

# Assign privileges:
$ ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN uid=admin \
 --bindPassword password  << EOF
dn: uid=kvaughan,ou=People,dc=example,dc=com
changetype: modify
add: ds-privilege-name
ds-privilege-name: config-read
EOF

$ ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN uid=admin \
 --bindPassword password << EOF
dn: uid=kvaughan,ou=People,dc=example,dc=com
changetype: modify
add: ds-privilege-name
ds-privilege-name: subentry-write
EOF

# Grant access to subentry policies
$ ldapmodify \
 --hostname localhost \
 --port 1636 \
 --useSsl \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --bindDN uid=admin \
 --bindPassword password << EOF
dn: dc=example,dc=com
changetype: modify
add: aci
aci: (targetattr = "pwdPolicySubentry||ds-pwp-password-policy-dn||ds-pwp-password-validator||subtreeSpecification")
 (version 3.0;acl "Allow Administrators to manage users' password policies";
 allow (all) (groupdn = "ldap:///cn=Directory Administrators,ou=Groups,dc=example,dc=com");)
EOF

# Grant access to configuration-based policy elements:
$ dsconfig \
 set-access-control-handler-prop \
 --hostname localhost \
 --port 4444 \
 --bindDN uid=admin \
 --bindPassword password \
 --add "global-aci:(target=\"ldap:///cn=config\")(targetattr=\"*||+\")\
(version 3.0; acl \"Config read for Kirsten Vaughan\"; allow (read,search,compare)\
userdn=\"ldap:///uid=kvaughan,ou=People,dc=example,dc=com\";)" \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --no-prompt

Create and assign a subentry password policy:

$ curl \
 --request PUT \
 --cacert ca-cert.pem \
 --user kvaughan:bribery \
 --header "Content-Type: application/json" \
 --header "If-None-Match: *" \
 --data '{
  "_id": "Subentry Password Policy with Validators",
  "_schema": "examples:passwordPolicy:1.0",
  "pwdAttribute": "userPassword",
  "pwdAllowUserChange": true,
  "pwdFailureCountInterval": 300,
  "pwdLockout": true,
  "pwdLockoutDuration": 300,
  "pwdMaxFailure": 3,
  "pwdSafeModify": true,
  "passwordValidator": [{"_id": "Character Set"}, {"_id": "Length-Based Password Validator"}],
  "subtreeSpecification": "{base \"ou=people\", specificationFilter \"(isMemberOf=cn=Directory Administrators,ou=Groups,dc=example,dc=com)\" }"
 }' \
 --silent \
 https://localhost:8443/api/subentries/Subentry%20Password%20Policy%20with%20Validators?_prettyPrint=true

Observe that the password administrator can view which password policy applies:

$ curl \
 --cacert ca-cert.pem \
 --user kvaughan:bribery \
 --silent \
 "https://localhost:8443/api/users/kvaughan?_fields=pwdPolicy&_prettyPrint=true"

{
  "_id" : "kvaughan",
  "_rev" : "<revision>",
  "pwdPolicy" : {
    "_id" : "Subentry Password Policy with Validators"
  }
}

Observe that the field is not visible to regular users:

$ curl \
 --cacert ca-cert.pem \
 --user bjensen:hifalutin \
 --silent \
 "https://localhost:8443/api/users/bjensen?_fields=pwdPolicy&_prettyPrint=true"

{
  "_id" : "bjensen",
  "_rev" : "<revision>"
}

Map LDAP Entries

This example demonstrates how to map an additional existing resource at the root of the REST API.

For example, the ds-evaluation setup profile includes localities:

dn: l=Bristol,ou=Locations,dc=example,dc=com
objectClass: top
objectClass: locality
l: Bristol
street: Broad Quay House, Prince Street

dn: l=Montbonnot,ou=Locations,dc=example,dc=com
objectClass: top
l: Montbonnot
street: 55 Rue Blaise Pascal

dn: l=Lysaker,ou=Locations,dc=example,dc=com
objectClass: top
objectClass: locality
l: Lysaker
street: Lysaker Torg 2

dn: l=San Francisco,ou=Locations,dc=example,dc=com
objectClass: top
objectClass: locality
l: San Francisco
street: 201 Mission Street Suite 2900

dn: l=Vancouver,ou=Locations,dc=example,dc=com
objectClass: top
objectClass: locality
l: Vancouver
street: 201 Northeast Park Plaza Drive

This demonstration adds an /api/locations next to /api/users and /api/groups.

Edit your copy of the default REST API mapping configuration. Add a location subresource at the top level next to users and groups subresource definitions:

"locations": {
    "type": "collection",
    "dnTemplate": "ou=locations,dc=example,dc=com",
    "resource": "frapi:opendj:rest2ldap:location:1.0",
    "namingStrategy": {
        "type": "clientDnNaming",
        "dnAttribute": "l"
    }
}

Add a location schema after the other schema definitions:

"frapi:opendj:rest2ldap:location:1.0": {
    "superType": "frapi:opendj:rest2ldap:object:1.0",
    "objectClasses": ["locality"],
    "properties": {
        "_id": {
            "type": "simple",
            "ldapAttribute": "l",
            "isRequired": true,
            "writability": "createOnly"
        },
        "displayName": {
            "type": "simple",
            "ldapAttribute": "l",
            "isRequired": true,
            "writability": "readOnly"
        },
        "description": {
            "type": "simple"
        },
        "state": {
            "type": "simple",
            "ldapAttribute": "st"
        },
        "street": {
            "type": "simple"
        }
    }
}

Alternatively, download example-locations.json to replace the existing example-v1.json file.

Replace the directory configuration file, config/rest2ldap/endpoints/api/example-v1.json, with the updated mapping configuration file. If you have not already done so, enable HTTP access. For details, see Configure HTTP User APIs. The following example forces the Rest2ldap endpoint to reread its configuration:

$ dsconfig \
 set-http-endpoint-prop \
 --hostname localhost \
 --port 4444 \
 --bindDN uid=admin \
 --bindPassword password \
 --endpoint-name "/api" \
 --set enabled:false \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --no-prompt

$ dsconfig \
 set-http-endpoint-prop \
 --hostname localhost \
 --port 4444 \
 --bindDN uid=admin \
 --bindPassword password \
 --endpoint-name "/api" \
 --set enabled:true \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --no-prompt

With the updated configuration loaded, view the results:

# List locations:
$ curl \
 --cacert ca-cert.pem \
 --user bjensen:hifalutin \
 --silent \
 "https://localhost:8443/api/locations/?_queryFilter=true&_prettyPrint=true"

{
  "result" : [ {
    "_id" : "Bristol",
    "_rev" : "<revision>",
    "_schema" : "frapi:opendj:rest2ldap:location:1.0",
    "displayName" : "Bristol",
    "street" : "Broad Quay House, Prince Street"
  }, {
    "_id" : "Montbonnot",
    "_rev" : "<revision>",
    "_schema" : "frapi:opendj:rest2ldap:location:1.0",
    "displayName" : "Montbonnot",
    "street" : "55 Rue Blaise Pascal"
  }, {
    "_id" : "Lysaker",
    "_rev" : "<revision>",
    "_schema" : "frapi:opendj:rest2ldap:location:1.0",
    "displayName" : "Lysaker",
    "street" : "Lysaker Torg 2"
  }, {
    "_id" : "San Francisco",
    "_rev" : "<revision>",
    "_schema" : "frapi:opendj:rest2ldap:location:1.0",
    "displayName" : "San Francisco",
    "street" : "201 Mission Street Suite 2900"
  }, {
    "_id" : "Vancouver",
    "_rev" : "<revision>",
    "_schema" : "frapi:opendj:rest2ldap:location:1.0",
    "displayName" : "Vancouver",
    "street" : "201 Northeast Park Plaza Drive"
  } ],
  "resultCount" : 5,
  "pagedResultsCookie" : null,
  "totalPagedResultsPolicy" : "NONE",
  "totalPagedResults" : -1,
  "remainingPagedResults" : -1
}

# Read a single location:
$ curl \
 --cacert ca-cert.pem \
 --user bjensen:hifalutin \
 --silent \
 "https://localhost:8443/api/locations/montbonnot?_prettyPrint=true"

{
  "_id" : "Montbonnot",
  "_rev" : "<revision>",
  "_schema" : "frapi:opendj:rest2ldap:location:1.0",
  "displayName" : "Montbonnot",
  "street" : "55 Rue Blaise Pascal"
}

Nested Resources

This example demonstrates how to add device resources under user resources. Use this pattern when you have LDAP child entries under parent entries.

For example, the ds-evaluation setup profile includes a user with two subordinate devices:

dn: uid=nbohr,ou=People,dc=example,dc=com
objectClass: person
objectClass: cos
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: posixAccount
objectClass: top
uid: nbohr
classOfService: gold
userpassword: password
facsimileTelephoneNumber: +1 408 555 1213
givenName: Niels
cn: Niels Bohr
telephoneNumber: +1 408 555 1212
sn: Bohr
roomNumber: 0007
homeDirectory: /home/nbohr
mail: nbohr@example.com
l: Vancouver
ou: People
uidNumber: 1111
gidNumber: 1000
description: Quantum device example

dn: cn=quantum dot,uid=nbohr,ou=People,dc=example,dc=com
objectClass: device
objectClass: top
cn: quantum dot
serialNumber: WI-3005
owner: uid=nbohr,ou=People,dc=example,dc=com

dn: cn=qubit generator,uid=nbohr,ou=People,dc=example,dc=com
objectClass: device
objectClass: top
cn: qubit generator
serialNumber: XF551426
owner: uid=nbohr,ou=People,dc=example,dc=com

In your REST API, the users collection contains zero or more user resources. Each user resource can have a devices collection that contains zero or more device resources. The path to a device is /users/uid/devices/cn, where uid is the user UID, and cn is the device CN. For example, /users/nbohr/devices/quantum dot.

Edit your copy of the default REST API mapping configuration. Add the subresources definition to the frapi:opendj:rest2ldap:user:1.0 schema:

"subResources": {
    "devices": {
        "type": "collection",
        "resource": "frapi:opendj:rest2ldap:device:1.0",
        "namingStrategy": {
            "type": "clientDnNaming",
            "dnAttribute": "cn"
        }
    }
}

Add a device schema after the other schema definitions:

"frapi:opendj:rest2ldap:device:1.0": {
    "superType": "frapi:opendj:rest2ldap:object:1.0",
    "objectClasses": ["device"],
    "properties": {
        "_id": {
            "type": "simple",
            "ldapAttribute": "cn",
            "isRequired": true,
            "writability": "createOnly"
        },
        "displayName": {
            "type": "simple",
            "ldapAttribute": "cn",
            "isRequired": true,
            "writability": "readOnly"
        },
        "description": {
            "type": "simple"
        },
        "owner": {
            "type": "reference",
            "resourcePath": "../..",
            "ldapAttribute": "owner"
        },
        "serialNumber": {
            "type": "simple"
        }
    }
}

Alternatively, download example-subresources.json to replace the default example-v1.json file.

Replace the directory configuration file, config/rest2ldap/endpoints/api/example-v1.json, with the updated mapping configuration file. If you have not already done so, enable HTTP access. For details, see Configure HTTP User APIs. The following example forces the Rest2ldap endpoint to reread its configuration:

$ dsconfig \
 set-http-endpoint-prop \
 --hostname localhost \
 --port 4444 \
 --bindDN uid=admin \
 --bindPassword password \
 --endpoint-name "/api" \
 --set enabled:false \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --no-prompt

$ dsconfig \
 set-http-endpoint-prop \
 --hostname localhost \
 --port 4444 \
 --bindDN uid=admin \
 --bindPassword password \
 --endpoint-name "/api" \
 --set enabled:true \
 --usePkcs12TrustStore /path/to/opendj/config/keystore \
 --trustStorePassword:file /path/to/opendj/config/keystore.pin \
 --no-prompt

With the updated configuration loaded, view the results:

# Read the user resource:
$ curl \
 --cacert ca-cert.pem \
 --user nbohr:password \
 --silent \
 "https://localhost:8443/api/users/nbohr?_prettyPrint=true"

{
  "_id" : "nbohr",
  "_rev" : "<revision>",
  "_schema" : "frapi:opendj:rest2ldap:posixUser:1.0",
  "userName" : "nbohr@example.com",
  "displayName" : [ "Niels Bohr" ],
  "name" : {
    "givenName" : "Niels",
    "familyName" : "Bohr"
  },
  "description" : "Quantum device example",
  "contactInformation" : {
    "telephoneNumber" : "+1 408 555 1212",
    "emailAddress" : "nbohr@example.com"
  },
  "uidNumber" : 1111,
  "gidNumber" : 1000,
  "homeDirectory" : "/home/nbohr"
}

# List devices:
$ curl \
 --cacert ca-cert.pem \
 --user nbohr:password \
 --silent \
 "https://localhost:8443/api/users/nbohr/devices/?_queryFilter=true&_prettyPrint=true"

{
  "result" : [ {
    "_id" : "quantum dot",
    "_rev" : "<revision>",
    "_schema" : "frapi:opendj:rest2ldap:device:1.0",
    "displayName" : "quantum dot",
    "owner" : {
      "_id" : "nbohr",
      "_rev" : "<revision>"
    },
    "serialNumber" : "WI-3005"
  }, {
    "_id" : "qubit generator",
    "_rev" : "<revision>",
    "_schema" : "frapi:opendj:rest2ldap:device:1.0",
    "displayName" : "qubit generator",
    "owner" : {
      "_id" : "nbohr",
      "_rev" : "<revision>"
    },
    "serialNumber" : "XF551426"
  } ],
  "resultCount" : 2,
  "pagedResultsCookie" : null,
  "totalPagedResultsPolicy" : "NONE",
  "totalPagedResults" : -1,
  "remainingPagedResults" : -1
}

# Read a device resource:
$ curl \
 --cacert ca-cert.pem \
 --user nbohr:password \
 --silent \
 "https://localhost:8443/api/users/nbohr/devices/quantum%20dot?_prettyPrint=true"

{
  "_id" : "quantum dot",
  "_rev" : "000000007ba330d8",
  "_schema" : "frapi:opendj:rest2ldap:device:1.0",
  "displayName" : "quantum dot",
  "owner" : {
    "_id" : "nbohr",
    "_rev" : "<revision>"
  },
  "serialNumber" : "WI-3005"
}m

JSON to LDAP

This example demonstrates simple user profiles, starting from JSON and working back to LDAP. The following JSON is a example profile:

{
    "externalId": "newuser",
    "userName": "newuser",
    "name": {
        "formatted": "New User",
        "givenName": "User",
        "familyName": "New"
    },
    "phoneNumbers": [{
        "value": "+1 408 555 1212",
        "type": "work"
    }],
    "emails": [
        {
            "value": "newuser@example.com",
            "type": "work",
            "primary": true
        },
        {
            "value": "newuser@example.org",
            "type": "personal",
            "primary": false
        }
    ]
}

Notice these key features of the example profile:

  • Unlike Common REST JSON objects, this profile has no "_id" field.

  • The "phoneNumbers" and "emails" fields are multi-valued, similar to the LDAP telephoneNumber and mail attributes.

    However, both JSON fields hold arrays of nested objects. The similar LDAP attributes hold simple values. The other fields have a straightforward mapping to standard LDAP attributes, but these fields do not.

    The "type" field in a phone number or email is an arbitrary label assigned by the user.

Define the LDAP schema

This example assumes that the user profile does not exist in LDAP yet. The aim is therefore to translate JSON objects into LDAP entries.

LDAP attributes generally hold values with tightly defined syntaxes, not arbitrary objects. JSON syntax attributes are an exception. The example profile holds a mix of fields that map directly to LDAP attributes, and fields that do not.

When determining them LDAP attributes to use, the first step is to look for natural matches with attributes defined in the default schema. Based on the About This Reference, you can derive the following table of correspondences:

Profile JSON Field LDAP Attribute

"externalId"

uid

"userName"

uid

"name/formatted"

cn

"name/givenName"

"name/familyName"

sn

"emails"

No direct match.

"phoneNumbers"

No direct match.

For the "emails" and "phoneNumbers" fields, there is no natural match. The uddiEMail and uddiPhone attributes come the closest because they optionally include a user-defined type. However, avoid those attributes for the following reasons:

  • These user profiles are unrelated to Universal Description, Discovery, and Integration (UDDI), which is focused instead on storing data for a SOAP-based web services discovery.

  • All client applications would have to know how to consume the UDDI-specific format. REST to LDAP has no means to transform type#value into {"type": "type", "value": "value"}.

  • The uddiEmail has no provision for the "primary" boolean value.

It would also be possible, but not advisable, to store "emails" and "phoneNumbers" in separate LDAP entries. The LDAP user entry could include attributes that reference email address and phone number entries by their DNs. The email and phone number entries could in turn reference users with the standard owner attribute. Unfortunately, there is no real value in storing email addresses and phone numbers separately from a user’s entry. The email and phone settings will not be shared with other entries, but instead belong only to the profile. Client applications will expect to update them atomically with the user profile. A profile change should not require updating an arbitrary number of LDAP entries. Even if they could be conveniently configured as single updates in the REST API, it would mean that updates to a JSON resource could partially fail in LDAP, a source of potential confusion.

An apt choice for the "emails" and "phoneNumbers" fields is therefore JSON syntax attributes. This example defines the following LDAP schema definitions for appropriate JSON attributes:

dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( example-email-oid
  NAME 'email'
  DESC 'An email address with an optional user-defined type and primary boolean'
  EQUALITY caseIgnoreJsonQueryMatch
  SYNTAX 1.3.6.1.4.1.36733.2.1.3.1
  USAGE userApplications
  X-SCHEMA-FILE '99-example.ldif'
  X-ORIGIN 'DS Documentation Examples' )
-
add: attributeTypes
attributeTypes: ( example-phone-number-oid
  NAME 'phoneNumber'
  DESC 'A phone number with an optional user-defined type'
  EQUALITY caseIgnoreJsonQueryMatch
  SYNTAX 1.3.6.1.4.1.36733.2.1.3.1
  USAGE userApplications
  X-SCHEMA-FILE '99-example.ldif'
  X-ORIGIN 'DS Documentation Examples' )
-
add: objectClasses
objectClasses: ( example-profile-user-oid
  NAME 'profileUser'
  DESC 'A user with optional profile components having user-defined types'
  SUP top
  AUXILIARY
  MAY ( email $ phoneNumber )
  X-SCHEMA-FILE '99-example.ldif'
  X-ORIGIN 'DS Documentation Examples' )

An auxiliary object class lets an entry that already has a structural object class hold additional attributes. For background information about LDAP schema, read LDAP Schema.

The following corresponding LDIF uses these definitions to define Example.com data with one user profile. Babs Jensen is the administrator and sole initial user. She can create other profiles and reset passwords:

dn: dc=example,dc=com
objectClass: domain
objectClass: top
dc: example
aci: (target ="ldap:///dc=example,dc=com")
 (targetattr != "userPassword")
 (version 3.0;acl "Authenticated users have read-search access";
 allow (read, search, compare)(userdn = "ldap:///all");)
aci: (target ="ldap:///dc=example,dc=com")
 (targetattr = "*")
 (version 3.0;acl "Allow Babs to administer other entries";
 allow (all)(userdn = "ldap:///uid=bjensen,ou=People,dc=example,dc=com");)

dn: ou=People,dc=example,dc=com
objectClass: organizationalunit
objectClass: top
ou: People
aci: (target ="ldap:///ou=People,dc=example,dc=com")
 (targetattr = "*")
 (version 3.0; acl "Allow self management of profiles";
 allow (delete, write)(userdn = "ldap:///self");)

dn: uid=bjensen,ou=People,dc=example,dc=com
objectClass: profileUser
objectClass: person
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: top
uid: bjensen
ou: People
cn: Barbara Jensen
cn: Babs Jensen
givenname: Barbara
sn: Jensen
userpassword: hifalutin
email: {"value": "bjensen@example.com", "type": "work", "primary": true}
phoneNumber: {"value": "+1 408 555 1862", "type": "work"}
ds-privilege-name: password-reset
Index the JSON attributes

Client applications might look up user profiles based on email addresses or phone numbers. They are not likely to look up user profiles based on user-defined labels for emails and phone numbers. Therefore, instead of indexing all fields of each JSON attribute value, index only the values:

  1. Install a directory server with the ds-evaluation setup profile.

  2. Configure a custom schema provider to allow the server to index only the JSON "values":

    $ dsconfig \
     create-schema-provider \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --provider-name "Value JSON Query Matching Rule" \
     --type json-query-equality-matching-rule \
     --set enabled:true \
     --set case-sensitive-strings:false \
     --set ignore-white-space:true \
     --set matching-rule-name:caseIgnoreJsonQueryMatch \
     --set matching-rule-oid:1.3.6.1.4.1.36733.2.1.4.1 \
     --set indexed-field:value \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --no-prompt
  3. Update the LDAP schema:

    $ ldapmodify \
     --hostname localhost \
     --port 1636 \
     --useSsl \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --bindDN uid=admin \
     --bindPassword password << EOF
    dn: cn=schema
    changetype: modify
    add: attributeTypes
    attributeTypes: ( example-email-oid
      NAME 'email'
      DESC 'An email address with an optional user-defined type and primary boolean'
      EQUALITY caseIgnoreJsonQueryMatch
      SYNTAX 1.3.6.1.4.1.36733.2.1.3.1
      USAGE userApplications
      X-SCHEMA-FILE '99-example.ldif'
      X-ORIGIN 'DS Documentation Examples' )
    -
    add: attributeTypes
    attributeTypes: ( example-phone-number-oid
      NAME 'phoneNumber'
      DESC 'A phone number with an optional user-defined type'
      EQUALITY caseIgnoreJsonQueryMatch
      SYNTAX 1.3.6.1.4.1.36733.2.1.3.1
      USAGE userApplications
      X-SCHEMA-FILE '99-example.ldif'
      X-ORIGIN 'DS Documentation Examples' )
    -
    add: objectClasses
    objectClasses: ( example-profile-user-oid
      NAME 'profileUser'
      DESC 'A user with optional profile components having user-defined types'
      SUP top
      AUXILIARY
      MAY ( email $ phoneNumber )
      X-SCHEMA-FILE '99-example.ldif'
      X-ORIGIN 'DS Documentation Examples' )
    EOF
  4. Add indexes for the email and phoneNumber JSON attributes:

    $ dsconfig \
     create-backend-index \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --backend-name dsEvaluation \
     --index-name email \
     --set index-type:equality \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --no-prompt
    
    $ dsconfig \
     create-backend-index \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --backend-name dsEvaluation \
     --index-name phoneNumber \
     --set index-type:equality \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --no-prompt
  5. Import the LDAP data with Babs’s profile:

    $ import-ldif \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --backendID dsEvaluation \
     --ldifFile /path/to/opendjprofiles.ldif \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin
  6. To check your work, read Babs’s profile over LDAP:

    $ ldapsearch \
     --hostname localhost \
     --port 1636 \
     --useSsl \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --baseDn dc=example,dc=com \
     "(uid=bjensen)"
    
    dn: uid=bjensen,ou=People,dc=example,dc=com
    objectClass: profileUser
    objectClass: person
    objectClass: inetOrgPerson
    objectClass: organizationalPerson
    objectClass: top
    cn: Barbara Jensen
    cn: Babs Jensen
    email: {"value": "bjensen@example.com", "type": "work", "primary": true}
    givenName: Barbara
    ou: People
    phoneNumber: {"value": "+1 408 555 1862", "type": "work"}
    sn: Jensen
    uid: bjensen

Now that you have sample data in LDAP, create the REST to LDAP mapping.

Configure the REST to LDAP mapping

The REST to LDAP mapping defines the HTTP API and JSON resources that are backed by the LDAP service and entries.

Rather than patch the default example mapping, this example adds a separate API with a new mapping for the profile view.

The new mapping uses the following features:

  • A "serverNaming" naming strategy ensures proper handing of Common REST "_id" values. LDAP entries use uid as the RDN. JSON profiles use server-generated "_id" values.

    The JSON profiles have "externalId" values corresponding to the LDAP uid. However, the JSON profiles do not define "_id" values. Client applications will use the "_id" values when uniquely identifying a profile, but not when looking up a profile.

    The "_id" maps to the entryUUID LDAP operational attribute. The entryUUID attribute is generated on creation and managed by the LDAP server.

  • As in the default example, passwords are not visible in profiles.

    Client applications use dedicated REST actions to manage password reset by administrators and to manage password modifications by users. The REST actions for password management require HTTPS.

  • The "externalId" and "userName" fields both map to LDAP uid attributes.

    You could make it possible to update these fields after creation. This example assumes that these fields might be expected not to change, and so restricts client applications from changing them.

  • The "formatted" and "familyName" fields of the name map to attributes that are required by the LDAP object class, inetOrgPerson. They are therefore required in the JSON profile as well.

The full mapping file is shown in the following listing:

{
    "version": "1.0",
    "resourceTypes": {
        "example-mapping": {
            "subResources": {
                "users": {
                    "type": "collection",
                    "dnTemplate": "ou=people,dc=example,dc=com",
                    "resource": "examples:user:1.0",
                    "namingStrategy": {
                        "type": "serverNaming",
                        "dnAttribute": "uid",
                        "idAttribute": "entryUUID"
                    }
                }
            }
        },
        "frapi:opendj:rest2ldap:object:1.0": {
            "isAbstract": true,
            "objectClasses": [
                "top"
            ],
            "properties": {
                "_schema": {
                    "type": "resourceType"
                },
                "_rev": {
                    "type": "simple",
                    "ldapAttribute": "etag",
                    "writability": "readOnly"
                },
                "_meta": {
                    "type": "object",
                    "properties": {
                        "created": {
                            "type": "simple",
                            "ldapAttribute": "createTimestamp",
                            "writability": "readOnly"
                        },
                        "lastModified": {
                            "type": "simple",
                            "ldapAttribute": "modifyTimestamp",
                            "writability": "readOnly"
                        }
                    }
                }
            }
        },
        "examples:user:1.0": {
            "superType": "frapi:opendj:rest2ldap:object:1.0",
            "objectClasses": [
                "profileUser",
                "person",
                "organizationalPerson",
                "inetOrgPerson"
            ],
            "supportedActions": [
                "modifyPassword",
                "resetPassword"
            ],
            "properties": {
                "externalId": {
                    "type": "simple",
                    "ldapAttribute": "uid",
                    "isRequired": true,
                    "writability": "createOnlyDiscardWrites"
                },
                "userName": {
                    "type": "simple",
                    "ldapAttribute": "uid",
                    "isRequired": true,
                    "writability": "createOnlyDiscardWrites"
                },
                "name": {
                    "type": "object",
                    "properties": {
                        "formatted": {
                            "type": "simple",
                            "ldapAttribute": "cn",
                            "isRequired": true
                        },
                        "givenName": {
                            "type": "simple"
                        },
                        "familyName": {
                            "type": "simple",
                            "ldapAttribute": "sn",
                            "isRequired": true
                        }
                    }
                },
                "emails": {
                    "type": "json",
                    "ldapAttribute": "email",
                    "isMultiValued": true
                },
                "phoneNumbers": {
                    "type": "json",
                    "ldapAttribute": "phoneNumber",
                    "isMultiValued": true
                }
            }
        }
    }
}
  1. Download the mapping file, example-mapping.json.

  2. Copy the mapping file to the appropriate server REST to LDAP configuration directory:

    $ mkdir /path/to/opendj/config/rest2ldap/endpoints/rest
    
    $ cp example-mapping.json /path/to/opendj/config/rest2ldap/endpoints/rest/

    The default example defines a REST API under /api. This example uses /rest instead.

  3. Enable a Rest2ldap endpoint for the API:

    $ dsconfig \
     create-http-endpoint \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --set authorization-mechanism:HTTP\ Basic \
     --set config-directory:config/rest2ldap/endpoints/rest \
     --set enabled:true \
     --type rest2ldap-endpoint \
     --endpoint-name /rest \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --no-prompt
Try the new API
Look Up a Profile by Email Address

The following query looks for profiles with email addresses containing bjensen:

$ curl \
 --request GET \
 --cacert ca-cert.pem \
 --user bjensen:hifalutin \
 --silent \
 "https://localhost:8443/rest/users/?_queryFilter=emails/value+co+'bjensen'"

{
  "result" : [ {
    "_id" : "<generated-id>",
    "_rev" : "<revision>",
    "_schema" : "examples:user:1.0",
    "externalId" : "bjensen",
    "userName" : "bjensen",
    "name" : {
      "formatted" : [ "Barbara Jensen", "Babs Jensen" ],
      "givenName" : "Barbara",
      "familyName" : "Jensen"
    },
    "emails" : [ {
      "value" : "bjensen@example.com",
      "type" : "work",
      "primary" : true
    } ],
    "phoneNumbers" : [ {
      "value" : "+1 408 555 1862",
      "type" : "work"
    } ]
  } ],
  "resultCount" : 1,
  "pagedResultsCookie" : null,
  "totalPagedResultsPolicy" : "NONE",
  "totalPagedResults" : -1,
  "remainingPagedResults" : -1
}
Look Up a Profile by Phone Number

The following query looks for profiles with the phone number +1 408 555 1862:

$ curl \
 --request GET \
 --cacert ca-cert.pem \
 --user bjensen:hifalutin \
 --silent \
 "https://localhost:8443/rest/users/?_queryFilter=phoneNumbers/value+eq+'%2B1%20408%20555%201862'"

Notice that the telephone number is URL encoded as %2B1%20408%20555%201862.

Create a User Profile

This example creates a profile using the JSON in newuser.json:

$ curl \
 --request POST \
 --cacert ca-cert.pem \
 --user bjensen:hifalutin \
 --header "Content-Type: application/json" \
 --data @newuser.json \
 --silent \
 https://localhost:8443/rest/users/

Upon initial creation, the user has no password, and so cannot authenticate, yet.

Set a Password

Set a password to allow the user to authenticate.

This example uses Bash shell and jq to manipulate JSON on the command line.

The first operation gets the user profile "_id" and holds it in an ID environment variable.

The second operation performs an administrative password reset as Babs and holds the resulting generated password in a PASSWORD environment variable.

The third and final operation uses the generated password to authenticate as the user and modify the password:

$ export ID=$(jq --raw-output '.result[0] ._id' \
 <(curl --silent --request GET --cacert ca-cert.pem --user bjensen:hifalutin 2>/dev/null \
 "https://localhost:8443/rest/users/?_queryFilter=externalId+eq+'newuser'"))

$ export PASSWORD=$(jq --raw-output '.generatedPassword' \
 <(curl --silent --request POST --cacert ca-cert.pem --user bjensen:hifalutin \
 --header "Content-Type: application/json" --data '{}' \
 "https://localhost:8443/rest/users/${ID}?_action=resetPassword"))

$ curl \
 --request POST \
 --cacert ca-cert.pem \
 --user "newuser:${PASSWORD}" \
 --header "Content-Type: application/json" \
 --data "{\"oldPassword\": \"${PASSWORD}\", \"newPassword\": \"password\"}" \
 --silent \
 "https://localhost:8443/rest/users/${ID}?_action=modifyPassword"
Read Your Profile

This example employs the user profile "_id":

$ curl \
 --request GET \
 --cacert ca-cert.pem \
 --user newuser:password \
 --silent \
 "https://localhost:8443/rest/users/${ID}"
Changing a User’s Own User-Defined Email Type

This example employs the user profile "_id".

Download the JSON patch payload, changeEmailType.json:

$ curl \
 --request PATCH \
 --cacert ca-cert.pem \
 --user newuser:password \
 --header "Content-Type: application/json" \
 --data @changeEmailType.json \
 --silent \
 "https://localhost:8443/rest/users/${ID}"

{
  "_id" : "<generated-user-id>",
  "_rev" : "<revision>",
  "_schema" : "examples:user:1.0",
  "_meta" : {
    "created" : "<datestamp>"
  },
  "externalId" : "newuser",
  "userName" : "newuser",
  "name" : {
    "formatted" : "New User",
    "givenName" : "User",
    "familyName" : "New"
  },
  "emails" : [ {
    "value" : "newuser@example.com",
    "type" : "work",
    "primary" : true
  }, {
    "value" : "newuser@example.org",
    "type" : "home",
    "primary" : false
  } ],
  "phoneNumbers" : [ {
    "value" : "+1 408 555 1212",
    "type" : "work"
  } ]
}
Remove Your Email Address

This example employs the user profile "_id".

Download the JSON patch payload, removeWorkEmail.json:

$ curl \
 --request PATCH \
 --cacert ca-cert.pem \
 --user newuser:password \
 --header "Content-Type: application/json" \
 --data @removeWorkEmail.json \
 --silent \
 "https://localhost:8443/rest/users/${ID}"

{
  "_id" : "<generated-user-id>",
  "_rev" : "<revision>",
  "_schema" : "examples:user:1.0",
  "_meta" : {
    "created" : "<datestamp>"
  },
  "externalId" : "newuser",
  "userName" : "newuser",
  "name" : {
    "formatted" : "New User",
    "givenName" : "User",
    "familyName" : "New"
  },
  "emails" : [ {
    "value" : "newuser@example.org",
    "type" : "home",
    "primary" : true
  } ],
  "phoneNumbers" : [ {
    "value" : "+1 408 555 1212",
    "type" : "work"
  } ]
}
Add Your Cell Phone Number

This example employs the user profile "_id".

Download the JSON patch payload, addPersonalCell.json:

$ curl \
 --request PATCH \
 --cacert ca-cert.pem \
 --user newuser:password \
 --header "Content-Type: application/json" \
 --data @addPersonalCell.json \
 --silent \
 "https://localhost:8443/rest/users/${ID}"

{
  "_id" : "<generated-user-id>",
  "_rev" : "<revision>",
  "_schema" : "examples:user:1.0",
  "_meta" : {
    "created" : "<datestamp>"
  },
  "externalId" : "newuser",
  "userName" : "newuser",
  "name" : {
    "formatted" : "New User",
    "givenName" : "User",
    "familyName" : "New"
  },
  "emails" : [ {
    "value" : "newuser@example.org",
    "type" : "home",
    "primary" : true
  } ],
  "phoneNumbers" : [ {
    "value" : "+1 408 555 1212",
    "type" : "work"
  }, {
    "value": "+1 408 555 1234",
    "type": "personal cell"
  } ]
}
Delete Your Profile

This example employs the user profile "_id":

$ curl \
 --request DELETE \
 --cacert ca-cert.pem \
 --user newuser:password \
 --header "Content-Type: application/json" \
 --silent \
 "https://localhost:8443/rest/users/${ID}"

REST API Documentation

API descriptors provide runtime documentation for REST APIs. Requests for API descriptors use the reserved query string parameters, _api and _crestapi. By default, DS servers do not return descriptors, but respond instead with HTTP status code 501 Not Implemented.

Although it is possible to serve the descriptors at runtime, do not use production servers for this purpose.

Instead, prepare the documentation by reading API descriptors from a server with the same API as production servers. Publish the documentation separately.

Preparing documentation for a Rest2ldap endpoint is an iterative process:

  1. Enable API descriptors for the connection handler you use:

    $ dsconfig \
     set-connection-handler-prop \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --handler-name HTTPS \
     --set api-descriptor-enabled:true \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --no-prompt
  2. Restart the connection handler to take the configuration change into account:

    $ dsconfig \
     set-connection-handler-prop \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --handler-name HTTPS \
     --set enabled:false \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --no-prompt
    
    $ dsconfig \
     set-connection-handler-prop \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --handler-name HTTPS \
     --set enabled:true \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --no-prompt
  3. Configure the API.

  4. Run a local copy of a tool for viewing OpenAPI documentation, such as Swagger UI.

  5. View the generated documentation through the tool by reading the OpenAPI format descriptor.

    For example, read the descriptor for the /api endpoint with a URL such as https://kvaughan:bribery@localhost:8443/api?_api for directory data, or https://admin:password@localhost:8443/admin?_api for the server configuration.

    The following screenshot shows example documentation:

    The generated documentation appears in the tool.

    If your browser does not display the generated documentation, disable CORS settings. See your browser’s documentation or search the web for details.

  6. Update the API configuration.

  7. Force the Rest2ldap endpoint to reread the updated configuration file:

    $ dsconfig \
     set-http-endpoint-prop \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --endpoint-name "/api" \
     --set enabled:false \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --no-prompt
    
    $ dsconfig \
     set-http-endpoint-prop \
     --hostname localhost \
     --port 4444 \
     --bindDN uid=admin \
     --bindPassword password \
     --endpoint-name "/api" \
     --set enabled:true \
     --usePkcs12TrustStore /path/to/opendj/config/keystore \
     --trustStorePassword:file /path/to/opendj/config/keystore.pin \
     --no-prompt
  8. Edit the descriptor.

  9. Publish the final descriptor alongside your production service.