+
+
+...
+----
+
+. Next, you should provide information for the tab. Based on the comments in the file, and the entries in the `Password` tab, the following code sets up a Mobile Phone number entry:
++
+
+[source, html]
+----
+
+ ...
+----
++
+
+[NOTE]
+======
+For illustration, this procedure uses the HTML tags found in the `UserProfileTemplate.html` file. You can use any standard HTML content within `tab-pane` tags, as long as they include a standard `form` tag and standard `input` fields. OpenIDM picks up this information when the tab is saved, and uses it to `PATCH` user content.
+======
+
+. Review the `managed.json` file. Make sure it is `viewable` and `userEditable` as shown in the following excerpt:
++
+
+[source, javascript]
+----
+"telephoneNumber" : {
+ "type" : "string",
+ "title" : "Mobile Phone",
+ "viewable" : true,
+ "userEditable" : true,
+ "pattern" : "^\\+?([0-9\\- \\(\\)])*$"
+},
+----
+
+. Open the applicable `translation.json` file. You should find a copy of this file in the following subdirectory: `selfservice/extension/locales/en/`.
++
+Search for the line with `basicInfo`, and add an entry for `mobilePhone`:
++
+
+[source, javascript]
+----
+"basicInfo": "Basic Info",
+"mobilePhone": "Mobile Phone",
+----
+
+. Review the result. Log in to the Self-Service UI, and click Profile. Note the entry for the Mobile Phone tab.
++
+
+image::images/ui-updated-profile.png[]
+
+====
+
+
+[#modifying-user-query-fields]
+==== Modifying Valid Query Fields
+
+For Password Reset and Forgotten Username functionality, you may choose to modify Valid Query Fields, such as those described in xref:#ui-configuring["Configuring User Self-Service"].
+
+For example, if you click Configure > Password Reset > User Query Form, you can make changes to __Valid Query Fields__.
+
+image::images/ui-valid-query.png[]
+If you add, delete, or modify any Valid Query Fields, you will have to change the corresponding `userQuery-initial.html` file.
+
+Assuming you set up custom `extension` subdirectories, as described in xref:#ui-custom-template["Customizing a UI Template"], you can find this file in the following directory: `selfservice/extension/templates/user/process`.
+If you change any Valid Query Fields, you should make corresponding changes.
+
+* For Forgotten Username functionality, you would modify the `username/userQuery-initial.html` file.
+
+* For Password Reset functionality, you would modify the `reset/userQuery-initial.html` file.
+
+For a model of how you can change the `userQuery-initial.html` file, see xref:#ui-selfreg-addentries["Customizing the User Registration Page"].
+
+
+
+[#ui-managing-accounts]
+=== Managing Accounts
+
+Only administrative users (with the role `openidm-admin`) can add, modify, and delete accounts from the Admin UI. Regular users can modify certain aspects of their own accounts from the Self-Service UI.
+
+[#ui-account-admin]
+==== Account Configuration
+
+In the Admin UI, you can manage most details associated with an account, as shown in the following screenshot.
+
+[#d0e3501]
+image::images/ui-data-account.png[]
+You can configure different functionality for an account under each tab:
+--
+
+Details::
+The Details tab includes basic identifying data for each user, with two special entries:
++
+[open]
+====
+
+Status::
+By default, accounts are shown as __active__. To suspend an account, such as for a user who has taken a leave of absence, set that user's status to __inactive__.
+
+Manager::
+You can assign a manager from the existing list of managed users.
+
+====
+
+Password::
+As an administrator, you can create new passwords for users in the managed user repository.
+
+Provisioning Roles::
+Used to specify how objects are provisioned to an external system. For more information, see xref:chap-users-groups-roles.adoc#working-with-managed-roles["Working With Managed Roles"].
+
+Authorization Roles::
+Used to specify the authorization rights of a managed user within OpenIDM. For more information, see xref:chap-users-groups-roles.adoc#working-with-managed-roles["Working With Managed Roles"].
+
+Direct Reports::
+Users who are listed as managers of others have entries under the Direct Reports tab, as shown in the following illustration:
++
+
+image::images/ui-direct-reports.png[]
+
+Linked Systems::
+Used to display account information reconciled from external systems.
+
+--
+
+
+[#ui-managing-account-procedures]
+==== Procedures for Managing Accounts
+
+With the following procedures, you can add, update, and deactivate accounts for managed objects such as users.
+
+The managed object does not have to be a user. It can be a role, a group, or even be a physical item such as an IoT device. The basic process for adding, modifying, deactivating, and deleting other objects is the same as it is with accounts. However, the details may vary; for example, many IoT devices do not have telephone numbers.
+
+[#add-user-account]
+.To Add a User Account
+====
+
+. Log in to the Admin UI at `\https://localhost:8443/admin`.
+
+. Click Manage > User.
+
+. Click New User.
+
+. Complete the fields on the New User page.
++
+Most of these fields are self-explanatory. Be aware that the user interface is subject to policy validation, as described in xref:chap-policies.adoc#chap-policies["Using Policies to Validate Data"]. So, for example, the email address must be a valid email address, and the password must comply with the password validation settings that appear if you enter an invalid password.
+
+====
+In a similar way, you can create accounts for other managed objects.
+
+You can review new managed object settings in the `managed.json` file of your `project-dir/conf` directory.
+
+In the following procedures, you learn how to update, deactivate, and delete user accounts, as well as how to view that account in different user resources. You can follow essentially the same procedures for other managed objects such as IoT devices.
+
+[#ui-update-account]
+.To Update a User Account
+====
+
+. Log in to the Admin UI at `\https://localhost:8443/admin` as an administrative user.
+
+. Click Manage > User.
+
+. Click the Username of the user that you want to update.
+
+. On the profile page for the user, modify the fields you want to change and click Update.
++
+The user account is updated in the OpenIDM repository.
+
+====
+
+[#delete-user-account]
+.To Delete a User Account
+====
+
+. Log in to the Admin UI at `\https://localhost:8443/admin` as an administrative user.
+
+. Click Manage > User.
+
+. Select the checkbox next to the desired Username.
+
+. Click the Delete Selected button.
+
+. Click OK to confirm the deletion.
++
+The user is deleted from the internal repository.
+
+====
+
+[#user-linked-view]
+.To View an Account in External Resources
+====
+The Admin UI displays the details of the account in the OpenIDM repository (managed/user). When a mapping has been configured between the repository and one or more external resources, you can view details of that account in any external system to which it is linked. As this view is read-only, you cannot update a user record in a linked system from within the Self-Service UI.
+
+By default, __implicit synchronization__ is enabled for mappings __from__ the `managed/user` repository __to__ any external resource. This means that when you update a managed object, any mappings defined in the `sync.json` file that have the managed object as the source are automatically executed to update the target system. You can see these changes in the Linked Systems section of a user's profile.
+
+To view a user's linked accounts:
+
+. Log in to the Admin UI at `\https://localhost:8443/admin`.
+
+. Click Manage User > __Username__ > Linked Systems.
+
+. The Linked Systems panel indicates the external mapped resource or resources.
+
+. Select the resource in which you want to view the account, from the Linked Resource list.
++
+The user record in the linked resource is displayed.
+
+====
+
+
+
+[#ui-account-relationships]
+=== Configuring Account Relationships
+
+This section will help you set up relationships between human users and devices, such as IoT devices.
+
+You'll set this up with the help of the Admin UI schema editor, which allows you to create and customize managed objects such as `Users` and `Devices` as well as relationships between managed objects. You can also create these options in the `managed.json` file for your project.
+
+When complete, you will have users who can own multiple unique devices. If you try to assign the same device to more than one owner, OpenIDM will stop you with an error message.
+
+This section assumes that you have started OpenIDM with xref:../samples-guide/chap-ldap-samples.adoc#more-sample-2b["Sample 2b - LDAP Two Way"] in the __Samples Guide__.
+After you have started OpenIDM with "Sample 2b", go through the following procedures, where you will:
+
+* Set up a managed object named `Device`, with unique serial numbers for each device. You can configure the searchable schema of your choice. See xref:#ui-add-iot-schema["Configuring Schema for a Device"] for details.
+
+* Set up a relationship from the Device to the User managed object. See xref:#ui-configure-iot-relationship["Configure a Relationship from the Device Managed Object"] for details.
+
+* Set up a reverse relationship from the User to the Device managed object. See xref:#ui-configure-iot-user["Configure a Relationship From the User Managed Object"] for details.
+
+* Demonstrate the relationships. Assign users to devices. See what happens when you try to assign a device to more than one user. For details, see xref:#ui-iot-demo["Demonstrating an IoT Relationship"].
+
+
+[#ui-add-iot-schema]
+.Configuring Schema for a Device
+====
+This procedure illustrates how you might set up a Device managed object, with schema that configures relationships to users.
+
+After you configure the schema for the Device managed object, you can collect information such as model, manufacturer, and serial number for each device. In the next procedure, you'll set up an `owner` schema property that includes a relationship to the User managed object.
+
+. Click Configure > Managed Objects > New Managed Object. Give that object an appropriate IoT name. For this procedure, specify `Device`. You should also select a managed object icon. Click Save.
+
+. You should now see four tabs: Details, Schema, Scripts, and Properties. Click the Schema tab.
++
+
+image::images/ui-initial-mo.png[]
+
+. The items that you can add to the new managed object depend on the associated properties.
++
+The Schema tab includes the `Readable Title` of the device; in this case, set it to `Device`.
+
+. You can add schema properties as needed in the UI. Click the Property button. Include the properties shown in the illustration: model, serialNumber, manufacturer, description, and category.
+
+. Initially, the new property is named `Property 1`. As soon as you enter a property name such as `model`, OpenIDM changes that property name accordingly.
+
+. To support UI-based searches of devices, make sure to set the Searchable option to true for all configured schema properties, unless it includes extensive text, In this case, you should set Searchable to false for the `description` property.
++
+The Searchable option is used in the data grid for the given object. When you click Manage > Device (or another object such as User), OpenIDM displays searchable properties for that object.
+
+. After you save the properties for the new managed object type, OpenIDM saves those entries in the `managed.json` file in the `project-dir/conf` directory.
+
+. Now click Manage > Device > New Device. Add a device as shown in the following illustration.
++
+
+image::images/ui-mo-wearable.png[]
+
+. You can continue adding new devices to the managed object, or reconcile that managed object with another data store. The other procedures in this section assume that you have set up the devices as shown in the next illustration.
+
+. When complete, you can review the list of devices. Based on this procedure, click Manage > Device.
++
+
+image::images/ui-mo-iot.png[]
+
+. Select one of the listed devices. You'll note that the label for the device in the Admin UI matches the name of the first property of the device.
++
+
+image::images/ui-oneiot-device.png[]
++
+You can change the order of schema properties for the Device managed object by clicking Configure > Managed Object > Device > Schema, and select the property that you want to move up or down the list.
++
+Alternatively, you can make the same changes to this (or any managed object schema) in the `managed.json` file for your project.
+
+====
+
+[#ui-configure-iot-relationship]
+.Configure a Relationship from the Device Managed Object
+====
+In this procedure, you will add a property to the schema of the Device managed object.
+
+. In the Admin UI, click Configure > Managed Objects > Device > Schema.
+
+. Under the Schema tab, add a new property. For this procedure, we call it __owner__. Unlike other schema properties, set the Searchable property to false.
+
+. Scroll down to Validation Policies; click the Type box and select Relationship. This opens additional relationship options.
+
+. Set up a Reverse Property Name of `IoT_Devices`. You'll use that reverse property name in the next xref:#ui-configure-iot-user["Configure a Relationship From the User Managed Object"].
++
+
+image::images/ui-device-relation.png[]
++
+Be sure to set the Reverse Relationship and Validate options to `true`, which ensures that each device is associated with no more than one user.
+
+. Scroll down and add a Resource Collection. Set up a link to the `managed/user` object, with a label that matches the `User` managed object.
+
+. Enable queries of the User managed object by setting Query Filter to true. The Query Filter value for this Device object allows you to identify the user who "owns" each device. For more information, see xref:chap-data.adoc#query-filters["Common Filter Expressions"].
++
+
+image::images/ui-device-resource.png[]
+
+. Set up fields from `managed/user` properties. The properties shown in the illustration are just examples, based on xref:../samples-guide/chap-ldap-samples.adoc#more-sample-2b["Sample 2b - LDAP Two Way"] in the __Samples Guide__.
+
+. Add one or more Sort Keys from the configured fields.
+
+. Save your changes.
+
+====
+
+[#ui-configure-iot-user]
+.Configure a Relationship From the User Managed Object
+====
+In this procedure, you will configure an existing User Managed Object with schema to match what was created in xref:#ui-configure-iot-relationship["Configure a Relationship from the Device Managed Object"].
+
+With the settings you create, OpenIDM supports a relationship between a single user and multiple devices. In addition, this procedure prevents multiple users from "owning" any single device.
+
+. In the Admin UI, click Configure > Managed Objects > User > Schema.
+
+. Under the Schema tab, add a new property, called IoT_Devices.
+
+. Make sure the searchable property is set to false, to minimize confusion in the relationship. Otherwise, you'll see every device owned by every user, when you click Manage > User.
+
+. For validation policies, you'll set up an __array__ with a relationship. Note how the reverse property name matches the property that you configured in xref:#ui-configure-iot-relationship["Configure a Relationship from the Device Managed Object"].
++
+
+image::images/ui-device-array.png[]
++
+Be sure to set the Reverse Relationship and Validate options to `true`, which ensures that no more than one user gets associated with a specific device.
+
+. Scroll down to Resource Collection, and add references to the `managed/device` resource, as shown in the next illustration.
+
+. Enter `true` in the Query Filter text box. In this relationship, OpenIDM will read all information from the `managed/device` managed object, with information from the device fields and sort keys that you configured in xref:#ui-configure-iot-relationship["Configure a Relationship from the Device Managed Object"].
++
+
+image::images/ui-device-resource-collection.png[]
+
+====
+
+[#ui-iot-demo]
+.Demonstrating an IoT Relationship
+====
+This procedure assumes that you have already taken the steps described in the previous procedures in this section, specifically, xref:#ui-add-iot-schema["Configuring Schema for a Device"], xref:#ui-configure-iot-relationship["Configure a Relationship from the Device Managed Object"], and xref:#ui-configure-iot-user["Configure a Relationship From the User Managed Object"].
+
+This procedure also assumes that you started OpenIDM with xref:../samples-guide/chap-ldap-samples.adoc#more-sample-2b["Sample 2b - LDAP Two Way"] in the __Samples Guide__, and have reconciled to set up users.
+
+. From the Admin UI, click Manage > User. Select a user, and in this case, click the IoT Devices tab. See how you can select any of the devices that you may have added in xref:#ui-add-iot-schema["Configuring Schema for a Device"].
++
+
+image::images/ui-user-iot-device.png[]
+
+. Alternatively, try to assign a device to an owner. To do so, click Manage > Device, and select a device. You'll see either an `Add Owner` or `Update Owner` button, which allows you to assign a device to a specific user.
++
+If you try to assign a device already assigned by a different user, you'll get the following message: `Conflict with Existing Relationship`.
+
+====
+
+
+[#ui-managing-workflows]
+=== Managing Workflows From the Self-Service UI
+
+The Self-Service UI is integrated with the embedded Activiti worfklow engine, enabling users to interact with workflows. Available workflows are displayed under the Processes item on the Dashboard. In order for a workflow to be displayed here, the workflow definition file must be present in the `openidm/workflow` directory.
+
+A sample workflow integration with the Self-Service UI is provided in `openidm/samples/workflow`, and documented in xref:../samples-guide/chap-workflow-samples.adoc#example-provisioning-workflow["Sample Workflow - Provisioning User Accounts"] in the __Samples Guide__. Follow the steps in that sample for an understanding of how the workflow integration works.
+
+General access to workflow-related endpoints is based on the access rules defined in the `script/access.js` file. The configuration defined in the `conf/process-access.json` file determines who can invoke workflows. By default all users with the role `openidm-authorized` or `openidm-admin` can invoke any available workflow. The default `process-access.json` file is as follows:
+
+[source, javascript]
+----
+{
+ "workflowAccess" : [
+ {
+ "propertiesCheck" : {
+ "property" : "_id",
+ "matches" : ".*",
+ "requiresRole" : "openidm-authorized"
+ }
+ },
+ {
+ "propertiesCheck" : {
+ "property" : "_id",
+ "matches" : ".*",
+ "requiresRole" : "openidm-admin"
+ }
+ }
+ ]
+}
+----
+--
+
+`"property"`::
+Specifies the property used to identify the process definition. By default, process definitions are identified by their `_id`.
+
+`"matches"`::
+A regular expression match is performed on the process definitions, according to the specified property. The default (`"matches" : ".*"`) implies that all process definition IDs match.
+
+`"requiresRole"`::
+Specifies the OpenIDM role that is required for users to have access to the matched process definition IDs. In the default file, users with the role `openidm-authorized` or `openidm-admin` have access.
+
+--
+To extend the process action definition file, identify the processes to which users should have access, and specify the qualifying user roles. For example, if you want to allow access to users with a role of `ldap`, add the following code block to the `process-access.json` file:
+
+[source]
+----
+{
+ "propertiesCheck" : {
+ "property" : "_id",
+ "matches" : ".*",
+ "requiresRole" : "ldap"
+ }
+}
+----
+
+[#add-role-workflow]
+==== Adding Another Role to a Workflow
+
+Sometimes, you'll want to configure multiple roles with access to the same workflow process. For example, if you want users with a role of doctor and nurse to both have access to certain workflows, you could set up the following code block within the `process-access.json` file:
+
+[source, javascript]
+----
+{
+ "propertiesCheck" : {
+ "property" : "_id",
+ "matches" : ".*",
+ "requiresRole" : "doctor"
+ }
+},
+{
+ "propertiesCheck" : {
+ "property" : "_id",
+ "matches" : ".*",
+ "requiresRole" : "nurse"
+ }
+}
+----
+You could add more `requiresRole` code blocks, such as:
+
+[source, javascript]
+----
+{
+ "propertiesCheck" : {
+ "property" : "_id",
+ "matches" : ".*",
+ "requiresRole" : "medic"
+ }
+}
+----
+
+
+
+[#ui-customizing]
+=== Customizing the UI
+
+OpenIDM allows you to customize both the Admin and Self-Service UIs. When you install OpenIDM, you can find the default UI configuration files in two directories:
+
+* Admin UI: `openidm/ui/admin/default`
+
+* Self-Service UI: `openidm/ui/selfservice/default`
+
+OpenIDM looks for custom themes and templates in the following directories:
+
+* Admin UI: `openidm/ui/admin/extension`
+
+* Self-Service UI: `openidm/ui/selfservice/extension`
+
+Before starting the customization process, you should create these directories. If you are running UNIX/Linux, the following commands create a copy of the appropriate subdirectories:
+
+[source, console]
+----
+$ cd /path/to/openidm/ui
+$ cp -r selfservice/default/. selfservice/extension
+$ cp -r admin/default/. admin/extension
+----
+OpenIDM also includes templates that may help, in two other directories:
+
+* Admin UI: `openidm/ui/admin/default/templates`
+
+* Self-Service UI: `openidm/ui/selfservice/default/templates`
+
+
+
+[#ui-theme]
+=== Changing the UI Theme
+
+You can customize the theme of the user interface. OpenIDM uses the link:http://getbootstrap.com[Bootstrap, window=\_blank] framework. You can download and customize the OpenIDM UI with the Bootstrap themes of your choice. OpenIDM is also configured with the link:http://fortawesome.github.io/Font-Awesome/[Font Awesome CSS toolkit, window=\_blank].
+
+[NOTE]
+====
+If you use link:http://fortawesome.github.io/Font-Awesome/icons/[Brand Icons from the Font Awesome CSS Toolkit, window=\_blank], be aware of the following statement:
+
+All brand icons are trademarks of their respective owners. The use of these trademarks does not indicate endorsement of the trademark holder by ForgeRock, nor vice versa.
+====
+
+[#ui-bootstrap]
+==== OpenIDM UI Themes and Bootstrap
+
+You can configure a few features of the OpenIDM UI in the `ui-themeconfig.json` file in your project's `conf/` subdirectory. However, to change most theme-related features of the UI, you must copy target files to the appropriate `extension` subdirectory, and then modify them as discussed in xref:#ui-customizing["Customizing the UI"].
+
+The default configuration files for the Admin and Self-Service UIs are identical for theme configuration.
+
+By default the UI reads the stylesheets and images from the respective `openidm/ui/function/default` directories. Do not modify the files in this directory. Your changes may be overwritten the next time you update or even patch your system.
+
+To customize your UI, first set up matching subdirectories for your system (`openidm/ui/admin/extension` and `openidm/ui/selfservice/extension`). For example, assume you want to customize colors, logos, and so on.
+
+You can set up a new theme, primarily through custom Bootstrap CSS files, in appropriate `extension/` subdirectories, such as `openidm/ui/selfservice/extension/libs` and `openidm/ui/selfservice/extension/css`.
+
+You may also need to update the `"stylesheets"` listing in the `ui-themeconfig.json` file for your project, in the `project-dir/conf` directory.
+
+[source, javascript]
+----
+...
+"stylesheets" : ["css/bootstrap-3.3.5-custom.css", "css/structure.css", "css/theme.css"],
+...
+----
+You can find these `stylesheets` in the `/css` subdirectory.
+
+* `bootstrap-3.3.5-custom.css`: Includes custom settings that you can get from various Bootstrap configuration sites, such as the Bootstrap link:http://getbootstrap.com/customize/[Customize and Download, window=\_blank] website.
++
+You may find the ForgeRock version of this in the `config.json` file in the `ui/selfservice/default/css/common/structure/` directory.
+
+* `structure.css`: Supports configuration of structural elements of the UI.
+
+* `theme.css`: Includes customizable options for UI themes such as colors, buttons, and navigation bars.
+
+If you want to set up custom versions of these files, copy them to the `extension/css` subdirectories.
+
+
+[#ui-logo]
+==== Changing the Default Logo
+
+For the Self-Service UI, you can find the default logo in the `openidm/ui/selfservice/default/images` directory. To change the default logo, copy desired files to the `openidm/ui/selfservice/extension/images` directory. You should see the changes after refreshing your browser.
+
+To specify a different file name, or to control the size, and other properties of the image file that is used for the logo, adjust the `logo` property in the UI theme configuration file for your project: `project-dir/conf/ui-themeconfig.json`).
+
+The following change to the UI theme configuration file points to an image file named `example-logo.png`, in the `openidm/ui/extension/images` directory:
+
+[source, javascript]
+----
+...
+"loginLogo" : {
+ "src" : "images/example-logo.png",
+ "title" : "Example.com",
+ "alt" : "Example.com",
+ "height" : "104px",
+ "width" : "210px"
+},
+...
+----
+Refresh your browser window for the new logo to appear.
+
+
+[#ui-locale]
+==== Changing the Language of the UI
+
+Currently, the UI is provided only in US English. You can translate the UI and specify that your own locale is used. The following example shows how to translate the UI into French:
+
+====
+
+. Assuming you set up custom `extension` subdirectories, as described in xref:#ui-customizing["Customizing the UI"], you can copy the default (`en`) locale to a new (`fr`) subdirectory as follows:
++
+
+[source, console]
+----
+$ cd /path/to/openidm/ui/selfservice/extension/locales
+$ cp -R en fr
+----
++
+The new locale (`fr`) now contains the default `translation.json` file:
++
+
+[source, console]
+----
+$ ls fr/
+translation.json
+----
+
+. Translate the values of the properties in the `fr/translate.json` file. Do __not__ translate the property names. For example:
++
+
+[source, javascript]
+----
+...
+"UserMessages" : {
+ "changedPassword" : "Mot de passe a été modifié",
+ "profileUpdateFailed" : "Problème lors de la mise à jour du profil",
+ "profileUpdateSuccessful" : "Profil a été mis à jour",
+ "userNameUpdated" : "Nom d'utilisateur a été modifié",
+....
+----
+
+. Change the UI configuration to use the new locale by setting the value of the `lang` property in the `project-dir/conf/ui-configuration.json` file, as follows:
++
+
+[source, console]
+----
+"lang" : "fr",
+----
+
+. Refresh your browser window, and OpenIDM applies your change.
+
+====
+You can also change the labels for accounts in the UI. To do so, navigate to the `Schema Properties` for the managed object to be changed.
+
+To change the labels for user accounts, navigate to the Admin UI. Click Configure > Managed Objects > User, and scroll down to Schema.
+
+Under Schema Properties, select a property and modify the `Readable Title`. For example, you can modify the `Readable Title` for `userName` to a label in another language, such as `Nom d'utilisateur`.
+
+
+[#ui-project-config]
+==== Creating a Project-Specific UI Theme
+
+You can create specific UI themes for different projects and then point a particular UI instance to use a defined theme on startup. To create a complete custom theme, follow these steps:
+
+====
+
+. Shut down the OpenIDM instance, if it is running. In the OSGi console, type:
++
+
+[source, console]
+----
+shutdown
+->
+----
+
+. Copy the entire default Self-Service UI theme to an accessible location. For example:
++
+
+[source, console]
+----
+$ cd /path/to/openidm/ui/selfservice
+$ cp -r default /path/to/openidm/new-project-theme
+----
+
+. If desired, repeat the process with the Admin UI; just remember to copy files to a different directory:
++
+
+[source, console]
+----
+$ cd /path/to/openidm/ui/admin
+$ cp -r default /path/to/openidm/admin-project-theme
+----
+
+. In the copied theme, modify the required elements, as described in the previous sections. Note that nothing is copied to the extension folder in this case - changes are made in the copied theme.
+
+. In the `conf/ui.context-selfservice.json` file, modify the values for `defaultDir` and `extensionDir` to the directory with your `new-project-theme`:
++
+
+[source, javascript]
+----
+{
+ "enabled" : true,
+ "urlContextRoot" : "/",
+ "defaultDir" : "&{launcher.install.location}/ui/selfservice/default",
+ "extensionDir" : "&{launcher.install.location}/ui/selfservice/extension"
+}
+----
+
+. If you want to repeat the process for the Admin UI, make parallel changes to the `project-dir/conf/ui.context-admin.json` file.
+
+. Restart OpenIDM.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh
+----
+
+. Relaunch the UI in your browser. The UI is displayed with the new custom theme.
+
+====
+
+
+
+[#ui-external-password-reset]
+=== Using an External System for Password Reset
+
+By default, the Password Reset mechanism is handled internally, in OpenIDM. You can reroute Password Reset in the event that a user has forgotten their password, by specifying an external URL to which Password Reset requests are sent. Note that this URL applies to the Password Reset link on the login page only, not to the security data change facility that is available after a user has logged in.
+
+To set an external URL to handle Password Reset, set the `passwordResetLink` parameter in the UI configuration file (`conf/ui-configuration.json`) file. The following example sets the `passwordResetLink` to `\https://accounts.example.com/account/reset-password`:
+
+[source, console]
+----
+passwordResetLink: "https://accounts.example.com/reset-password"
+----
+The `passwordResetLink` parameter takes either an empty string as a value (which indicates that no external link is used) or a full URL to the external system that handles Password Reset requests.
+
+[NOTE]
+====
+External Password Reset and security questions for internal Password Reset are mutually exclusive. Therefore, if you set a value for the `passwordResetLink` parameter, users will not be prompted with any security questions, regardless of the setting of the `securityQuestions` parameter.
+====
+
+
+[#ui-external-logout]
+=== Providing a Logout URL to External Applications
+
+By default, a UI session is invalidated when a user clicks on the Log out link. In certain situations your external applications might require a distinct logout URL to which users can be routed, to terminate their UI session.
+
+The logout URL is `#logout`, appended to the UI URL, for example, `\https://localhost:8443/#logout/`.
+
+The logout URL effectively performs the same action as clicking on the Log out link of the UI.
+
+
+[#ui-path]
+=== Changing the UI Path
+
+By default, the self service UI is registered at the root context and is accessible at the URL `\https://localhost:8443`. To specify a different URL, edit the `project-dir/conf/ui.context-selfservice.json` file, setting the `urlContextRoot` property to the new URL.
+
+For example, to change the URL of the self service UI to `\https://localhost:8443/exampleui`, edit the file as follows:
+
+[source, console]
+----
+"urlContextRoot" : "/exampleui",
+----
+Alternatively, to change the Self-Service UI URL in the Admin UI, follow these steps:
+
+====
+
+. Log in to the Admin UI.
+
+. Select Configure > System Preferences, and select the Self-Service UI tab.
+
+. Specify the new context route in the Relative URL field.
+
+====
+
+
+[#ui-disabling]
+=== Disabling the UI
+
+The UI is packaged as a separate bundle that can be disabled in the configuration before server startup. To disable the registration of the UI servlet, edit the `project-dir/conf/ui.context-selfservice.json` file, setting the `enabled` property to false:
+
+[source, console]
+----
+"enabled" : false,
+----
+
+
diff --git a/openidm-doc/src/main/asciidoc/integrators-guide/chap-users-groups-roles.adoc b/openidm-doc/src/main/asciidoc/integrators-guide/chap-users-groups-roles.adoc
new file mode 100644
index 000000000..3b09909ec
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/integrators-guide/chap-users-groups-roles.adoc
@@ -0,0 +1,1827 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-users-groups-roles]
+== Managing Users, Groups, Roles and Relationships
+
+OpenIDM provides a default schema for typical managed object types, such as users and roles, but does not control the structure of objects that you store in the OpenIDM repository. You can modify or extend the schema for the default object types, and you can set up a new managed object type for any item that can be collected in a data set. For example, with the right schema, you can set up any device associated with the Internet of Things (IoT).
+
+Managed objects and their properties are defined in your project's `conf/managed.json` file. Note that the schema defined in this file is not a comprehensive list of all the properties that can be stored in the managed object repository. If you use a generic object mapping, you can create a managed object with any arbitrary property, and that property will be stored in the repository. For more information about explicit and generic object mappings, see xref:chap-repo.adoc#explicit-generic-mapping["Using Explicit or Generic Object Mapping With a JDBC Repository"].
+
+This chapter describes how to work with the default managed object types and how to create new object types as required by your deployment. For more information about the OpenIDM object model, see xref:appendix-objects.adoc#appendix-objects["Data Models and Objects Reference"].
+
+[#creating-modifying-managed-objects]
+=== Creating and Modifying Managed Object Types
+
+If the managed object types provided in the default configuration are not sufficient for your deployment, you can create any number of new managed object types.
+
+The easiest way to create a new managed object type is to use the Admin UI, as follows:
+
+. Navigate to the Admin UI URL (`\https://localhost:8443/admin`) then select Configure > Managed Objects > New Managed Object.
+
+. Enter a name for the new managed object and, optionally, an icon that will be displayed for that object type in the UI.
++
+Click Save.
+
+. Select the Scripts tab and specify any scripts that should be applied on various events associated with that object type, for example, when an object of that type is created, updated or deleted.
+
+. Specify the schema for the object type, that is, the properties that make up the object, and any policies or restrictions that must be applied to the property values.
+
+Click the JSON button on the Schema tab to display the properties in JSON format. You can also create a new managed object type by adding its configuration, in JSON, to your project's `conf/managed.json` file. The following excerpt of the `managed.json` file shows the configuration of a "Phone" object, that was created through the UI.
+
+[source, javascript]
+----
+{
+ "name": "Phone",
+ "schema": {
+ "$schema": "http://forgerock.org/json-schema#",
+ "type": "object",
+ "properties": {
+ "brand": {
+ "description": "The supplier of the mobile phone",
+ "title": "Brand",
+ "viewable": true,
+ "searchable": true,
+ "userEditable": false,
+ "policies": [],
+ "returnByDefault": false,
+ "minLength": "",
+ "pattern": "",
+ "isVirtual": false,
+ "type": "string"
+ },
+ "assetNumber": {
+ "description": "The asset tag number of the mobile device",
+ "title": "Asset Number",
+ "viewable": true,
+ "searchable": true,
+ "userEditable": false,
+ "policies": [],
+ "returnByDefault": false,
+ "minLength": "",
+ "pattern": "",
+ "isVirtual": false,
+ "type": "string"
+ },
+ "model": {
+ "description": "The model number of the mobile device, such as 6 plus, Galaxy S4",
+ "title": "Model",
+ "viewable": true,
+ "searchable": false,
+ "userEditable": false,
+ "policies": [],
+ "returnByDefault": false,
+ "minLength": "",
+ "pattern": "",
+ "isVirtual": false,
+ "type": "string"
+ }
+ },
+ "required": [],
+ "order": [
+ "brand",
+ "assetNumber",
+ "model"
+ ]
+ }
+}
+----
+You can add any arbitrary properties to the schema of a new managed object type. A property definition typically includes the following fields:
+
+* `name` - the name of the property
+
+* `title` - the name of the property, in human-readable language, used to display the property in the UI
+
+* `description` - a description of the property
+
+* `viewable` - specifies whether this property is viewable in the object's profile in the UI). Boolean, `true` or `false` (`true` by default).
+
+* `searchable` - specifies whether this property can be searched in the UI. A searchable property is visible within the Managed Object data grid in the Self-Service UI. Note that for a property to be searchable in the UI, it __must be indexed__ in the repository configuration. For information on indexing properties in a repository, see xref:chap-repo.adoc#explicit-generic-mapping["Using Explicit or Generic Object Mapping With a JDBC Repository"].
++
+Boolean, `true` or `false` (`false` by default).
+
+* `userEditable` - specifies whether users can edit the property value in the UI. This property applies in the context of the self-service UI, where users are able to edit certain properties of their own accounts. Boolean, `true` or `false` (`false` by default).
+
+* `minLength` - the minimum number of characters that the value of this property must have.
+
+* `pattern` - any specific pattern to which the value of the property must adhere. For example, a property whose value is a date might require a specific date format.
+
+* `policies` - any policy validation that must be applied to the property. For more information on managed object policies, see xref:chap-policies.adoc#configuring-default-policy["Configuring the Default Policy for Managed Objects"].
+
+* `required` - specifies whether the property must be supplied when an object of this type is created. Boolean, `true` or `false`.
+
+* `type` - the data type for the property value; can be `String`, `Array`, `Boolean`, `Integer`, `Number`, `Object`, or `Resource Collection`.
+
+* `isVirtual` - specifies whether the property takes a static value, or whether its value is calculated "on the fly" as the result of a script. Boolean, `true` or `false`.
+
+* `returnByDefault` - for non-core attributes (virtual attributes and relationship fields), specifies whether the property will be returned in the results of a query on an object of this type __if it is not explicitly requested__. Virtual attributes and relationship fields are not returned by default. When configured in an array within a relationship, always set to `false` Boolean, `true` or `false`.
+
+
+
+[#working-with-managed-users]
+=== Working with Managed Users
+
+User objects that are stored in OpenIDM's repository are referred to as __managed users__. For a JDBC repository, OpenIDM stores managed users in the `managedobjects` table. A second table, `managedobjectproperties`, serves as the index table. For an OrientDB repository, managed users are stored in the `managed_user` table.
+
+OpenIDM provides RESTful access to managed users, at the context path `/openidm/managed/user`. For more information, see xref:../install-guide/chap-install.adoc#first-steps-with-rest["Getting Started With the OpenIDM REST Interface"] in the __Installation Guide__.
+
+
+[#working-with-groups]
+=== Working With Managed Groups
+
+OpenIDM provides support for a managed `group` object. For a JDBC repository, OpenIDM stores managed groups with all other managed objects, in the `managedobjects` table, and uses the `managedobjectproperties` for indexing. For an OrientDB repository, managed groups are stored in the `managed_group` table.
+
+The managed group object is not provided by default. To use managed groups, add an object similar to the following to your `conf/managed.json` file:
+
+[source]
+----
+{
+ "name" : "group"
+},
+----
+With this addition, OpenIDM provides RESTful access to managed groups, at the context path `/openidm/managed/group`.
+
+For an example of a deployment that uses managed groups, see xref:../samples-guide/chap-ldap-samples.adoc#more-sample-2d["Sample 2d - Synchronizing LDAP Groups"] in the __Samples Guide__.
+
+
+[#working-with-managed-roles]
+=== Working With Managed Roles
+
+OpenIDM supports two types of roles:
+
+* __Provisioning roles__ - used to specify how objects are provisioned to an external system.
+
+* __Authorization roles__ - used to specify the authorization rights of a managed object internally, within OpenIDM.
+
+Provisioning roles are always created as managed roles, at the context path `openidm/managed/role/role-name`. Provisioning roles are granted to managed users as values of the user's `roles` property.
+
+Authorization roles can be created either as managed roles (at the context path `openidm/managed/role/role-name`) or as internal roles (at the context path `openidm/repo/internal/role/role-name`). Authorization roles are granted to managed users as values of the user's `authzRoles` property.
+
+Both provisioning roles and authorization roles use the relationships mechanism to link the role to the managed object to which it applies. For more information about relationships between objects, see xref:#managing-relationships["Managing Relationships Between Objects"].
+
+This section describes how to create and use __managed roles__, either managed provisioning roles, or managed authorization roles. For more information about authorization roles, and how OpenIDM controls authorization to its own endpoints, see xref:chap-auth.adoc#openidm-authorization["Authorization"].
+
+__Managed roles__ are defined like any other managed object, and are granted to users through the __relationships__ mechanism.
+
+A managed role can be granted manually, as a static value of the user's `roles` or `authzRoles` attribute, or dynamically, as a result of a condition or script. For example, a user might be granted a role such as `sales-role` dynamically, if that user is in the `sales` organization.
+
+A managed user's `roles` and `authzRoles` attributes take an array of __references__ as a value, where the references point to the managed roles. For example, if user bjensen has been granted two provisioning roles (`employee` and `supervisor`), the value of bjensen's `roles` attribute would look something like the following:
+
+[source, javascript]
+----
+"roles": [
+ {
+ "_ref": "managed/role/employee",
+ "_refProperties": {
+ "_id": "c090818d-57fd-435c-b1b1-bb23f47eaf09",
+ "_rev": "1"
+ }
+ },
+ {
+ "_ref": "managed/role/supervisor",
+ "_refProperties": {
+ "_id": "4961912a-e2df-411a-8c0f-8e63b62dbef6",
+ "_rev": "1"
+ }
+ }
+ ]
+----
+
+[IMPORTANT]
+====
+The `_ref` property points to the ID of the managed role that has been granted to the user. This particular example uses a client-assigned ID that is the same as the role name, to make the example easier to understand. All other examples in this chapter use system-assigned IDs. In production, you should use system-assigned IDs for role objects.
+====
+The following sections describe how to create, read, update, and delete managed roles, and how to grant roles to users. For information about how roles are used to provision users to external systems, see xref:#working-with-role-assignments["Working With Role Assignments"]. For a sample that demonstrates the basic CRUD operations on roles, see xref:../samples-guide/chap-roles-sample.adoc#chap-roles-sample["Roles Samples - Demonstrating the OpenIDM Roles Implementation"] in the __Samples Guide__.
+
+[#create-new-role]
+==== Creating a Role
+
+The easiest way to create a new role is by using the Admin UI. Select Manage > Role and click New Role on the Role List page. Enter a name and description for the new role and click Save.
+
+Optionally, select Enable Condition to define a query filter that will allow this role to be granted to members dynamically. For more information, see xref:#granting-roles-dynamically["Granting Roles Dynamically"].
+
+To create a managed role over REST, send a PUT or POST request to the `/openidm/managed/role` context path. The following example creates a managed role named `employee`:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "name" : "employee",
+ "description" : "Role granted to workers on the company payroll"
+ }' \
+ "http://localhost:8080/openidm/managed/role?_action=create"
+{
+ "_id": "cedadaed-5774-4d65-b4a2-41d455ed524a",
+ "_rev": "1",
+ "name": "employee",
+ "description": "Role granted to workers on the company payroll"
+}
+----
+At this stage, the `employee` role has no corresponding __assignments__. Assignments are what enables the provisioning logic to the external system. Assignments are created and maintained as separate managed objects, and are referred to within role definitions. For more information about assignments, see xref:#working-with-role-assignments["Working With Role Assignments"].
+
+
+[#list-existing-roles]
+==== Listing Existing Roles
+
+You can display a list of all configured managed roles over REST or by using the Admin UI.
+
+To list the managed roles in the Admin UI, select Manage > Role.
+
+To list the managed roles over REST, query the `openidm/managed/role` endpoint. The following example shows the `employee` role that you created in the previous section:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role?_queryFilter=true"
+{
+ "result": [
+ {
+ "_id": "cedadaed-5774-4d65-b4a2-41d455ed524a",
+ "_rev": "1",
+ "name": "employee",
+ "description": "Role granted to workers on the company payroll"
+ }
+ ],
+...
+}
+----
+
+
+[#granting-role-user]
+==== Granting a Role to a User
+
+Roles are granted to users through the relationship mechanism. Relationships are essentially references from one managed object to another, in this case from a user object to a role object. For more information about relationships, see xref:#managing-relationships["Managing Relationships Between Objects"].
+
+Roles can be granted manually or dynamically.
+To grant a role manually, you must do one of the following:
+
+* Update the value of the user's `roles` property (if the role is a provisioning role) or `authzRoles` property (if the role is an authorization role) to reference the role.
+
+* Update the value of the role's `members` property to reference the user.
+
+Manual role grants are described further in xref:#granting-roles-manually["Granting Roles Manually"].
+
+Dynamic role grants use the result of a condition or script to update a user's list of roles. Dynamic role grants are described in detail in xref:#granting-roles-dynamically["Granting Roles Dynamically"].
+
+[#granting-roles-manually]
+===== Granting Roles Manually
+
+To grant a role to a user manually, use the Admin UI or the REST interface as follows:
+--
+
+Using the Admin UI::
+Use one of the following UI methods to grant a role to a user:
+
+* Update the user entry:
++
+
+. Select Manage > User and click on the user to whom you want to grant the role.
+
+. Select the Provisioning Roles tab and click Add Provisioning Roles.
+
+. Select the role from the dropdown list and click Add.
+
+
+* Update the role entry:
++
+
+. Select Manage > Role and click on the role that you want to grant.
+
+. Select the Role Members tab and click Add Role Members.
+
+. Select the user from the dropdown list and click Add.
+
+
+
+Over the REST interface::
+Use one of the following methods to grant a role to a user over REST:
+
+* Update the user to refer to the role.
++
+The following sample command grants the `employee` role (with ID `cedadaed-5774-4d65-b4a2-41d455ed524a`) to user scarter:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[
+ {
+ "operation": "add",
+ "field": "/roles/-",
+ "value": {"_ref" : "managed/role/cedadaed-5774-4d65-b4a2-41d455ed524a"}
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/scarter"
+{
+ "_id": "scarter",
+ "_rev": "2",
+ "mail": "scarter@example.com",
+ "givenName": "Steven",
+ "sn": "Carter",
+ "description": "Created By XML1",
+ "userName": "scarter@example.com",
+ "telephoneNumber": "1234567",
+ "accountStatus": "active",
+ "effectiveRoles": [
+ {
+ "_ref": "managed/role/cedadaed-5774-4d65-b4a2-41d455ed524a"
+ }
+ ],
+ "effectiveAssignments": []
+}
+----
++
+Note that scarter's `effectiveRoles` attribute has been updated with a reference to the new role. For more information about effective roles and effective assignments, see xref:#effective-roles-and-assignments["Understanding Effective Roles and Effective Assignments"].
+
+* Update the role to refer to the user.
++
+The following sample command makes scarter a member of the `employee` role:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[
+ {
+ "operation": "add",
+ "field": "/members/-",
+ "value": {"_ref" : "managed/user/scarter"}
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/role/cedadaed-5774-4d65-b4a2-41d455ed524a"
+{
+ "_id": "cedadaed-5774-4d65-b4a2-41d455ed524a",
+ "_rev": "2",
+ "name": "employee",
+ "description": "Role granted to workers on the company payroll"
+}
+----
++
+Note that the `members` attribute of a role is not returned by default in the output. To show all members of a role, you must specifically request the relationship properties (`*_ref`) in your query. The following sample command lists the members of the `employee` role (currently only scarter):
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role/cedadaed-5774-4d65-b4a2-41d455ed524a?_fields=*_ref,name"
+ {
+ "_id": "cedadaed-5774-4d65-b4a2-41d455ed524a",
+ "_rev": "1",
+ "name": "employee",
+ "members": [
+ {
+ "_ref": "managed/user/scarter",
+ "_refProperties": {
+ "_id": "98d22d75-7090-47f8-9608-01ff92b447a4",
+ "_rev": "1"
+ }
+ }
+ ],
+ "authzMembers": [],
+ "assignments": []
+}
+----
+
+* You can replace an existing role grant with a new one by using the `replace` operation in your patch request. The following command
++
+The following command replaces scarter's entire `roles` entry (that is, overwrites any existing roles) with a single entry, the reference to the `employee` role (ID `cedadaed-5774-4d65-b4a2-41d455ed524a`):
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[
+ {
+ "operation": "replace",
+ "field":"/roles",
+ "value":[
+ {"_ref":"managed/role/cedadaed-5774-4d65-b4a2-41d455ed524a"}
+ ]
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/scarter"
+----
+
+
+--
+
+
+[#granting-roles-dynamically]
+===== Granting Roles Dynamically
+
+The previous section showed how to grant roles to a user manually, by listing a reference to the role as a value of the user's `roles` attribute. OpenIDM also supports the following methods of granting a role __dynamically__:
+
+* Granting a role based on a condition, where that condition is expressed in a query filter in the role definition. If the condition is `true` for a particular member, that member is granted the role.
+
+* Using a custom script to define a more complex role granting strategy.
+
+
+[#conditional-role-grants]
+====== Granting Roles Based on a Condition
+
+A role that is granted based on a defined condition is called a __conditional role__. To create a conditional role, include a query filter in the role definition.
+
+To create a conditional role by using the Admin UI, select Condition on the role Details page, then define the query filter that will be used to assess the condition. In the following example, the role `fr-employee` will be granted only to those users who live in France (whose `country` property is set to `FR`):
+
+[#d0e8129]
+image::images/conditional-role.png[]
+To create a conditional role over REST, include the query filter as a value of the `condition` property in the role definition. The following command creates a role similar to the one created in the previous screen shot:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "name": "fr-employee",
+ "description": "Role granted to employees resident in France",
+ "condition": "/country eq \"FR\""
+ }' \
+ "http://localhost:8080/openidm/managed/role?_action=create"
+ {
+ "_id": "4b0a3e42-e5be-461b-a995-3e66c74551c1",
+ "_rev": "1",
+ "name": "fr-employee",
+ "description": "Role granted to employees resident in France",
+ "condition": "/country eq \"FR\""
+}
+----
+When a conditional role is created or updated, OpenIDM automatically assesses all managed users, and recalculates the value of their `roles` property, if they qualify for that role. When a condition is removed from a role, that is, when the role becomes an unconditional role, all conditional grants removed. So, users who were granted the role based on the condition have that role removed from their `roles` property.
+
+[CAUTION]
+====
+When a conditional role is defined in an existing data set, every user entry (including the mapped entries on remote systems) must be updated with the assignments implied by that conditional role. The time that it takes to create a new conditional role is impacted by the following items:
+
+* The number of managed users affected by the condition
+
+* The number of assignments related to the conditional role
+
+* The average time required to provision updates to all remote systems affected by those assignments
+
+In a data set with a very large number of users, creating a new conditional role can therefore incur a significant performance cost at the time of creation. Ideally, you should set up your conditional roles at the beginning of your deployment to avoid performance issues later.
+====
+
+
+[#dynamic-role-scripts]
+====== Granting Roles By Using Custom Scripts
+
+The easiest way to grant roles dynamically is to use conditional roles, as described in xref:#conditional-role-grants["Granting Roles Based on a Condition"]. If your deployment requires complex conditional logic that cannot be achieved with a query filter, you can create a custom script to grant the role, as follows:
+
+====
+
+. Create a `roles` directory in your project's `script` directory and copy the default effective roles script to that new directory:
++
+
+[source, console]
+----
+$ mkdir project-dir/script/roles/
+$ cp /path/to/openidm/bin/defaults/script/roles/effectiveRoles.js \
+ project-dir/script/roles/
+----
++
+The new script will override the default effective roles script.
+
+. Modify the script to reference additional roles that have not been granted manually, or as the result of a conditional grant. The effective roles script calculates the grants that are in effect when the user is retrieved.
++
+For example, the following addition to the `effectiveRoles.js` script grants the roles `dynamic-role1` and `dynamic-role2` to all active users (managed user objects whose `accountStatus` value is `active`). This example assumes that you have already created the managed roles, `dynamic-role1` (with ID `d2e29d5f-0d74-4d04-bcfe-b1daf508ad7c`) and `dynamic-role2` (with ID `709fed03-897b-4ff0-8a59-6faaa34e3af6`, and their corresponding assignments:
++
+
+[source, javascript]
+----
+// This is the location to expand to dynamic roles,
+// project role script return values can then be added via
+// effectiveRoles = effectiveRoles.concat(dynamicRolesArray);
+
+if (object.accountStatus === 'active') {
+ effectiveRoles = effectiveRoles.concat([
+ {"_ref": "managed/role/d2e29d5f-0d74-4d04-bcfe-b1daf508ad7c"},
+ {"_ref": "managed/role/709fed03-897b-4ff0-8a59-6faaa34e3af6"}
+ ]);
+}
+----
+
+====
+
+[NOTE]
+====
+For conditional roles, the user's `roles` property is updated if the user meets the condition. For custom scripted roles, the user's `effectiveRoles` property is calculated when the user is retrieved and includes the dynamic roles according to the custom script.
+====
+If you make any of the following changes to a scripted role grant, you must perform a manual reconciliation of all affected users before assignment changes will take effect on an external system:
+
+* If you create a new scripted role grant.
+
+* If you change the definition of an existing scripted role grant.
+
+* If you change any of the assignment rules for a role that is granted by a custom script.
+
+
+
+
+
+[#roles-temporal-constraints]
+==== Using Temporal Constraints to Restrict Effective Roles
+
+To restrict the period during which a role is effective, you can set a temporal constraint on the role itself, or on the role grant. A temporal constraint that is set on a role definition applies to all grants of that role. A temporal constraint that is set on a role grant enables you to specify the period that the role is valid __per user__.
+
+For example, you might want a role definition such as `contractors-2016` to apply to all contract employees __only__ for the year 2016. Or you might want a `contractors` role to apply to an individual user only during the duration of his contract of employment.
+
+The following sections describe how to set temporal constraints on role definitions, and on individual role grants.
+
+[#temporal-constraints-role-definition]
+===== Adding a Temporal Constraint to a Role Definition
+
+When you create a role, you can include a temporal constraint in the role definition, which restricts the validity of the entire role, regardless of how that role is granted. Temporal constraints are expressed as a `duration` in ISO 8601 date and time format. For more information on this format, see link:https://en.wikipedia.org/wiki/ISO_8601#Durations[https://en.wikipedia.org/wiki/ISO_8601#Durations, window=\_blank].
+
+To restrict the period during which a role is valid by using the Admin UI, select Temporal Constraint on the role Details page, then select the timezone and start and end dates for the required period.
+
+In the following example, the role `contractor` is effective from January 1st, 2016 to January 1st, 2017:
+
+[#d0e8300]
+image::images/temporal-role.png[]
+The following example adds a similar `contractor` role, over the REST interface:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "name" : "contractor",
+ "description" : "Role granted to contract workers for 2016",
+ "temporalConstraints" : [
+ {
+ "duration" : "2016-01-01T00:00:00.000Z/2017-01-01T00:00:00.000Z"
+ }
+ ]
+ }' \
+ "http://localhost:8080/openidm/managed/role?_action=create"
+{
+ "_id": "071283a8-0237-40a2-a31e-ceaa4d93c93d",
+ "_rev": "1",
+ "name": "contractor",
+ "description": "Role granted to contract workers for 2016",
+ "temporalConstraints": [
+ {
+ "duration": "2016-01-01T00:00:00.000Z/2017-01-01T00:00:00.000Z"
+ }
+ ]
+}
+----
+The preceding example specifies the time zone as Coordinated Universal Time (UTC) by appending `Z` to the time. If no time zone information is provided, the time zone is assumed to be local time. To specify a different time zone, include an offset (from UTC) in the format `±hh:mm`. For example, a duration of `2016-01-01T00:00:00.000+04:00/2017-01-01T00:00:00.000+04:00` specifies a time zone that is four hours ahead of UTC.
+
+When the period defined by the constraint has ended, the role object remains in the repository but the effective roles script will not include the role in the list of effective roles for any user.
+
+The following example assumes that user scarter has been granted a role `contractor-april`. A temporal constraint has been included in the `contractor-april` definition that specifies that the role should be applicable only during the month of April 2016. At the end of this period, a query on scarter's entry shows that his `roles` property still includes the `contractor-april` role (with ID `3eb67be6-205b-483d-b36d-562b43a04ff8`), but his `effectiveRoles` property does not:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/scarter?_fields=_id,userName,roles,effectiveRoles"
+{
+ "_id": "scarter",
+ "_rev": "1",
+ "userName": "scarter@example.com",
+ "roles": [
+ {
+ "_ref": "managed/role/3eb67be6-205b-483d-b36d-562b43a04ff8",
+ "_refProperties": {
+ "temporalConstraints": [],
+ "_grantType": "",
+ "_id": "257099f5-56e5-4ce0-8580-f0f4d4b93d93",
+ "_rev": "1"
+ }
+ }
+ ],
+ "effectiveRoles": []
+}
+----
+In other words, the role is still in place but is no longer effective.
+
+
+[#temporal-constraints-role-grant]
+===== Adding a Temporal Constraint to a Role Grant
+
+To restrict the validity of a role for individual users, you can apply a temporal constraint at the grant level, rather than as part of the role definition. In this case, the temporal constraint is taken into account per user, when the user's effective roles are calculated. Temporal constraints that are defined at the grant level can be different for each user who is a member of that role.
+
+To restrict the period during which a role grant is valid by using the Admin UI, set a temporal constraint when you add the member to the role.
+
+For example, to specify that bjensen be added to a Contractor role only for the duration of her employment contract, select Manage > Role, click the Contractor role, and click Add Role Members. On the Add Role Members screen, select bjensen from the list, then enable the Temporal Constraint and specify the start and end date of her contract.
+
+To apply a temporal constraint to a grant over the REST interface, include the constraint as one of the `_refProperties` of the relationship between the user and the role. The following example assumes a `contractor` role, with ID `9321fd67-30d1-4104-934d-cfd0a22e8182`. The command adds user bjensen as a member of that role, with a temporal constraint that specifies that she be a member of the role only for one year, from January 1st, 2016 to January 1st, 2017:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[
+ {
+ "operation": "add",
+ "field": "/members/-",
+ "value": {
+ "_ref" : "managed/user/bjensen",
+ "_refProperties": {
+ "temporalConstraints": [{"duration": "2016-01-01T00:00:00.000Z/2017-01-01T00:00:00.000Z"}]
+ }
+ }
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/role/9321fd67-30d1-4104-934d-cfd0a22e8182"
+{
+ "_id": "9321fd67-30d1-4104-934d-cfd0a22e8182",
+ "_rev": "2",
+ "name": "contractor",
+ "description": "Role for contract workers"
+}
+----
+A query on bjensen's roles property shows that the temporal constraint has been applied to this grant:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/bjensen/roles?_queryFilter=true"
+{
+ "result": [
+ {
+ "_ref": "managed/role/9321fd67-30d1-4104-934d-cfd0a22e8182",
+ "_refProperties": {
+ "temporalConstraints": [
+ {
+ "duration": "2016-01-01T00:00:00.000Z/2017-01-01T00:00:00.000Z"
+ }
+ ],
+ "_id": "84f5342c-cebe-4f0b-96c9-0267bf68a095",
+ "_rev": "1"
+ }
+ }
+ ],
+...
+}
+----
+
+
+
+[#querying-user-roles]
+==== Querying a User's Manual and Conditional Roles
+
+The easiest way to check what roles have been granted to a user, either manually, or as the result of a condition, is to look at the user's entry in the Admin UI. Select Manage > User, click on the user whose roles you want to see, and select the Provisioning Roles tab.
+
+To obtain a similar list over the REST interface, you can query the user's `roles` property. The following sample query shows that scarter has been granted two roles - an `employee` role (with ID `6bf4701a-7579-43c4-8bb4-7fd6cac552a1`) and an `fr-employee` role (with ID `00561df0-1e7d-4c8a-9c1e-3b1096116903`). specifies :
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/scarter/roles?_queryFilter=true&_fields=_ref,_refProperties,name"
+{
+ "result": [
+ {
+ "_ref": "managed/role/6bf4701a-7579-43c4-8bb4-7fd6cac552a1",
+ "_refProperties": {
+ "temporalConstraints": [],
+ "_grantType": "",
+ "_id": "8417106e-c3ef-4f59-a482-4c92dbf00308",
+ "_rev": "2"
+ },
+ "name": "employee"
+ },
+ {
+ "_ref": "managed/role/00561df0-1e7d-4c8a-9c1e-3b1096116903",
+ "_refProperties": {
+ "_grantType": "conditional",
+ "_id": "e59ce7c3-46ce-492a-ba01-be27af731435",
+ "_rev": "1"
+ },
+ "name": "fr-employee"
+ }
+ ],
+ ...
+}
+----
+Note that the `fr-employee` role has an additional reference property, `_grantType`. This property indicates __how__ the role was granted to the user. If there is no `_grantType`, the role was granted manually.
+
+Querying a user's roles in this way __does not__ return any roles that would be in effect as a result of a custom script, or of any temporal constraint applied to the role. To return a complete list of __all__ the roles in effect at a specific time, you need to query the user's `effectiveRoles` property, as follows:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/scarter?_fields=effectiveRoles"
+----
+
+
+[#delete-role-user]
+==== Deleting a User's Roles
+
+Roles that have been granted manually can be removed from a user's entry in two ways:
+
+* Update the value of the user's `roles` property (if the role is a provisioning role) or `authzRoles` property (if the role is an authorization role) to remove the reference to the role.
+
+* Update the value of the role's `members` property to remove the reference to that user.
+
+Both of these actions can be achieved by using the Admin UI, or over REST.
+--
+
+Using the Admin UI::
+Use one of the following methods to remove a user's roles:
+
+* Select Manage > User and click on the user whose role or roles you want to remove.
++
+Select the Provisioning Roles tab, select the role that you want to remove, and click Remove Selected Provisioning Roles.
+
+* Select Manage > Role and click on the role whose members you want to remove.
++
+Select the Role Members tab, select the member or members that that you want to remove, and click Remove Selected Role Members.
+
+
+Over the REST interface::
+Use one of the following methods to remove a role grant from a user:
+
+* Delete the role from the user's `roles` property, including the reference ID (the ID of the relationship between the user and the role) in the delete request:
++
+The following sample command removes the `employee` role (with ID `6bf4701a-7579-43c4-8bb4-7fd6cac552a1`) from user scarter:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/user/scarter/roles/8417106e-c3ef-4f59-a482-4c92dbf00308"
+{
+ "_ref": "managed/role/6bf4701a-7579-43c4-8bb4-7fd6cac552a1",
+ "_refProperties": {
+ "temporalConstraints": [],
+ "_grantType": "",
+ "_id": "8417106e-c3ef-4f59-a482-4c92dbf00308",
+ "_rev": "2"
+ }
+}
+----
+
+* PATCH the user entry to remove the role from the array of roles, specifying the __value__ of the role object in the JSON payload.
++
+
+[CAUTION]
+======
+When you remove a role in this way, you must include the __entire object__ in the value, as shown in the following example:
+======
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "remove",
+ "field" : "/roles",
+ "value" : {
+ "_ref": "managed/role/6bf4701a-7579-43c4-8bb4-7fd6cac552a1",
+ "_refProperties": {
+ "temporalConstraints": [],
+ "_grantType": "",
+ "_id": "8417106e-c3ef-4f59-a482-4c92dbf00308",
+ "_rev": "1"
+ }
+ }
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/scarter"
+{
+ "_id": "scarter",
+ "_rev": "3",
+ "mail": "scarter@example.com",
+ "givenName": "Steven",
+ "sn": "Carter",
+ "description": "Created By XML1",
+ "userName": "scarter@example.com",
+ "telephoneNumber": "1234567",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
+
+* Delete the user from the role's `members` property, including the reference ID (the ID of the relationship between the user and the role) in the delete request.
++
+The following example first queries the members of the `employee` role, to obtain the ID of the relationship, then removes bjensen's membership from that role:
++
+
+[source, console]
+----
+$ url \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role/6bf4701a-7579-43c4-8bb4-7fd6cac552a1/members?_queryFilter=true"
+{
+ "result": [
+ {
+ "_ref": "managed/user/bjensen",
+ "_refProperties": {
+ "temporalConstraints": [],
+ "_grantType": "",
+ "_id": "3c047f39-a9a3-4030-8d0c-bcd1fadb1d3d",
+ "_rev": "3"
+ }
+ }
+ ],
+...
+}
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/role/6bf4701a-7579-43c4-8bb4-7fd6cac552a1/members/3c047f39-a9a3-4030-8d0c-bcd1fadb1d3d"
+{
+ "_ref": "managed/user/bjensen",
+ "_refProperties": {
+ "temporalConstraints": [],
+ "_grantType": "",
+ "_id": "3c047f39-a9a3-4030-8d0c-bcd1fadb1d3d",
+ "_rev": "3"
+ }
+}
+----
+
+
+--
+
+[NOTE]
+====
+Roles that have been granted as the result of a condition can only be removed when the condition is changed or removed, or when the role itself is deleted.
+====
+
+
+[#delete-role-definition]
+==== Deleting a Role Definition
+
+You can delete a managed provisioning or authorization role by using the Admin UI, or over the REST interface.
+
+To delete a role by using the Admin UI, select Manage > Role, select the role you want to remove, and click Delete.
+
+To delete a role over the REST interface, simply delete that managed object. The following command deletes the `employee` role created in the previous section:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/role/6bf4701a-7579-43c4-8bb4-7fd6cac552a1"
+{
+ "_id": "6bf4701a-7579-43c4-8bb4-7fd6cac552a1",
+ "_rev": "1",
+ "name": "employee",
+ "description": "Role granted to workers on the company payroll"
+}
+----
+
+[NOTE]
+====
+You cannot delete a role if it is currently granted to one or more users. If you attempt to delete a role that is granted to a user (either over the REST interface, or by using the Admin UI), OpenIDM returns an error. The following command indicates an attempt to remove the `employee` role while it is still granted to user scarter:
+====
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/role/6bf4701a-7579-43c4-8bb4-7fd6cac552a1"
+{
+ "code":409,
+ "reason":"Conflict",
+ "message":"Cannot delete a role that is currently granted"
+ }
+----
+
+
+[#working-with-role-assignments]
+==== Working With Role Assignments
+
+__Authorization roles__ control access to OpenIDM itself. __Provisioning roles__ define rules for how attribute values are updated on external systems. These rules are configured through __assignments__ that are attached to a provisioning role definition. The purpose of an assignment is to provision an attribute or set of attributes, based on an object's role membership.
+
+The synchronization mapping configuration between two resources (defined in the `sync.json` file) provides the basic account provisioning logic (how an account is mapped from a source to a target system). Role assignments provide additional provisioning logic that is not covered in the basic mapping configuration. The attributes and values that are updated by using assignments might include group membership, access to specific external resources, and so on. A group of assignments can collectively represent a __role__.
+
+Assignment objects are created, updated and deleted like any other managed object, and are attached to a role by using the relationships mechanism, in much the same way as a role is granted to a user. Assignment are stored in the repository and are accessible at the context path `/openidm/managed/assignment`.
+
+This section describes how to manipulate managed assignments over the REST interface, and by using the Admin UI. When you have created an assignment, and attached it to a role definition, all user objects that reference that role definition will, as a result, reference the corresponding assignment in their `effectiveAssignments` attribute.
+
+[#creating-an-assignment]
+===== Creating an Assignment
+
+The easiest way to create an assignment is by using the Admin UI, as follows:
+
+. Select Manage > Assignment and click New Assignment on the Assignment List page.
+
+. Enter a name and description for the new assignment, and select the mapping to which the assignment should apply. The mapping indicates the target resource, that is, the resource on which the attributes specified in the assignment will be adjusted.
+
+. Click Add Assignment.
+
+. Select the Attributes tab and select the attribute or attributes whose values will be adjusted by this assignment.
++
+
+* If a regular text field appears, specify what the value of the attribute should be, when this assignment is applied.
+
+* If an Item button appears, you can specify a managed object type, such as an object, relationship, or string.
+
+* If a Properties button appears, you can specify additional information such as an array of role references, as described in xref:#working-with-managed-roles["Working With Managed Roles"].
+
+
+. Select the assignment operation from the dropdown list:
++
+
+* `Merge With Target` - the attribute value will be added to any existing values for that attribute. This operation merges the existing value of the target object attribute with the value(s) from the assignment. If duplicate values are found (for attributes that take a list as a value), each value is included only once in the resulting target. This assignment operation is used only with complex attribute values like arrays and objects, and does not work with strings or numbers. (Property: `mergeWithTarget`.)
+
+* `Replace Target` - the attribute value will overwrite any existing values for that attribute. The value from the assignment becomes the authoritative source for the attribute. (Property: `replaceTarget`.)
+
++
+Select the unassignment operation from the dropdown list. You can set the unassignment operation to one of the following:
++
+
+* `Remove From Target` - the attribute value is removed from the system object when the user is no longer a member of the role, or when the assignment itself is removed from the role definition. (Property: `removeFromTarget`.)
+
+* `No Operation` - removing the assignment from the user's `effectiveAssignments` has no effect on the current state of the attribute in the system object. (Property: `noOp`.)
+
+
+. Optionally, click the Events tab to specify any scriptable events associated with this assignment.
++
+The assignment and unassignment operations described in the previous step operate at the __attribute level__. That is, you specify what should happen with each attribute affected by the assignment when the assignment is applied to a user, or removed from a user.
++
+The scriptable __On assignment__ and __On unassignment__ events operate at the __assignment level__, rather than the attribute level. You define scripts here to apply additional logic or operations that should be performed when a user (or other object) receives or loses an entire assignment. This logic can be anything that is not restricted to an operation on a single attribute.
+
+. Click the Roles tab to attach this assignment to an existing role definition.
+
+To create a new assignment over REST, send a PUT or POST request to the `/openidm/managed/assignment` context path.
+
+The following example creates a new managed assignment named `employee`. The JSON payload in this example shows the following:
+
+* The assignment is applied for the mapping `managedUser_systemLdapAccounts`, so attributes will be updated on the external LDAP system specified in this mapping.
+
+* The name of the attribute on the external system whose value will be set is `employeeType` and its value will be set to `Employee`.
+
+* When the assignment is applied during a sync operation, the attribute value `Employee` will be added to any existing values for that attribute. When the assignment is removed (if the role is deleted, or if the managed user is no longer a member of that role), the attribute value `Employee` will be removed from the values of that attribute.
+
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "name" : "employee",
+ "description": "Assignment for employees.",
+ "mapping" : "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "employeeType",
+ "value": "Employee",
+ "assignmentOperation" : "mergeWithTarget",
+ "unassignmentOperation" : "removeFromTarget"
+ }
+ ]
+ }' \
+ "http://localhost:8080/openidm/managed/assignment?_action=create"
+{
+ "_id": "2fb3aa12-109f-431c-bdb7-e42213747700",
+ "_rev": "1",
+ "name": "employee",
+ "description": "Assignment for employees.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "employeeType",
+ "value": "Employee",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ]
+}
+----
+Note that at this stage, the assignment is not linked to any role, so no user can make use of the assignment. You must add the assignment to a role, as described in the following section.
+
+
+[#adding-assignment-to-role]
+===== Adding an Assignment to a Role
+
+When you have created a managed role, and a managed assignment, you reference the assignment from the role, in much the same way as a user references a role.
+
+You can update a role definition to include one or more assignments, either by using the Admin UI, or over the REST interface.
+--
+
+Using the Admin UI::
+
+. Select Manage > Role and click on the role to which you want to add an assignment.
+
+. Select the Managed Assignments tab and click Add Managed Assignments.
+
+. Select the assignment that you want to add to the role and click Add.
+
+
+Over the REST interface::
+Update the role definition to include a reference to the ID of the assignment in the `assignments` property of the role. The following sample command adds the `employee` assignment (with ID `2fb3aa12-109f-431c-bdb7-e42213747700`) to an existing `employee` role (whose ID is `59a8cc01-bac3-4bae-8012-f639d002ad8c`):
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/assignments/-",
+ "value" : { "_ref": "managed/assignment/2fb3aa12-109f-431c-bdb7-e42213747700" }
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/role/59a8cc01-bac3-4bae-8012-f639d002ad8c"
+{
+ "_id": "59a8cc01-bac3-4bae-8012-f639d002ad8c",
+ "_rev": "3",
+ "name": "employee",
+ "description": "Role granted to workers on the company payroll"
+}
+----
++
+To check that the assignment was added successfully, you can query the `assignments` property of the role:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role/59a8cc01-bac3-4bae-8012-f639d002ad8c/assignments?_queryFilter=true&_fields=_ref,_refProperties,name"
+
+{
+ "result": [
+ {
+ "_ref": "managed/assignment/2fb3aa12-109f-431c-bdb7-e42213747700",
+ "_refProperties": {
+ "_id": "686b328a-e2bd-4e48-be25-4a4e12f3b431",
+ "_rev": "4"
+ },
+ "name": "employee"
+ }
+ ],
+...
+}
+----
++
+Note that the role's `assignments` property now references the assignment that you created in the previous step.
+
+--
+To remove an assignment from a role definition, remove the reference to the assignment from the role's `assignments` property.
+
+
+[#delete-managed-assignment]
+===== Deleting an Assignment
+
+You can delete an assignment by using the Admin UI, or over the REST interface.
+
+To delete an assignment by using the Admin UI, select Manage > Assignment, select the assignment you want to remove, and click Delete.
+
+To delete an assignment over the REST interface, simply delete that object. The following command deletes the `employee` assignment created in the previous section:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/assignment/2fb3aa12-109f-431c-bdb7-e42213747700"
+ {
+ "_id": "2fb3aa12-109f-431c-bdb7-e42213747700",
+ "_rev": "1",
+ "name": "employee",
+ "description": "Assignment for employees.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "employeeType",
+ "value": "Employee",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ]
+}
+----
+
+[NOTE]
+====
+You __can__ delete an assignment, even if it is referenced by a managed role. When the assignment is removed, any users to whom the corresponding roles were granted will no longer have that assignment in their list of `effectiveAssignments`. For more information about effective roles and effective assignments, see xref:#effective-roles-and-assignments["Understanding Effective Roles and Effective Assignments"].
+====
+
+
+
+[#effective-roles-and-assignments]
+==== Understanding Effective Roles and Effective Assignments
+
+__Effective roles__ and __effective assignments__ are virtual properties of a user object. Their values are calculated __on the fly__ by the `openidm/bin/defaults/script/roles/effectiveRoles.js` and `openidm/bin/defaults/script/roles/effectiveAssignments.js` scripts. These scripts are triggered when a managed user is retrieved.
+
+The following excerpt of a `managed.json` file shows how these two virtual properties are constructed for each managed user object:
+
+[source, javascript]
+----
+"effectiveRoles" : {
+ "type" : "array",
+ "title" : "Effective Roles",
+ "viewable" : false,
+ "returnByDefault" : true,
+ "isVirtual" : true,
+ "onRetrieve" : {
+ "type" : "text/javascript",
+ "source" : "require('roles/effectiveRoles').calculateEffectiveRoles(object, 'roles');"
+ },
+ "items" : {
+ "type" : "object"
+ }
+},
+"effectiveAssignments" : {
+ "type" : "array",
+ "title" : "Effective Assignments",
+ "viewable" : false,
+ "returnByDefault" : true,
+ "isVirtual" : true,
+ "onRetrieve" : {
+ "type" : "text/javascript",
+ "file" : "roles/effectiveAssignments.js",
+ "effectiveRolesPropName" : "effectiveRoles"
+ },
+ "items" : {
+ "type" : "object"
+ }
+},
+----
+When a role references an assignment, and a user references the role, that user automatically references the assignment in its list of effective assignments.
+
+The `effectiveRoles.js` script uses the `roles` attribute of a user entry to calculate the grants (manual or conditional) that are currently in effect at the time of retrieval, based on temporal constraints or other custom scripted logic.
+
+The `effectiveAssignments.js` script uses the virtual `effectiveRoles` attribute to calculate that user's effective assignments. The synchronization engine reads the calculated value of the `effectiveAssignments` attribute when it processes the user. The target system is updated according to the configured `assignmentOperation` for each assignment.
+
+Do not change the default `effectiveRoles.js` and `effectiveAssignments.js` scripts. If you need to change the logic that calculates `effectiveRoles` and `effectiveAssignments`, create your own custom script and include a reference to it in your project's `conf/managed.json` file. For more information about using custom scripts, see xref:appendix-scripting.adoc#appendix-scripting["Scripting Reference"].
+
+When a user entry is retrieved, OpenIDM calculates the `effectiveRoles` and `effectiveAssignments` for that user based on the current value of the user's `roles` property, and on any roles that might be granted dynamically through a custom script. The previous set of examples showed the creation of a role `employee` that referenced an assignment `employee` and was granted to user bjensen. Querying that user entry would show the following effective roles and effective assignments:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/bjensen?_fields=userName,roles,effectiveRoles,effectiveAssignments"
+{
+ "_id": "bjensen",
+ "_rev": "2",
+ "userName": "bjensen@example.com",
+ "roles": [
+ {
+ "_ref": "managed/role/59a8cc01-bac3-4bae-8012-f639d002ad8c",
+ "_refProperties": {
+ "temporalConstraints": [],
+ "_grantType": "",
+ "_id": "881f0b96-06e9-4af4-b86b-aba4ee15e4ef",
+ "_rev": "2"
+ }
+ }
+ ],
+ "effectiveRoles": [
+ {
+ "_ref": "managed/role/59a8cc01-bac3-4bae-8012-f639d002ad8c"
+ }
+ ],
+ "effectiveAssignments": [
+ {
+ "name": "employee",
+ "description": "Assignment for employees.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "employeeType",
+ "value": "Employee",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ],
+ "_id": "4606245c-9412-4f1f-af0c-2b06852dedb8",
+ "_rev": "2"
+ }
+ ]
+}
+----
+In this example, synchronizing the managed/user repository with the external LDAP system defined in the mapping should populate user bjensen's `employeeType` attribute in LDAP with the value `employee`.
+
+
+[#managed-role-script-hooks]
+==== Managed Role Script Hooks
+
+Like any other managed object, a role has script hooks that enable you to configure role behavior. The default role definition in `conf/managed.json` includes the following script hooks:
+
+[source, javascript]
+----
+{
+ "name" : "role",
+ "onDelete" : {
+ "type" : "text/javascript",
+ "file" : "roles/onDelete-roles.js"
+ },
+ "onSync" : {
+ "type" : "text/javascript",
+ "source" : "require('roles/onSync-roles').syncUsersOfRoles(resourceName, oldObject, newObject, ['members']);"
+ },
+ "onCreate" : {
+ "type" : "text/javascript",
+ "source" : "require('roles/conditionalRoles').roleCreate(object);"
+ },
+ "onUpdate" : {
+ "type" : "text/javascript",
+ "source" : "require('roles/conditionalRoles').roleUpdate(oldObject, object);"
+ },
+ "postCreate" : {
+ "type" : "text/javascript",
+ "file" : "roles/postOperation-roles.js"
+ },
+ "postUpdate" : {
+ "type" : "text/javascript",
+ "file" : "roles/postOperation-roles.js"
+ },
+ "postDelete" : {
+ "type" : "text/javascript",
+ "file" : "roles/postOperation-roles.js"
+ },
+...
+----
+When a role is deleted, the `onDelete` script hook calls the `bin/default/script/roles/onDelete-roles.js` script.
+
+When a role is synchronized, the `onSync` hook causes a synchronization operation on all managed objects that reference the role.
+
+When a __conditional role__ is created or updated, the `onCreate` and `onUpdate` script hooks force an update on all managed users affected by the conditional role.
+
+Directly after a role is created, updated or deleted, the `postCreate`, `postUpdate`, and `postDelete` hooks call the `bin/default/script/roles/postOperation-roles.js` script. Depending on when this script is called, it either creates or removes the scheduled jobs required to manage temporal constraints on roles.
+
+
+
+[#managing-relationships]
+=== Managing Relationships Between Objects
+
+OpenIDM enables you to define __relationships__ between two managed objects. Managed roles are implemented using relationship objects, but you can create a variety of relationship objects, as required by your deployment.
+
+[#defining-a-relationship-type]
+==== Defining a Relationship Type
+
+Relationships are defined in your project's managed object configuration file (`conf/managed.json`). By default, OpenIDM provides a relationship named `manager`, that enables you to configure a management relationship between two managed users. The `manager` relationship is a good example from which to understand how relationships work.
+
+The default `manager` relationship is configured as follows:
+
+[source, javascript]
+----
+"manager" : {
+ "type" : "relationship",
+ "returnByDefault" : false,
+ "description" : "",
+ "title" : "Manager",
+ "viewable" : true,
+ "searchable" : false,
+ "properties" : {
+ "_ref" : { "type" : "string" },
+ "_refProperties": {
+ "type": "object",
+ "properties": {
+ "_id": { "type": "string" }
+ }
+ }
+},
+----
+--
+All relationships have the following configurable properties:
+
+`type` (string)::
+The object type. Must be `relationship` for a relationship object.
+
+`returnByDefault` (boolean `true, false`)::
+Specifies whether the relationship should be returned in the result of a read or search query on the managed object that has the relationship. If included in an array, always set this property to `false`. By default, relationships are not returned, unless explicitly requested.
+
+`description` (string, optional)::
+An optional string that provides additional information about the relationship object.
+
+`title` (string)::
+Used by the UI to refer to the relationship.
+
+`viewable` (boolean, `true, false`)::
+Specifies whether the relationship is visible as a field in the UI. The default value is `true`.
+
+`searchable` (boolean, `true, false`)::
+Specifies whether values of the relationship can be searched, in the UI. For example, if you set this property to `true` for the `manager` relationship, a user will be able to search for managed user entries using the `manager` field as a filter.
+
+`_ref` (JSON object)::
+Specifies how the relationship between two managed objects is referenced.
+
++
+In the relationship definition, the value of this property is `{ "type" : "string" }`. In a managed user entry, the value of the `_ref` property is the reference to the other resource. The `_ref` property is described in more detail in xref:#establishing-relationships-between-objects["Establishing a Relationship Between Two Objects"].
+
+`_refProperties` (JSON object)::
+Specifies any required properties from the relationship that should be included in the managed object. The `_refProperties` field includes a unique ID (`_id`) and the revision (`_rev`) of the object. `_refProperties` can also contain arbitrary fields to support metadata within the relationship.
+
+--
+
+
+[#establishing-relationships-between-objects]
+==== Establishing a Relationship Between Two Objects
+
+When you have defined a relationship __type__, (such as the `manager` relationship, described in the previous section), you can reference that relationship from a managed user, using the `_ref` property.
+
+For example, imagine that you are creating a new user, psmith, and that psmith's manager will be bjensen. You would add psmith's user entry, and __reference__ bjensen's entry with the `_ref` property, as follows:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "If-None-Match: *" \
+ --header "Content-Type: application/json" \
+ --request PUT \
+ --data '{
+ "sn":"Smith",
+ "userName":"psmith",
+ "givenName":"Patricia",
+ "displayName":"Patti Smith",
+ "description" : "psmith - new user",
+ "mail" : "psmith@example.com",
+ "phoneNumber" : "0831245986",
+ "password" : "Passw0rd",
+ "manager" : {"_ref" : "managed/user/bjensen"}
+ }' \
+"http://localhost:8080/openidm/managed/user/psmith"
+{
+ "_id": "psmith",
+ "_rev": "1",
+ "sn": "Smith",
+ "userName": "psmith",
+ "givenName": "Patricia",
+ "displayName": "Patti Smith",
+ "description": "psmith - new user",
+ "mail": "psmith@example.com",
+ "phoneNumber": "0831245986",
+ "accountStatus": "active",
+ "effectiveRoles": null,
+ "effectiveAssignments": [],
+ "roles": []
+}
+----
+Note that the relationship information is not returned by default in the command-line output.
+
+Any change to a relationship triggers a synchronization operation on any other managed objects that are referenced by the relationship. For example, OpenIDM maintains referential integrity by deleting the relationship reference, if the object referred to by that relationship is deleted. In our example, if bjensen's user entry is deleted, the corresponding reference in psmith's `manager` property is removed.
+
+
+[#relationships-validation]
+==== Validating Relationships Between Objects
+
+Optionally, you can specify that a relationship between two objects must be validated when the relationship is created. For example, you can indicate that a user cannot reference a role, if that role does not exist.
+
+When you create a new relationship type, validation is disabled by default as it entails a query to the relationship that can be expensive, if it is not required. To configure validation of a referenced relationship, set `"validate": true` in the object configuration (in `managed.json`). The `managed.json` files provided with OpenIDM enable validation for the following relationships:
+
+* For user objects ‒ roles, managers, and reports
+
+* For role objects ‒ members and assignments
+
+* For assignment objects ‒ roles
+
+The following configuration of the `manager` relationship enables validation, and prevents a user from referencing a manager that has not already been created:
+
+[source, javascript]
+----
+"manager" : {
+ "type" : "relationship",
+ ...
+ "validate" : true,
+----
+
+
+[#reverse-relationships]
+==== Working With Bi-Directional Relationships
+
+In some cases, it is useful to define a relationship between two objects __in both directions__. For example, a relationship between a user and his manager might indicate a __reverse relationship__ between the manager and her direct report. Reverse relationships are particularly useful in querying. For example, you might want to query jdoe's user entry to discover who his manager is, __or__ query bjensen's user entry to discover all the users who report to bjensen.
+
+A reverse relationship is declared in the managed object configuration (`conf/managed.json`). Consider the following sample excerpt of the default managed object configuration:
+
+[source, javascript]
+----
+"roles" : {
+ "description" : "",
+ "title" : "Provisioning Roles",
+ ...
+ "type" : "array",
+ "items" : {
+ "type" : "relationship",
+ "validate": false,
+ "reverseRelationship" : true,
+ "reversePropertyName" : "members",
+ ...
+----
+The `roles` property is a `relationship`. So, you can __refer__ to a managed user's roles by referencing the role definition. However, the roles property is also a reverse relationship (`"reverseRelationship" : true`) which means that you can list all users that reference that role. In other words, you can list all `members` of the role. The `members` property is therefore the `reversePropertyName`.
+
+
+[#viewing-relationships-over-rest]
+==== Viewing Relationships Over REST
+
+By default, information about relationships is not returned as the result of a GET request on a managed object. You must explicitly include the relationship property in the request, for example:
+
+[source, console]
+----
+$ curl
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/psmith?_fields=manager"
+{
+ "_id": "psmith",
+ "_rev": "1",
+ "manager": {
+ "_ref": "managed/user/bjensen",
+ "_refProperties": {
+ "_id": "e15779ad-be54-4a1c-b643-133dd9bb2e99",
+ "_rev": "1"
+ }
+ }
+}
+----
+To obtain more information about the referenced object (psmith's manager, in this case), you can include additional fields from the referenced object in the query, using the syntax `object/property` (for a simple string value) or `object/*/property` (for an array of values).
+
+The following example returns the email address and contact number for psmith's manager:
+
+[source, console]
+----
+$ curl
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/psmith?_fields=manager/mail,manager/phoneNumber"
+{
+ "_id": "psmith",
+ "_rev": "1",
+ "phoneNumber": "1234567",
+ "manager": {
+ "_ref": "managed/user/bjensen",
+ "_refProperties": {
+ "_id": "e15779ad-be54-4a1c-b643-133dd9bb2e99",
+ "_rev": "1"
+ },
+ "mail": "bjensen@example.com",
+ "phoneNumber": "1234567"
+ }
+}
+----
+You can query all the relationships associated with a managed object by querying the reference (`*_ref`) property of the object. For example, the following query shows all the objects that are referenced by psmith's entry:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/psmith?_fields=*_ref"
+{
+ "_id": "psmith",
+ "_rev": "1",
+ "roles": [],
+ "authzRoles": [
+ {
+ "_ref": "repo/internal/role/openidm-authorized",
+ "_refProperties": {
+ "_id": "8e7b2c97-dfa8-4eec-a95b-b40b710d443d",
+ "_rev": "1"
+ }
+ }
+ ],
+ "manager": {
+ "_ref": "managed/user/bjensen",
+ "_refProperties": {
+ "_id": "3a246327-a972-4576-b6a6-7126df780029",
+ "_rev": "1"
+ }
+ }
+}
+----
+
+
+[#viewing-relationships-ui]
+==== Viewing Relationships in Graph Form
+
+OpenIDM provides a relationship graph widget that gives a visual display of the relationships between objects.
+
+The relationship graph widget is not displayed on any dashboard by default. You can add it as follows:
+
+. Log into the Admin UI.
+
+. Select Dashboards, and choose the dashboard to which you want to add the widget.
++
+For more information about managing dashboards in the UI, see xref:chap-ui.adoc#ui-admin-new-dashboard["Creating and Modifying Dashboards"].
+
+. Select Add Widgets. In the Add Widgets window, scroll to the Identity Relationships widget, and click Add.
+
+. Select Close to exit the Add Widgets window.
+
+. On the dashboard, scroll down to the Identity Relationships widget. Select the vertical ellipses > Settings to configure the widget.
+
+. Choose the Widget Size, then enter the object for which you want to display relationships such as `user` and the search property for that object, such as `userName`.
++
+If you want to include an additional level of relationships in the graph, select Display sub-relationships. In a traditional organization, this option will display a user's manager, along with all users with that same manager.
+
+. Click Save.
+
+When you have configured the Identity Relationships widget, enter the user whose relationships you want to search.
+
+The following graph shows all of bjensen's relationships. The graph shows bjensen's manager (emacheke) and all other users who are direct reports of emacheke.
+
+image::images/relationships-graph.png[]
+Select or deselect the Data Types on the left of the screen to control how much information is displayed.
+
+Select and move the graph for a better view. Double-click on any user in the graph to view that user's profile.
+
+
+
+[#managed-objects-scripts]
+=== Running Scripts on Managed Objects
+
+OpenIDM provides a number of __hooks__ that enable you to manipulate managed objects using scripts. These scripts can be triggered during various stages of the lifecycle of the managed object, and are defined in the managed objects configuration file (`managed.json`).
+
+The scripts can be triggered when a managed object is created (onCreate), updated (onUpdate), retrieved (onRetrieve), deleted (onDelete), validated (onValidate), or stored in the repository (onStore). A script can also be triggered when a change to a managed object triggers an implicit synchronization operation (onSync).
+
+In addition, OpenIDM supports the use of post-action scripts for managed objects, including after the creation of an object is complete (postCreate), after the update of an object is complete (postUpdate), and after the deletion of an object (postDelete).
+
+The following sample extract of a `managed.json` file runs a script to calculate the effective assignments of a managed object, whenever that object is retrieved from the repository:
+
+[source, javascript]
+----
+"effectiveAssignments" : {
+ "type" : "array",
+ "title" : "Effective Assignments",
+ "viewable" : false,
+ "returnByDefault" : true,
+ "isVirtual" : true,
+ "onRetrieve" : {
+ "type" : "text/javascript",
+ "file" : "roles/effectiveAssignments.js",
+ "effectiveRolesPropName" : "effectiveRoles"
+ },
+ "items" : {
+ "type" : "object"
+ }
+},
+----
+
+
+[#encoding-attribute-values]
+=== Encoding Attribute Values
+
+OpenIDM supports two methods of encoding attribute values for managed objects - reversible encryption and the use of salted hashing algorithms. Attribute values that might be encoded include passwords, authentication questions, credit card numbers, and social security numbers. If passwords are already encoded on the external resource, they are generally excluded from the synchronization process. For more information, see xref:chap-passwords.adoc#chap-passwords["Managing Passwords"].
+
+You configure attribute value encoding, per schema property, in the managed object configuration (in your project's `conf/managed.json` file). The following sections show how to use reversible encryption and salted hash algorithms to encode attribute values.
+
+[#encoding-encryption]
+==== Encoding Attribute Values With Reversible Encryption
+
+The following excerpt of a `managed.json` file shows a managed object configuration that encrypts and decrypts the `password` attribute using the default symmetric key:
+
+[source, javascript]
+----
+{
+ "objects" : [
+ {
+ "name" : "user",
+ ...
+ "schema" : {
+ ...
+ "properties" : {
+ ...
+ "password" : {
+ "title" : "Password",
+ ...
+ "encryption" : {
+ "key" : "openidm-sym-default"
+ },
+ "scope" : "private",
+ ...
+ }
+ ]
+}
+----
+
+[TIP]
+====
+To configure encryption of properties by using the Admin UI:
+
+. Select Configure > Managed Objects, and click on the object type whose property values you want to encrypt (for example User).
+
+. On the Properties tab, select the property whose value should be encrypted and select the Encrypt checkbox.
+
+====
+For information about encrypting attribute values from the command-line, see xref:chap-cli.adoc#cli-encrypt["Using the encrypt Subcommand"].
+
+
+[#encoding-salted-hash]
+==== Encoding Attribute Values by Using Salted Hash Algorithms
+
+To encode attribute values with salted hash algorithms, add the `secureHash` property to the attribute definition, and specify the algorithm that should be used to hash the value. OpenIDM supports the following hash algorithms:
+[none]
+* `MD5`
+* `SHA-1`
+* `SHA-256`
+* `SHA-384`
+* `SHA-512`
+The following excerpt of a `managed.json` file shows a managed object configuration that hashes the values of the `password` attribute using the `SHA-1` algorithm:
+
+[source, javascript]
+----
+{
+ "objects" : [
+ {
+ "name" : "user",
+ ...
+ "schema" : {
+ ...
+ "properties" : {
+ ...
+ "password" : {
+ "title" : "Password",
+ ...
+ "secureHash" : {
+ "algorithm" : "SHA-1"
+ },
+ "scope" : "private",
+ ...
+ }
+ ]
+}
+----
+
+[TIP]
+====
+To configure hashing of properties by using the Admin UI:
+
+. Select Configure > Managed Objects, and click on the object type whose property values you want to hash (for example User).
+
+. On the Properties tab, select the property whose value must be hashed and select the Hash checkbox.
+
+. Select the algorithm that should be used to hash the property value.
++
+OpenIDM supports the following hash algorithms:
++
+[none]
+* `MD5`
+* `SHA-1`
+* `SHA-256`
+* `SHA-384`
+* `SHA-512`
+
+====
+For information about hashing attribute values from the command-line, see xref:chap-cli.adoc#cli-secure-hash["Using the secureHash Subcommand"].
+
+
+
+[#restricting-http-access]
+=== Restricting HTTP Access to Sensitive Data
+
+You can protect specific sensitive managed data by marking the corresponding properties as `private`. Private data, whether it is encrypted or not, is not accessible over the REST interface. Properties that are marked as private are removed from an object when that object is retrieved over REST.
+
+To mark a property as private, set its `scope` to `private` in the `conf/managed.json` file.
+
+The following extract of the `managed.json` file shows how HTTP access is prevented on the `password` and `securityAnswer` properties:
+
+[source, javascript]
+----
+{
+ "objects": [
+ {
+ "name": "user",
+ "schema": {
+ "id" : "http://jsonschema.net",
+ "title" : "User",
+ ...
+ "properties": {
+ ...
+ {
+ "name": "securityAnswer",
+ "encryption": {
+ "key": "openidm-sym-default"
+ },
+ "scope" : "private"
+ },
+ {
+ "name": "password",
+ "encryption": {
+ "key": "openidm-sym-default"
+ }'
+ "scope" : "private"
+ }
+ },
+ ...
+ }
+ ]
+}
+----
+
+[TIP]
+====
+To configure private properties by using the Admin UI:
+
+. Select Configure > Managed Objects, and click on the object type whose property values you want to make private (for example User).
+
+. On the Properties tab, select the property that must be private and select the Private checkbox.
+
+====
+A potential caveat with using private properties is that private properties are __removed__ if an object is updated by using an HTTP `PUT` request. A `PUT` request replaces the entire object in the repository. Because properties that are marked as private are ignored in HTTP requests, these properties are effectively removed from the object when the update is done. To work around this limitation, do not use `PUT` requests if you have configured private properties. Instead, use a `PATCH` request to update only those properties that need to be changed.
+
+For example, to update the `givenName` of user jdoe, you could run the following command:
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header "Content-Type: application/json" \
+--request POST \
+--data '[
+ {
+ "operation":"replace",
+ "field":"/givenName",
+ "value":"Jon"
+ }
+]' \
+"http://localhost:8080/openidm/managed/user?_action=patch&_queryId=for-userName&uid=jdoe"
+----
+
+[NOTE]
+====
+The filtering of private data applies only to direct HTTP read and query calls on managed objects. No automatic filtering is done for internal callers, and the data that these callers choose to expose.
+====
+
+
diff --git a/openidm-doc/src/main/asciidoc/integrators-guide/chap-workflow.adoc b/openidm-doc/src/main/asciidoc/integrators-guide/chap-workflow.adoc
new file mode 100644
index 000000000..1a6510d93
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/integrators-guide/chap-workflow.adoc
@@ -0,0 +1,1256 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-workflow]
+== Integrating Business Processes and Workflows
+
+Key to any identity management solution is the ability to provide workflow-driven provisioning activities, whether for self-service actions such as requests for entitlements, roles or resources, running sunrise or sunset processes, handling approvals with escalations, or performing maintenance.
+
+OpenIDM provides an embedded workflow and business process engine based on Activiti and the Business Process Model and Notation (BPMN) 2.0 standard.
+
+More information about Activiti and the Activiti project can be found at link:http://www.activiti.org[http://www.activiti.org, window=\_blank].
+
+This chapter describes how to configure the Activiti engine, and how to manage workflow tasks and processes over the REST interface. You can also manage workflows in the Admin UI by selecting Manage > Workflow and then selecting Tasks or Processes.
+
+For a number of samples that demonstrate workflow-driven provisioning, see xref:../samples-guide/chap-workflow-samples.adoc#chap-workflow-samples["Workflow Samples"] in the __Samples Guide__.
+
+[#about-bmpm-2-activiti]
+=== BPMN 2.0 and the Activiti Tools
+
+Business Process Model and Notation 2.0 is the result of consensus among Business Process Management (BPM) system vendors. The link:http://omg.org/[Object Management Group, window=\_blank] (OMG) has developed and maintained the link:http://www.omg.org/spec/BPMN/[BPMN, window=\_blank] standard since 2004.
+
+The first version of the BPMN specification focused only on graphical notation, and quickly became popular with the business analyst audience. BPMN 1.x defines how constructs such as human tasks, executable scripts, and automated decisions are visualized in a vendor-neutral, standard way. The second version of BPMN extends that focus to include execution semantics, and a common exchange format. Thus, BPMN 2.0 process definition models can be exchanged not only between different graphical editors, but can also be executed as is on any BPMN 2.0-compliant engine, such as the engine embedded in OpenIDM.
+
+Using BPMN 2.0, you can add artifacts describing workflow and business process behavior to OpenIDM for provisioning and other purposes. For example, you can craft the actual artifacts defining business processes and workflow in a text editor, or using a special Eclipse plugin. The Eclipse plugin provides visual design capabilities, simplifying packaging and deployment of the artifact to OpenIDM. For instructions on installing Activiti Eclipse BPMN 2.0 Designer, see the corresponding link:http://docs.alfresco.com/4.1/tasks/wf-install-activiti-designer.html[Alfresco documentation, window=\_blank].
+
+Also, read the Activiti __User Guide__ section covering link:http://www.activiti.org/userguide/#bpmnConstructs[BPMN 2.0 Constructs, window=\_blank], which describes in detail the graphical notations and XML representations for events, flows, gateways, tasks, and process constructs.
+
+With the latest version of Activiti, JavaScript tasks can be added to workflow definitions. However, OpenIDM functions cannot be called from a JavaScript task in a workflow. Therefore, you can use JavaScript for non-OpenIDM workflow tasks, but you must use the `activiti:expression` construct to call OpenIDM functions.
+
+
+[#setting-up-activiti]
+=== Setting Up Activiti Integration With OpenIDM
+
+OpenIDM embeds an Activiti Process Engine that is started in the OpenIDM OSGi container.
+
+After OpenIDM has been installed (as described in xref:../install-guide/chap-install.adoc#chap-install["Installing OpenIDM Services"] in the __Installation Guide__), start OpenIDM, and run the `scr list` command in the OSGi console to check that the workflow bundle is active.
+
+[source, console]
+----
+-> OpenIDM ready
+scr list
+ Id State Name
+...
+[ 39] [active ] org.forgerock.openidm.workflow
+...
+----
+OpenIDM reads workflow definitions from the `/path/to/openidm/workflow` directory. To test workflow integration, at least one workflow definition must exist in this directory.
+
+A sample workflow (`example.bpmn20.xml`) is provided in the `/path/to/openidm/samples/misc` directory. Copy this workflow to the `/path/to/openidm/workflow` directory to test the workflow integration.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ cp samples/misc/example.bpmn20.xml workflow/
+----
+Verify the workflow integration by using the REST API. The following REST call lists the defined workflows:
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processdefinition?_queryId=query-all-ids"
+----
+The sample workflow definition that you copied in the previous step is named `osgiProcess`. The result of the preceding REST call therefore includes output similar to the following:
+
+[source, console]
+----
+{
+...
+ "result":[
+ {
+ ...
+ "key": "osgiProcess",
+ ...
+ "name":"Osgi process",
+ ...
+ "_id":"osgiProcess:1:3",
+ ...
+ }
+ ]
+}
+----
+The `osgiProcess` workflow calls OpenIDM, queries the available workflow definitions from Activiti, then prints the list of workflow definitions to the OpenIDM logs. Invoke the `osgiProcess` workflow with the following REST call:
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{"_key":"osgiProcess"}' \
+ "https://localhost:8443/openidm/workflow/processinstance?_action=create"
+----
+The workflow prints the list of workflow definitions to the OSGi console. With the default sample, you should see something like this:
+
+[source, console]
+----
+script task using resolver: [
+ pagedResultsCookie:null,
+ remainingPagedResults:-1,
+ result:[
+ [
+ tenantId:,
+ candidateStarterGroupIdExpressions:[],
+ candidateStarterUserIdExpressions:[],
+ participantProcess:null,
+ processDiagramResourceName:null,
+ historyLevel:null,
+ hasStartFormKey:false,
+ laneSets:[],
+ version:1, _id:osgiProcess:1:3,
+ description:null,
+ name:Osgi process,
+ executionListeners:[:],
+ key:osgiProcess,
+ resourceName:OSGI-INF/activiti/example.bpmn20.xml,
+ ioSpecification:null,
+ taskDefinitions:null,
+ suspensionState:1,
+ deploymentId:1,
+ properties:[:],
+ startFormHandler:null,
+ suspended:false,
+ variables:null,
+ _rev:1,
+ revisionNext:2,
+ category:Examples,
+ eventSupport:[:],
+ graphicalNotationDefined:false
+ ]
+ ]
+]
+script task using expression resolver: [
+ pagedResultsCookie:null,
+ remainingPagedResults:-1,
+ result:[
+ [
+ tenantId:,
+ candidateStarterGroupIdExpressions:[],
+ ...
+]
+----
+
+[#configuring-activiti-engine]
+==== Configuring the Activiti Engine
+
+The OpenIDM Activiti module is configured in a file named `conf/workflow.json`. To disable workflows, do not include this file in your project's `conf/` subdirectory. In the default OpenIDM installation, the `workflow.json` file is shown here:
+
+[source, javascript]
+----
+{
+ "useDataSource" : "default",
+ "workflowDirectory" : "&{launcher.project.location}/workflow"
+}
+----
+--
+
+`useDataSource`::
+The Activiti data source is enabled by default.
+
+`workflowDirectory`::
+This directory specifies the location in which OpenIDM expects to find workflow processes. By default, OpenIDM looks for workflow process in the `workflow` folder of the current project.
+
+--
+There are several additional configuration properties for the Activiti module. A sample `workflow.json` file that includes all configurable properties, is provided in `samples/misc`. To configure an Activiti engine beyond the default configuration, edit this sample file and copy it to your project's `conf/` subdirectory.
+
+The sample `workflow.json` file contains the following configuration:
+
+[source, javascript]
+----
+{
+ "location" : "remote",
+ "engine" : {
+ "url" : "http://localhost:9090/openidm-workflow-remote-4.5.1-20",
+ "username" : "youractivitiuser",
+ "password" : "youractivitipassword"
+ },
+ "mail" : {
+ "host" : "yourserver.smtp.com",
+ "port" : 587,
+ "username" : "yourusername",
+ "password" : "yourpassword",
+ "starttls" : true
+ },
+ "history" : "audit"
+}
+----
+
+[WARNING]
+====
+Activiti remote integration is not currently supported.
+====
+These properties have the following meaning:
+
+* `location`: Specifies the remote Activiti engine; it's considered "remote" even if located on the same host as OpenIDM.
+
+* `engine`: Specifies the details of the Activiti engine.
++
+
+** `url`: Notes the URL to access the remote Activiti engine.
+
+** `username`: The user name of the account that connects to the remote Activiti engine.
+
+** `password`: The password of the account that connects to the remote Activiti engine.
+
+
+* `mail`: Specifies the details of the mail server that Activiti will use to send email notifications. By default, Activiti uses the mail server `localhost:25`. To specify a different mail server, enter the details of the mail server here.
++
+
+** `host`: The host of the mail server.
+
+** `port`: The port number of the mail server.
+
+** `username`: The user name of the account that connects to the mail server.
+
+** `password`: The password for the user specified above.
+
+** `startTLS`: Whether startTLS should be used to secure the connection.
+
+
+* `history`. Determines the history level that should be used for the Activiti engine. For more information, see link:#activiti-history-level[Configuring the Activiti History Level].
+
+
+[#activiti-history-level]
+===== Configuring the Activiti History Level
+
+The Activiti history level determines how much historical information is retained when workflows are executed. You can configure the history level by setting the `history` property in the `workflow.json` file, for example:
+
+[source]
+----
+"history" : "audit"
+----
+The following history levels can be configured:
+
+* `none`. No history archiving is done. This level results in the best performance for workflow execution, but no historical information is available.
+
+* `activity`. Archives all process instances and activity instances. No details are archived.
+
+* `audit`. This is the default level. All process instances, activity instances and submitted form properties are archived so that all user interaction through forms is traceable and can be audited.
+
+* `full`. This is the highest level of history archiving and has the greatest performance impact. This history level stores all the information that is stored for the `audit` level, as well as any process variable updates.
+
+
+
+
+[#defining-activiti-workflows]
+==== Defining Activiti Workflows
+
+The following section outlines the process to follow when you create an Activiti workflow for OpenIDM. Before you start creating workflows, you must configure the Activiti engine, as described in link:#configuring-activiti-engine[Configuring the Activiti Engine].
+
+====
+
+. Define your workflow in a text file, either using an editor, such as Activiti Eclipse BPMN 2.0 Designer, or a simple text editor.
+
+. Package the workflow definition file as a `.bar` file (Business Archive File). If you are using Eclipse to define the workflow, a `.bar` file is created when you select "Create deployment artifacts". A `.bar` file is essentially the same as a `.zip` file, but with the `.bar` extension.
+
+. Copy the `.bar` file to the `openidm/workflow` directory.
+
+. Invoke the workflow using a script (in `openidm/script/`) or directly using the REST interface. For more information, see xref:#invoking-activiti-workflows["Invoking Activiti Workflows"].
++
+You can also schedule the workflow to be invoked repeatedly, or at a future time. For more information, see xref:chap-scheduler-conf.adoc#chap-scheduler-conf["Scheduling Tasks and Events"].
+
+====
+
+
+[#invoking-activiti-workflows]
+==== Invoking Activiti Workflows
+
+You can invoke workflows and business processes from any trigger point within OpenIDM, including reacting to situations discovered during reconciliation. Workflows can be invoked from script files, using the `openidm.create()` function, or directly from the REST interface.
+
+The following sample script extract shows how to invoke a workflow from a script file:
+
+[source, javascript]
+----
+/*
+ * Calling 'myWorkflow' workflow
+ */
+
+var params = {
+ "_key": "myWorkflow"
+};
+
+openidm.create('workflow/processinstance', null, params);
+----
+The `null` in this example indicates that you do not want to specify an ID as part of the create call. For more information, see xref:appendix-scripting.adoc#function-create["openidm.create(resourceName, newResourceId, content, params, fields)"].
+
+You can invoke the same workflow from the REST interface by sending the following REST call to OpenIDM:
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{"_key":"myWorkflow"}' \
+ "https://localhost:8443/openidm/workflow/processinstance?_action=create"
+----
+There are two ways in which you can specify the workflow definition that is used when a new workflow instance is started.
+
+* `_key` specifies the `id` attribute of the workflow process definition, for example:
++
+
+[source, javascript]
+----
+
+----
++
+If there is more than one workflow definition with the same `_key` parameter, the latest deployed version of the workflow definition is invoked.
+
+* `_processDefinitionId` specifies the ID that is generated by the Activiti Process Engine when a workflow definition is deployed, for example:
++
+
+[source, javascript]
+----
+"sendNotificationProcess:1:104";
+----
++
+To obtain the `processDefinitionId`, query the available workflows, for example:
++
+
+[source, javascript]
+----
+{
+ "result": [
+ {
+ "name": "Process Start Auto Generated Task Auto Generated",
+ "_id": "ProcessSAGTAG:1:728"
+ },
+ {
+ "name": "Process Start Auto Generated Task Empty",
+ "_id": "ProcessSAGTE:1:725"
+ },
+ ...
+----
++
+If you specify a `_key` and a `_processDefinitionId`, the `_processDefinitionId` is used because it is more precise.
+
+Use the optional `_businessKey` parameter to add specific business logic information to the workflow when it is invoked. For example, the following workflow invocation assigns the workflow a business key of `"newOrder"`. This business key can later be used to query "newOrder" processes.
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{"_key":"myWorkflow", "_businessKey":"newOrder"}' \
+ "https://localhost:8443/openidm/workflow/processinstance?_action=create"
+----
+Access to workflows is based on OpenIDM roles, and is configured in your project's `conf/process-access.json` file. For more information, see xref:chap-ui.adoc#ui-managing-workflows["Managing Workflows From the Self-Service UI"].
+
+
+[#querying-activiti-workflows]
+==== Querying Activiti Workflows
+
+The Activiti implementation supports filtered queries that enable you to query the running process instances and tasks, based on specific query parameters. To perform a filtered query send a GET request to the `workflow/processinstance` context path, including the query in the URL.
+
+For example, the following query returns all process instances with the business key `"newOrder"`, as invoked in the previous example.
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processinstance?_queryId=filtered-query&processInstanceBusinessKey=newOrder"
+----
+Any Activiti properties can be queried using the same notation, for example, `processDefinitionId=managedUserApproval:1:6405`. The query syntax applies to all queries with `_queryId=filtered-query`. The following query returns all process instances that were started by the user `openidm-admin`:
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processinstance?_queryId=filtered-query&startUserId=openidm-admin"
+----
+You can also query process instances based on the value of any process instance variable, by prefixing the variable name with `var-`. For example:
+
+[source, console]
+----
+var-processvariablename=processvariablevalue
+----
+
+
+
+[#activiti-custom-templates]
+=== Using Custom Templates for Activiti Workflows
+
+The embedded Activiti engine is integrated with the default user interface. For simple workflows, you can use the standard Activiti form properties, and have the UI render the corresponding generic forms automatically. If you require a more complex form template, (including input validation, rich input field types, complex CSS, and so forth) you must define a custom form template.
+There are two ways in which you can define custom form templates for your workflows:
+
+* Create an HTML template, and refer to that template in the workflow definition.
++
+This is the recommended method of creating custom form templates. To refer to the HTML template in the workflow definition, use the `activiti:formKey` attribute, for example `activiti:formKey="nUCStartForm.xhtml"`.
++
+The HTML file must be deployed as part of the workflow definition. Create a .zip file that contains the HTML template and the workflow definition file. Rename the .zip file with a .bar extension.
++
+For a sample workflow that uses external, referenced form templates, see `samples/usecase/workflow/newUserCreate.bpmn20.xml`. The HTML templates, and the corresponding .bar file are included in that directory.
+
+* Use an embedded template within the workflow definition.
++
+This method is not ideal, because the HTML code must be escaped, and is difficult to read, edit, or maintain, as a result. Also, sections of HTML code will most likely need to be duplicated if your workflow includes multiple task stages. However, you might want to use this method if your form is small, not too complex and you do not want to bother with creating a separate HTML file and .bar deployment.
+
+
+
+[#workflows-REST]
+=== Managing Workflows Over the REST Interface
+
+In addition to the queries described previously, the following examples show the context paths that are exposed for managing workflows over the REST interface. The example output is based on the sample workflow that is provided in `openidm/samples/sample9`.
+====openidm/workflow/processdefinition
+
+
+* List the available workflow definitions:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processdefinition?_queryId=query-all-ids"
+{
+ "result" : [ {
+ "tenantId" : "",
+ "candidateStarterGroupIdExpressions" : [ ],
+ "candidateStarterUserIdExpressions" : [ ],
+ "participantProcess" : null,
+ "processDiagramResourceName" : null,
+ "historyLevel" : null,
+ "hasStartFormKey" : false,
+ "laneSets" : [ ],
+ "version" : 1,
+ "_id" : "managedUserApproval:1:3",
+ "description" : null,
+ "name" : "Managed User Approval Workflow",
+ "executionListeners" : { },
+ "key" : "managedUserApproval",
+ "resourceName" : "OSGI-INF/activiti/managedUserApproval.bpmn20.xml",
+ "ioSpecification" : null,
+ "taskDefinitions" : null,
+ "suspensionState" : 1,
+ "deploymentId" : "1",
+ "properties" : { },
+ "startFormHandler" : null,
+ "suspended" : false,
+ "variables" : null,
+ "_rev" : 1,
+ "revisionNext" : 2,
+ "category" : "Examples",
+ "eventSupport" : { },
+ "graphicalNotationDefined" : false
+ } ],
+ "resultCount" : 1,
+ "pagedResultsCookie" : null,
+ "remainingPagedResults" : -1
+}
+----
+
+* List the workflow definitions, based on certain filter criteria:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processdefinition?_queryId=filtered-query&category=Examples"
+{
+ "result": [
+ {
+ ...
+ "name": "Managed User Approval Workflow",
+ "_id": "managedUserApproval:1:3",
+ ...
+ "category" : "Examples",
+ ...
+ }
+ ]
+}
+----
+
+====openidm/workflow/processdefinition/{id}
+
+
+* Obtain detailed information for a process definition, based on the ID. You can determine the ID by querying all the available process definitions, as described in the first example in this section.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processdefinition/managedUserApproval:1:3"
+{
+ "tenantId" : "",
+ "candidateStarterGroupIdExpressions" : [ ],
+ "candidateStarterUserIdExpressions" : [ ],
+ "participantProcess" : null,
+ "processDiagramResourceName" : null,
+ "historyLevel" : null,
+ "hasStartFormKey" : false,
+ "laneSets" : [ ],
+ "version" : 1,
+ "formProperties" : [ ],
+ "_id" : "managedUserApproval:1:3",
+ "description" : null,
+ "name" : "Managed User Approval Workflow",
+ "executionListeners" : {
+ "end" : [ { } ]
+ },
+ "key" : "managedUserApproval",
+ "resourceName" : "OSGI-INF/activiti/managedUserApproval.bpmn20.xml",
+ "ioSpecification" : null,
+ "taskDefinitions" : {
+ "evaluateRequest" : {
+ "assigneeExpression" : {
+ "expressionText" : "openidm-admin"
+ },
+ "candidateGroupIdExpressions" : [ ],
+ "candidateUserIdExpressions" : [ ],
+ "categoryExpression" : null,
+ "descriptionExpression" : null,
+ "dueDateExpression" : null,
+ "key" : "evaluateRequest",
+ "nameExpression" : {
+ "expressionText" : "Evaluate request"
+ },
+ "ownerExpression" : null,
+ "priorityExpression" : null,
+ "taskFormHandler" : {
+ "deploymentId" : "1",
+ "formKey" : null,
+ "formPropertyHandlers" : [ {
+ "defaultExpression" : null,
+ "id" : "requesterName",
+ "name" : "Requester's name",
+ "readable" : true,
+ "required" : false,
+ "type" : null,
+ "variableExpression" : {
+ "expressionText" : "${sourceId}"
+ },
+ "variableName" : null,
+ "writable" : false
+ }, {
+ "defaultExpression" : null,
+ "id" : "requestApproved",
+ "name" : "Do you approve the request?",
+ "readable" : true,
+ "required" : true,
+ "type" : {
+ "name" : "enum",
+ "values" : {
+ "true" : "Yes",
+ "false" : "No"
+ }
+ },
+ "variableExpression" : null,
+ "variableName" : null,
+ "writable" : true
+ } ]
+ },
+ "taskListeners" : {
+ "assignment" : [ { } ],
+ "create" : [ { } ]
+ }
+ }
+ },
+ "suspensionState" : 1,
+ "deploymentId" : "1",
+ "properties" : {
+ "documentation" : null
+ },
+ "startFormHandler" : {
+ "deploymentId" : "1",
+ "formKey" : null,
+ "formPropertyHandlers" : [ ]
+ },
+ "suspended" : false,
+ "variables" : { },
+ "_rev" : 2,
+ "revisionNext" : 3,
+ "category" : "Examples",
+ "eventSupport" : { },
+ "graphicalNotationDefined" : false
+}
+----
+
+* Delete a workflow process definition, based on its ID. Note that you cannot delete a process definition if there are currently running instances of that process definition.
++
+OpenIDM picks up workflow definitions from the files located in the `/path/to/openidm/workflow` directory. If you delete the workflow definition (`.xml` file) from this directory, the OSGI bundle is deleted. However, deleting this file does not remove the workflow definition from the Activiti engine. You must therefore delete the definition over REST, as shown in the following example.
++
+Note that, although there is only one representation of a workflow definition in the file system, there might be several versions of the same definition in Activiti. If you want to delete redundant process definitions, delete the definition over REST, __making sure that you do not delete the latest version__.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "If-Match: *" \
+ --request DELETE \
+ "https://localhost:8443/openidm/workflow/processdefinition/managedUserApproval:1:3"
+----
++
+The delete request returns the contents of the deleted workflow definition.
+
+====openidm/workflow/processinstance
+
+
+* Start a workflow process instance. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "Content-Type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --data '{"_key":"managedUserApproval"}' \
+ --request POST \
+ "https://localhost:8443/openidm/workflow/processinstance?_action=create"
+{
+ "_id" : "4",
+ "processInstanceId" : "4",
+ "status" : "suspended",
+ "businessKey" : null,
+ "processDefinitionId" : "managedUserApproval:1:3"
+}
+----
+
+* Obtain the list of running workflows (process instances). The query returns a list of IDs. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processinstance?_queryId=query-all-ids"
+
+{
+ "result" : [ {
+ "tenantId" : "",
+ "businessKey" : null,
+ "queryVariables" : null,
+ "durationInMillis" : null,
+ "processVariables" : { },
+ "endTime" : null,
+ "superProcessInstanceId" : null,
+ "startActivityId" : "start",
+ "startTime" : "2014-04-25T09:54:30.035+02:00",
+ "startUserId" : "openidm-admin",
+ "_id" : "4",
+ "endActivityId" : null,
+ "processInstanceId" : "4",
+ "processDefinitionId" : "managedUserApproval:1:3",
+ "deleteReason" : null
+ } ],
+ "resultCount" : 1,
+ "pagedResultsCookie" : null,
+ "remainingPagedResults" : -1
+}
+----
+
+* Obtain the list of running workflows based on specific filter criteria.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processinstance?_queryId=filtered-query&businessKey=myBusinessKey"
+----
+
+====openidm/workflow/processinstance/{id}
+
+
+* Obtain the details of the specified process instance. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processinstance/4"
+{
+ "tenantId" : "",
+ "businessKey" : null,
+ "queryVariables" : null,
+ "durationInMillis" : null,
+ "processVariables" : { },
+ "endTime" : null,
+ "superProcessInstanceId" : null,
+ "startActivityId" : "start",
+ "startTime" : "2014-05-12T20:56:25.415+02:00",
+ "startUserId" : "openidm-admin",
+ "_id" : "4",
+ "endActivityId" : null,
+ "processInstanceId" : "4",
+ "processDefinitionId" : "managedUserApproval:1:3",
+ "deleteReason" : null
+}
+----
+
+* Stop the specified process instance. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "https://localhost:8443/openidm/workflow/processinstance/4"
+{
+ "deleteReason": null,
+ "processDefinitionId": "managedUserApproval:1:3",
+ "processInstanceId": "4",
+ "endActivityId": null,
+ "_id": "4",
+ "startUserId": "openidm-admin",
+ "startTime": "2014-06-18T10:33:40.955+02:00",
+ "tenantId": "",
+ "businessKey": null,
+ "queryVariables": null,
+ "durationInMillis": null,
+ "processVariables": {},
+ "endTime": null,
+ "superProcessInstanceId": null,
+ "startActivityId": "start"
+}
+----
++
+The delete request returns the contents of the deleted process instance.
+
+====openidm/workflow/processinstance/history
+
+
+* List the running and completed workflows (process instances).
++
+The following query returns two process instances - one that has completed (`"endActivityId": "end"`) and one that is still running (`"endActivityId": null`):
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processinstance/history?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "12",
+ "businessKey": null,
+ "deleteReason": null,
+ "durationInMillis": 465287,
+ "endActivityId": "end",
+ "endTime": "2015-07-28T14:43:53.374+02:00",
+ "processDefinitionId": "newUserCreate:1:11",
+ "processInstanceId": "12",
+ "processVariables": {},
+ "queryVariables": null,
+ "startActivityId": "start",
+ "startTime": "2015-07-28T14:36:08.087+02:00",
+ "startUserId": "user.1",
+ "superProcessInstanceId": null,
+ "tenantId": "",
+ "processDefinitionResourceName": "User onboarding process"
+ },
+ {
+ "_id": "65",
+ "businessKey": null,
+ "deleteReason": null,
+ "durationInMillis": null,
+ "endActivityId": null,
+ "endTime": null,
+ "processDefinitionId": "newUserCreate:1:11",
+ "processInstanceId": "65",
+ "processVariables": {},
+ "queryVariables": null,
+ "startActivityId": "start",
+ "startTime": "2015-07-28T15:36:20.187+02:00",
+ "startUserId": "user.0",
+ "superProcessInstanceId": null,
+ "tenantId": "",
+ "processDefinitionResourceName": "User onboarding process"
+ }
+ ],
+ "resultCount": 2,
+ "pagedResultsCookie": null,
+ "remainingPagedResults": -1
+}
+----
+
+* Obtain the list of running and completed workflows, based on specific filter criteria.
++
+The following command returns the running and completed workflows that were launched by `user.0`.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processinstance/history?_queryId=filtered-query&startUserId=user.0"
+{
+ "result": [
+ {
+ "_id": "65",
+ "businessKey": null,
+ "deleteReason": null,
+ "durationInMillis": null,
+ "endActivityId": null,
+ "endTime": null,
+ "processDefinitionId": "newUserCreate:1:11",
+ "processInstanceId": "65",
+ "processVariables": {},
+ "queryVariables": null,
+ "startActivityId": "start",
+ "startTime": "2015-07-28T15:36:20.187+02:00",
+ "startUserId": "user.0",
+ "superProcessInstanceId": null,
+ "tenantId": "",
+ "processDefinitionResourceName": "User onboarding process"
+ }
+ ],
+ "resultCount": 1,
+ "pagedResultsCookie": null,
+ "remainingPagedResults": -1
+}
+----
++
+For large result sets, you can use the `_sortKeys` parameter with a `filtered-query` to order search results by one or more fields. You can prefix a `-` character to the field name to specify that results should be returned in descending order, rather than ascending order.
++
+The following query orders results according to their `startTime`. The `-` character in this case indicates that results should be sorted in reverse order, that is, with the most recent results returned first.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processinstance/history?_queryId=filtered-query&_sortKeys=-startTime"
+{
+ "result": [
+ {
+ "_id": "104",
+ "businessKey": null,
+ "deleteReason": null,
+ "durationInMillis": null,
+ "endActivityId": null,
+ "endTime": null,
+ "processDefinitionId": "newUserCreate:1:11",
+ "processInstanceId": "104",
+ "processVariables": {},
+ "queryVariables": null,
+ "startActivityId": "start",
+ "startTime": "2015-07-28T16:33:37.834+02:00",
+ "startUserId": "user.0",
+ "superProcessInstanceId": null,
+ "tenantId": "",
+ "processDefinitionResourceName": "User onboarding process"
+ },
+ {
+ "_id": "65",
+ "businessKey": null,
+ "deleteReason": null,
+ "durationInMillis": 3738013,
+ "endActivityId": "end",
+ "endTime": "2015-07-28T16:38:38.200+02:00",
+ "processDefinitionId": "newUserCreate:1:11",
+ "processInstanceId": "65",
+ "processVariables": {},
+ "queryVariables": null,
+ "startActivityId": "start",
+ "startTime": "2015-07-28T15:36:20.187+02:00",
+ "startUserId": "user.0",
+ "superProcessInstanceId": null,
+ "tenantId": "",
+ "processDefinitionResourceName": "User onboarding process"
+ },
+ {
+ "_id": "12",
+ "businessKey": null,
+ "deleteReason": null,
+ "durationInMillis": 465287,
+ "endActivityId": "end",
+ "endTime": "2015-07-28T14:43:53.374+02:00",
+ "processDefinitionId": "newUserCreate:1:11",
+ "processInstanceId": "12",
+ "processVariables": {},
+ "queryVariables": null,
+ "startActivityId": "start",
+ "startTime": "2015-07-28T14:36:08.087+02:00",
+ "startUserId": "user.1",
+ "superProcessInstanceId": null,
+ "tenantId": "",
+ "processDefinitionResourceName": "User onboarding process"
+ }
+ ],
+ "resultCount": 3,
+ "pagedResultsCookie": null,
+ "remainingPagedResults": -1
+}
+----
++
+
+[CAUTION]
+====
+The Activiti engine treats certain property values as __strings__, regardless of their actual data type. This might result in results being returned in an order that is different to what you might expect. For example, if you wanted to sort the following results by their `_id` field, `"88", "45", "101"`, you would expect them to be returned in the order `"45", "88", "101"`. Because Activiti treats IDs as strings, rather than numbers, they would be returned in the order `"101", "45", "88"`.
+====
+
+====openidm/workflow/processdefinition/{id}/taskdefinition
+
+
+* Query the list of tasks defined for a specific process definition. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processdefinition/managedUserApproval:1:3/taskdefinition?_queryId=query-all-ids"
+{
+ "result" : [ {
+ "taskCandidateGroup" : [ ],
+ "ownerExpression" : null,
+ "assignee" : {
+ "expressionText" : "openidm-admin"
+ },
+ "categoryExpression" : null,
+ "taskListeners" : {
+ "assignment" : [ { } ],
+ "create" : [ { } ]
+ },
+ "formProperties" : {
+ "deploymentId" : "1",
+ "formKey" : null,
+ "formPropertyHandlers" : [ {
+ "_id" : "requesterName",
+ "defaultExpression" : null,
+ "name" : "Requester's name",
+ "readable" : true,
+ "required" : false,
+ "type" : null,
+ "variableExpression" : {
+ "expressionText" : "${sourceId}"
+ },
+ "variableName" : null,
+ "writable" : false
+ }, {
+ "_id" : "requestApproved",
+ "defaultExpression" : null,
+ "name" : "Do you approve the request?",
+ "readable" : true,
+ "required" : true,
+ "type" : {
+ "name" : "enum",
+ "values" : {
+ "true" : "Yes",
+ "false" : "No"
+ }
+ },
+ "variableExpression" : null,
+ "variableName" : null,
+ "writable" : true
+ } ]
+ },
+ "taskCandidateUser" : [ ],
+ "formResourceKey" : null,
+ "_id" : "evaluateRequest",
+ "priority" : null,
+ "descriptionExpression" : null,
+ "name" : {
+ "expressionText" : "Evaluate request"
+ },
+ "dueDate" : null
+ } ],
+ "resultCount" : 1,
+ "pagedResultsCookie" : null,
+ "remainingPagedResults" : -1
+}
+----
+
+* Query a task definition based on the process definition ID and the task name (`taskDefinitionKey`). For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/processdefinition/managedUserApproval:1:3/taskdefinition/evaluateRequest"
+{
+ "taskCandidateGroup" : [ ],
+ "ownerExpression" : null,
+ "formProperties" : {
+ "deploymentId" : "1",
+ "formKey" : null,
+ "formPropertyHandlers" : [ {
+ "_id" : "requesterName",
+ "defaultExpression" : null,
+ "name" : "Requester's name",
+ "readable" : true,
+ "required" : false,
+ "type" : null,
+ "variableExpression" : {
+ "expressionText" : "${sourceId}"
+ },
+ "variableName" : null,
+ "writable" : false
+ }, {
+ "_id" : "requestApproved",
+ "defaultExpression" : null,
+ "name" : "Do you approve the request?",
+ "readable" : true,
+ "required" : true,
+ "type" : {
+ "name" : "enum",
+ "values" : {
+ "true" : "Yes",
+ "false" : "No"
+ }
+ },
+ "variableExpression" : null,
+ "variableName" : null,
+ "writable" : true
+ } ]
+ },
+ "taskCandidateUser" : [ ],
+ "_id" : "evaluateRequest",
+ "priority" : null,
+ "name" : {
+ "expressionText" : "Evaluate request"
+ },
+ "descriptionExpression" : null,
+ "categoryExpression" : null,
+ "assignee" : {
+ "expressionText" : "openidm-admin"
+ },
+ "taskListeners" : {
+ "assignment" : [ { } ],
+ "create" : [ { } ]
+ },
+ "dueDate" : null
+}
+----
+
+====openidm/workflow/taskinstance
+
+
+* Query all running task instances. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/taskinstance?_queryId=query-all-ids"
+{
+ "result" : [ {
+ "tenantId" : "",
+ "createTime" : "2014-05-12T21:17:10.054+02:00",
+ "executionId" : "10",
+ "delegationStateString" : null,
+ "processVariables" : { },
+ "_id" : "15",
+ "processInstanceId" : "10",
+ "description" : null,
+ "priority" : 50,
+ "name" : "Evaluate request",
+ "dueDate" : null,
+ "parentTaskId" : null,
+ "processDefinitionId" : "managedUserApproval:1:3",
+ "taskLocalVariables" : { },
+ "suspensionState" : 1,
+ "assignee" : "openidm-admin",
+ "cachedElContext" : null,
+ "queryVariables" : null,
+ "activityInstanceVariables" : { },
+ "deleted" : false,
+ "suspended" : false,
+ "_rev" : 1,
+ "revisionNext" : 2,
+ "category" : null,
+ "taskDefinitionKey" : "evaluateRequest",
+ "owner" : null,
+ "eventName" : null,
+ "delegationState" : null
+ } ],
+ "resultCount" : 1,
+ "pagedResultsCookie" : null,
+ "remainingPagedResults" : -1
+}
+----
+
+* Query task instances based on candidate users or candidate groups. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/taskinstance?_queryId=filtered-query&taskCandidateUser=manager1"
+----
++
+or
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/taskinstance?_queryId=filtered-query&taskCandidateGroup=management"
+----
++
+Note that you can include both users and groups in the same query.
+
+====openidm/workflow/taskinstance/{id}
+
+
+* Obtain detailed information for a running task, based on the task ID. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/workflow/taskinstance/15"
+{
+ "dueDate": null,
+ "processDefinitionId": "managedUserApproval:1:3",
+ "owner": null,
+ "taskDefinitionKey": "evaluateRequest",
+ "name": "Evaluate request",
+...
+----
+
+* Update task-related data stored in the Activiti workflow engine. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "Content-Type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "If-Match : *" \
+ --request PUT \
+ --data '{"description":"Evaluate the new managed user request"}' \
+ "https://localhost:8443/openidm/workflow/taskinstance/15"
+----
+
+* Complete the specified task. The variables required by the task are provided in the request body. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "Content-Type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{"requestApproved":"true"}' \
+ "https://localhost:8443/openidm/workflow/taskinstance/15?_action=complete"
+----
+
+* Claim the specified task. A user who claims a task has that task inserted into his list of pending tasks. The ID of the user who claims the task is provided in the request body. For example:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "Content-Type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{"userId":"manager1"}' \
+ "https://localhost:8443/openidm/workflow/taskinstance/15?_action=claim"
+----
+
+
+
diff --git a/openidm-doc/src/main/asciidoc/integrators-guide/index.adoc b/openidm-doc/src/main/asciidoc/integrators-guide/index.adoc
new file mode 100644
index 000000000..279e22a80
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/integrators-guide/index.adoc
@@ -0,0 +1,69 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+= Integrator's Guide
+:doctype: book
+:toc:
+:authors: Anders Askåsen, Paul Bryan, Mark Craig, Andi Egloff, Laszlo Hordos, Matthias Tristl, Lana Frost, Mike Jang, Daly Chikhaoui
+:copyright: Copyright 2011-2017 ForgeRock AS.
+:copyright: Portions Copyright 2024 3A Systems LLC.
+
+:imagesdir: ../
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+[abstract]
+Guide to configuring and integrating OpenIDM into identity management solutions. OpenIDM identity management software offers flexible, open source services for automating management of the identity life cycle.
+
+include::./preface.adoc[]
+include::./chap-overview.adoc[]
+include::./chap-services.adoc[]
+include::./chap-cli.adoc[]
+include::./chap-ui.adoc[]
+include::./chap-repo.adoc[]
+include::./chap-configuration.adoc[]
+include::./chap-data.adoc[]
+include::./chap-users-groups-roles.adoc[]
+include::./chap-policies.adoc[]
+include::./chap-logs.adoc[]
+include::./chap-resource-conf.adoc[]
+include::./chap-synchronization.adoc[]
+include::./chap-scripting.adoc[]
+include::./chap-scheduler-conf.adoc[]
+include::./chap-passwords.adoc[]
+include::./chap-auth.adoc[]
+include::./chap-security.adoc[]
+include::./chap-workflow.adoc[]
+include::./chap-auditing.adoc[]
+include::./chap-cluster.adoc[]
+include::./chap-mail.adoc[]
+include::./chap-external-rest.adoc[]
+include::./chap-best-practices.adoc[]
+include::./chap-troubleshooting.adoc[]
+include::./chap-advanced.adoc[]
+include::./appendix-file-layout.adoc[]
+include::./appendix-ports-used.adoc[]
+include::./appendix-objects.adoc[]
+include::./appendix-synchronization.adoc[]
+include::./appendix-rest.adoc[]
+include::./appendix-scripting.adoc[]
+include::./appendix-router.adoc[]
+include::./appendix-jetty.adoc[]
+include::./appendix-auth-modules.adoc[]
+include::./appendix-audit.adoc[]
+include::./appendix-interface-stability.adoc[]
+include::./openidm-glossary.adoc[]
diff --git a/openidm-doc/src/main/asciidoc/integrators-guide/openidm-glossary.adoc b/openidm-doc/src/main/asciidoc/integrators-guide/openidm-glossary.adoc
new file mode 100644
index 000000000..36d1e00ec
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/integrators-guide/openidm-glossary.adoc
@@ -0,0 +1,80 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[glossary]
+[#openidm-glossary]
+== OpenIDM Glossary
+
+
+correlation query::
+A correlation query specifies an expression that matches existing entries in a source repository to one or more entries on a target repository. While a correlation query may be built with a script, it is __not__ a correlation script.
+
++
+As noted in xref:../integrators-guide/chap-synchronization.adoc#correlation["Correlating Source Objects With Existing Target Objects"], you can set up a query definition, such as`_queryId`,`_queryFilter`, or`_queryExpression`, possibly with the help of a`linkQualifier`.
+
+correlation script::
+A correlation script matches existing entries in a source repository, and returns the IDs of one or more matching entries on a target repository. While it skips the intermediate step associated with a`correlation query`, a correlation script can be relatively complex, based on the operations of the script.
+
+entitlement::
+An entitlement is a collection of attributes that can be added to a user entry via roles. As such, it is a specialized type of `assignment`. A user or device with an entitlement gets access rights to specified resources. An entitlement is a property of a managed object.
+
+JSON::
+JavaScript Object Notation, a lightweight data interchange format based on a subset of JavaScript syntax. For more information, see the link:http://www.json.org[JSON, window=\_blank] site.
+
+JWT::
+JSON Web Token. As noted in the link:http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html[JSON Web Token draft IETF Memo, window=\_blank], "JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties." For OpenIDM, the JWT is associated with the `JWT_SESSION` authentication module.
+
+managed object::
+An object that represents the identity-related data managed by OpenIDM. Managed objects are configurable, JSON-based data structures that OpenIDM stores in its pluggable repository. The default configuration of a managed object is that of a user, but you can define any kind of managed object, for example, groups or roles.
+
+mapping::
+A policy that is defined between a source object and a target object during reconciliation or synchronization. A mapping can also define a trigger for validation, customization, filtering, and transformation of source and target objects.
+
+OSGi::
+A module system and service platform for the Java programming language that implements a complete and dynamic component model. For a good introduction, see the link:https://www.osgi.org//developer/benefits-of-using-osgi[OSGi, window=\_blank] site. While OpenIDM services are designed to run in any OSGi container, currently only link:http://felix.apache.org/[Apache Felix, window=\_blank] is supported.
+
+reconciliation::
+During reconciliation, comparisons are made between managed objects and objects on source or target systems. Reconciliation can result in one or more specified actions, including, but not limited to, synchronization.
+
+resource::
+An external system, database, directory server, or other source of identity data to be managed and audited by the identity management system.
+
+[#gloss-rest]
+REST::
+Representational State Transfer. A software architecture style for exposing resources, using the technologies and protocols of the World Wide Web. REST describes how distributed data objects, or resources, can be defined and addressed.
+
+role::
+OpenIDM includes two different types of provisioning roles and authorization roles. For more information, see xref:../integrators-guide/chap-users-groups-roles.adoc#working-with-managed-roles["Working With Managed Roles"].
+
+source object::
+In the context of reconciliation, a source object is a data object on the source system, that OpenIDM scans before attempting to find a corresponding object on the target system. Depending on the defined mapping, OpenIDM then adjusts the object on the target system (target object).
+
+synchronization::
+The synchronization process creates, updates, or deletes objects on a target system, based on the defined mappings from the source system. Synchronization can be scheduled or on demand.
+
+system object::
+A pluggable representation of an object on an external system. For example, a user entry that is stored in an external LDAP directory is represented as a system object in OpenIDM for the period during which OpenIDM requires access to that entry.System objects follow the same RESTful resource-based design principles as managed objects.
+
+target object::
+In the context of reconciliation, a target object is a data object on the target system, that OpenIDM scans after locating its corresponding object on the source system. Depending on the defined mapping, OpenIDM then adjusts the target object to match the corresponding source object.
+
+
diff --git a/openidm-doc/src/main/asciidoc/integrators-guide/preface.adoc b/openidm-doc/src/main/asciidoc/integrators-guide/preface.adoc
new file mode 100644
index 000000000..c68865fad
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/integrators-guide/preface.adoc
@@ -0,0 +1,43 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[preface]
+[#preface]
+== Preface
+
+In this guide you will learn how to integrate OpenIDM as part of a complete identity management solution.
+
+[#d0e226]
+=== Who Should Use This Guide
+
+This guide is written for systems integrators building identity management solutions based on OpenIDM services. This guide describes OpenIDM, and shows you how to set up OpenIDM as part of your identity management solution.
+
+You do not need to be an OpenIDM wizard to learn something from this guide, though a background in identity management and building identity management solutions can help.
+
+
+include::../partials/sec-formatting-conventions.adoc[]
+
+include::../partials/sec-accessing-doc-online.adoc[]
+
+include::../partials/sec-joining-the-community.adoc[]
+
+include::../partials/sec-support-contact.adoc[]
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-audit-sample.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-audit-sample.adoc
new file mode 100644
index 000000000..d85df61e9
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-audit-sample.adoc
@@ -0,0 +1,352 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-audit-sample]
+== Audit Samples
+
+This chapter demonstrates two ways to configure OpenIDM to save audit data. In xref:#basic-audit-sample["Directing Audit Information To a MySQL Database"], you'll see how to direct information for OpenIDM audit events to a MySQL database server. In xref:#jms-audit-sample["Show Audit Events Published on a JMS Topic"], you'll see how to configure an audit event handler for the Java Message Service (JMS).
+
+[#basic-audit-sample]
+=== Directing Audit Information To a MySQL Database
+
+This sample demonstrates how you can use the audit features provided with OpenIDM and directs audit information to a MySQL database. The sample is closely related to xref:chap-xml-samples.adoc#more-sample-1["First OpenIDM Sample - Reconciling an XML File Resource"] but includes a ScriptedSQL implementation of the Groovy Connector Toolkit to connect to the MySQL database.
+
+To use any of the audit features that are demonstrated in this sample, copy information from files in the `samples/audit-sample` directory. This can help you to set up auditing for any other sample.
+
+[#audit-config-files]
+==== Audit Sample Configuration Files
+
+Review the configuration files used in this sample. They can help you understand the functionality of the data sets being audited.
+The key configuration files, in the `samples/audit-sample` directory, are as follows:
+
+* `conf/provisioner.openicf-scriptedsql.json` shows the configuration of the Scripted SQL implementation of the Groovy Connector. For more information, see xref:../connectors-guide/chap-groovy.adoc#chap-groovy["Groovy Connector Toolkit"] in the __Connectors Guide__.
+
+* `conf/provisioner.openicf-xml.json` shows the configuration of the xref:../connectors-guide/chap-xml.adoc#chap-xml["XML File Connector"] in the __Connectors Guide__.
+
+* `conf/audit.json` configures audit logging on the router, to a remote system, as discussed in xref:../integrators-guide/chap-auditing.adoc#configure-audit-service["Configuring the Audit Service"] in the __Integrator's Guide__.
++
+
+* `conf/sync.json` shows mappings between managed users and the data set attached through the XML File Connector.
+
+* `data/sample_audit_db.mysql` includes a schema that supports tables in the external MySQL database.
+
+* Groovy scripts in the `tools/` subdirectory supports communications between the Scripted SQL connector and the MySQL database.
+
+
+
+[#external-audit-mysql-sample]
+==== Configuration with MySQL
+
+You need to set up communications between OpenIDM and an external MySQL database server.
+
+Make sure MySQL is running. Follow the configuration instructions described in xref:../install-guide/chap-repository.adoc#repository-mysql["To Set Up OpenIDM With MySQL"] in the __Installation Guide__.
+The sample expects the following configuration for MySQL:
+
+* The database is available on the local system.
+
+* The database listens on the standard MySQL port, 3306.
+
+* You can connect to the MySQL database over the network.
+
+* OpenIDM should include the MySQL connector .jar file in the `/path/to/openidm/bundle` directory. If you need to download this file, see xref:../install-guide/chap-repository.adoc#repository-mysql["To Set Up OpenIDM With MySQL"] in the __Installation Guide__ for instructions.
+
+* MySQL serves a database called `audit` with six tables: `auditaccess`, `auditactivity`, `auditauthentication`, `auditconfig`, `auditrecon`, and `auditsync`.
+
+* For more information on the database schema, examine the following data definition language file: `openidm/samples/audit-sample/data/sample_audit_db.mysql`. Import the file into MySQL before running the sample.
++
+
+[source, console]
+----
+$ mysql -u root -p < /path/to/openidm/samples/audit-sample/data/sample_audit_db.mysql
+
+Enter password:
+$
+----
+
+Now review the format of the audit database sample, created from the `sample_audit_db.mysql` file, at the MySQL prompt. To access that prompt, run the following command:
+
+[source, console]
+----
+$ mysql -u root -p
+mysql > use audit;
+Reading table information for completion of table and column names
+You can turn off this feature to get a quicker startup with -A
+
+Database changed
+----
+You can now review the current state of each noted audit log with the following commands:
+
+[source, console]
+----
+select * from auditaccess;
+select * from auditactivity;
+select * from auditauthentication;
+select * from auditconfig;
+select * from auditrecon;
+select * from auditsync;
+----
+Unless you enable scheduled reconciliation, you will not see audit reconciliation data until you run reconciliation manually.
+
+
+[#install-sample-audit]
+==== Install the Sample
+
+Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for the audit sample.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/audit-sample
+----
+
+
+[#run-sample-audit]
+==== Running the Sample
+
+Run reconciliation over the REST interface.
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemXmlfileAccounts_managedUser&waitForCompletion=true"
+----
+You can also run this reconciliation from the Admin UI, at `\https://localhost:8443/admin`.
+
+[WARNING]
+====
+The default password for the OpenIDM administrative user, `openidm-admin`, is `openidm-admin`.
+
+To protect your deployment in production, change the default administrative password. To do so, navigate to the Self-Service UI at `\https://localhost:8443/` and click Change Password.
+====
+You can now review the results in the MySQL database, from the MySQL prompt, using the commands described earlier.
+
+If you have retained the default `"useForQueries" : true` option in the `conf/audit.json` file, you can also `GET` the same data with a REST call. For examples on how you can query audit logs, see xref:../integrators-guide/chap-auditing.adoc#querying-audit-over-rest["Querying Audit Logs Over REST"] in the __Integrator's Guide__.
+
+
+[#audit-sample-more]
+==== Additional Audit Configuration
+
+You can configure a variety of audit event handlers, event topics, audit filter policies, and scripts. You can even set up auditing, by topic, in CSV files. For more information, see xref:../integrators-guide/chap-auditing.adoc#chap-auditing["Using Audit Logs"] in the __Integrator's Guide__.
+
+You can see how this works from the Admin UI. After you log in with the OpenIDM administrative account, click Configure > System Preferences > Audit.
+
+
+
+[#jms-audit-sample]
+=== Show Audit Events Published on a JMS Topic
+
+Starting with OpenIDM 4.5.0, you can configure a Java Message Service (JMS) Audit Event Handler. JMS is an API that supports Java-based peer-to-peer messages between clients. The JMS API can create, send, receive, and read messages, reliably and asynchronously. You can now set up a JMS audit event handler, which can publish messages that comply with the link:http://download.oracle.com/otndocs/jcp/7195-jms-1.1-fr-spec-oth-JSpec/[Java(TM) Message Service Specification Final Release 1.1, window=\_blank].
+
+[WARNING]
+====
+JMS topics are not related to ForgeRock audit event topics. The ForgeRock implementation of JMS topics use the link:http://docs.oracle.com/javaee/6/tutorial/doc/bncdx.html#bnced[publish/subscribe messaging domain, window=\_blank], and may direct messages to the JMS audit event handler. In contrast, ForgeRock audit event topics specify categories of events.
+====
+In this sample, we demonstrate the use of the JMS audit event handler. This sample is based on xref:chap-xml-samples.adoc#more-sample-1["First OpenIDM Sample - Reconciling an XML File Resource"]. You will set up communications between OpenIDM and an external JMS Message Broker, as well as link:http://activemq.apache.org/[Apache Active MQ, window=\_blank] as the JMS provider and message broker.
+
+[#section-jms-bundles]
+==== Adding Required Bundles for the JMS Audit Event Handler
+
+To test this sample, you'll download a total of five JAR files. The first four are OSGi Bundles:
+
+* link:https://repository.apache.org/content/repositories/releases/org/apache/activemq/activemq-client/[ActiveMQ Client, window=\_top]
+
+* The link:http://bnd.bndtools.org/[bnd, window=\_blank] JAR for working with OSGi bundles, which you can download from link:https://repo1.maven.org/maven2/biz/aQute/bnd/1.50.0/bnd-1.50.0.jar[bnd-1.50.0.jar, window=\_top].
+
+* The Apache Geronimo J2EE management bundle, `geronimo-j2ee-management_1.1_spec-1.0.1.jar`, which you can download from link:https://repo1.maven.org/maven2/org/apache/geronimo/specs/geronimo-j2ee-management_1.1_spec/1.0.1/[https://repo1.maven.org/maven2/org/apache/geronimo/specs/geronimo-j2ee-management_1.1_spec/1.0.1/, window=\_top].
+
+* The link:https://github.com/chirino/hawtbuf[hawtbuf, window=\_blank] Maven-based protocol buffer compiler JAR, which you can download from link:https://repo1.maven.org/maven2/org/fusesource/hawtbuf/hawtbuf/1.11/[hawtbuf-1.11.jar, window=\_top].
+
+* The ActiveMQ 5.13.2 binary, which you can download from link:http://activemq.apache.org/activemq-5132-release.html[http://activemq.apache.org/activemq-5132-release.html, window=\_top].
+
+
+[NOTE]
+====
+The JMS audit event handler has been tested and documented with the noted versions of the JAR files that you've just downloaded.
+====
+Make sure at least the first two JAR files, for __the Active MQ Client__ and __bnd__, are in the same directory. Navigate to that directory, and create an OSGi bundle with the following steps:
+
+====
+
+. Create a BND file named `activemq.bnd` with the following contents:
++
+
+[source, console]
+----
+version=5.13.2
+Export-Package: *;version=${version}
+Bundle-Name: ActiveMQ :: Client
+Bundle-SymbolicName: org.apache.activemq
+Bundle-Version: ${version}
+----
+
+. Run the following command to create the OSGi bundle archive file:
++
+
+[source, console]
+----
+$ java \
+-jar \
+bnd-1.50.0.jar \
+wrap \
+-properties \
+activemq.bnd \
+activemq-client-5.13.2.jar
+----
+
+. Rename the `activemq-client-5.13.2.bar` file that appears to `activemq-client-5.13.2-osgi.jar` and copy it to the `/path/to/openidm/bundle` directory.
+
+====
+Copy the other two bundle files, __Apache Geronimo__ and __hawtbuf__, to the `/path/to/openidm/bundle` directory.
+
+
+[#jms-sample-start]
+==== Starting the ActiveMQ Broker and OpenIDM
+
+With the appropriate bundles in the `/path/to/openidm/bundles` directory, you're ready to start the ActiveMQ message broker, as well as OpenIDM with the JMS Audit Sample.
+
+Navigate to the directory where you unpacked the ActiveMQ binary, possibly `/path/to/apache-activemq-5.13.0/`. If you need SSL protection for your audit data, edit the ActiveMQ configuration file, `activemq.xml`, in the `conf/` subdirectory. Find the code block associated with ``, and add the following line within that block:
+
+[source, xml]
+----
+
+----
+To start the ActiveMQ broker, navigate to the directory where you unpacked the ActiveMQ binary, and run the following command:
+
+[source, console]
+----
+$ bin/activemq start
+INFO: Loading '/path/to/apache-activemq-5.13.0/bin/env'
+INFO: Using java '/usr/bin/java'
+INFO: Starting - inspect logfiles specified in logging.properties and log4j.properties to get details
+INFO: pidfile created : '/path/to/apache-activemq-5.13.0/data/activemq.pid' (pid '22671')
+----
+Now start OpenIDM, with the sample in the `/path/to/openidm/samples/audit-jms-sample` directory:
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/audit-jms-sample
+----
+
+[NOTE]
+====
+If you see the following error in the OpenIDM console, you may have forgotten to go through the steps shown in xref:#section-jms-bundles["Adding Required Bundles for the JMS Audit Event Handler"]; you also need to start the ActiveMQ broker.
+
+[source, console]
+----
+SEVERE: Unable to create JmsAuditEventHandler 'jms': null
+----
+====
+
+
+[#jms-sample-consume]
+==== Configuring and Using a JMS Consumer Application
+
+To take advantage of the Apache ActiveMQ event broker, the JMS audit sample includes a Java consumer in the following directory: `/path/to/openidm/samples/audit-jms-sample/consumer/`
+
+Assuming you have Apache Maven installed on the local system, you can compile that sample consumer with the following commands:
+
+[source, console]
+----
+$ cd /path/to/openidm/samples/audit-jms-sample/consumer/
+$ mvn clean install
+----
+When the build process is complete, you'll see a `BUILD SUCCESS` message:
+
+[source, console]
+----
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 12.638 s
+[INFO] Finished at: 2016-04-15T15:18:31-07:00
+[INFO] Final Memory: 13M/119M
+[INFO] ------------------------------------------------------------------------
+----
+
+[NOTE]
+====
+You may see `[WARNING]` messages during the build. As long as the messages end with `BUILD SUCCESS`, you can proceed with the JMS consumer application.
+====
+You can then run the following command to output audit messages related to OpenIDM actions:
+
+[source, console]
+----
+$ mvn \
+exec:java \
+-Dexec.mainClass="SimpleConsumer" \
+-Dexec.args="tcp://localhost:61616"
+ [INFO] ------------------------------------------------------------------------
+[INFO] Building SimpleConsumer 1.0-SNAPSHOT
+[INFO] ------------------------------------------------------------------------
+[INFO]
+[INFO] --- exec-maven-plugin:1.4.0:java (default-cli) @ SimpleConsumer ---
+Connection factory=org.apache.activemq.ActiveMQConnectionFactory
+READY, listening for messages. (Press 'Enter' to exit)
+----
+If you've configured ActiveMQ on a secure port, as described in xref:#jms-sample-start["Starting the ActiveMQ Broker and OpenIDM"], you can run this alternative command:
+
+[source, console]
+----
+$ mvn \
+exec:java \
+-Dexec.mainClass="SimpleConsumer" \
+-Dexec.args="ssl://localhost:61617?daemon=true&socket.enabledCipherSuites=
+ SSL_RSA_WITH_RC4_128_SHA,SSL_DH_anon_WITH_3DES_EDE_CBC_SHA"
+----
+Try some actions on OpenIDM, either in a different console or in the Admin UI. Watch the output in the `SimpleConsumer` console. As an example, you might see output similar to the following when you are xref:chap-xml-samples.adoc#sample-running-reconciliation["Running Reconciliation"] on the data in this sample:
+
+[source, javascript]
+----
+{
+ "event": {
+ "_id": "88b3da4d-e427-4f21-881c-036d7a854ccc-2559",
+ "reconId": "88b3da4d-e427-4f21-881c-036d7a854ccc-2546",
+ "mapping": "systemXmlfileAccounts_managedUser",
+ "linkQualifier": "default",
+ "exception": null,
+ "action": "UPDATE",
+ "userId": "openidm-admin",
+ "eventName": "recon",
+ "timestamp": "2016-04-16T13:40:35.974Z",
+ "transactionId": "88b3da4d-e427-4f21-881c-036d7a854ccc-2546",
+ "message": null,
+ "situation": "CONFIRMED",
+ "sourceObjectId": "system/xmlfile/account/scarter",
+ "status": "SUCCESS",
+ "targetObjectId": "managed/user/scarter",
+ "reconciling": "source",
+ "ambiguousTargetObjectIds": "",
+ "entryType": "entry"
+ },
+ "auditTopic": "recon"
+}
+----
+
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-endpoint-sample.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-endpoint-sample.adoc
new file mode 100644
index 000000000..096283fcf
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-endpoint-sample.adoc
@@ -0,0 +1,198 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-endpoint-sample]
+== Custom Endpoint Sample
+
+This chapter describes the custom endpoint sample delivered with OpenIDM.
+
+OpenIDM supports scriptable custom endpoints that enable you to launch arbitrary scripts through an OpenIDM REST URI. For information about how custom endpoints are configured, see xref:../integrators-guide/chap-scripting.adoc#custom-endpoints["Creating Custom Endpoints to Launch Scripts"] in the __Integrator's Guide__.
+
+The sample endpoint provided in `/path/to/openidm/samples/customendpoint` illustrates the configuration of a custom endpoint, and the structure of custom endpoint scripts.
+
+The purpose of this custom endpoint is to return a list of variables available to each method used in a script. The scripts show the complete set of methods that can be used. These methods map to the standard HTTP verbs - create, read, update, delete, patch, query, and action. A sample JavaScript and Groovy script is provided.
+
+====
+To run the sample:
+
+. Copy the endpoint configuration file (`samples/customendpoint/conf/endpoint-echo.json` to your project's `conf` directory.
+
+. Copy either the JavaScript file (`samples/customendpoint/script/echo.js`) or Groovy script file (`samples/customendpoint/script/echo.groovy`) to your project's `script` directory.
+
+. Open the endpoint configuration file in a text editor:
++
+
+[source, javascript]
+----
+{
+ "file" : "echo.groovy",
+ "type" : "groovy",
+ "_file" : "echo.js",
+ "_type" : "text/javascript"
+}
+----
++
+The configuration file contains nothing more than a reference to the endpoint scripts. In this case, the JavaScript script is commented out (with an underscore before the `file` and `type` properties. If you want to use the JavaScript endpoint script, uncomment these lines and comment out the lines that correspond to the Groovy script in the same way.
++
+Endpoint configuration files can include a `context` property that specifies the route to the endpoint, for example:
++
+
+[source, javascript]
+----
+"context" : "endpoint/linkedView/*"
+----
++
+If no `context` is specified, the route to the endpoint is taken from the file name, in this case `endpoint/echo`.
+
+. Test each method in succession to return the expected request structure of that method. The following examples show the request structure of the read, create and patch methods. The configuration file has been edited to use the JavaScript file, rather than the Groovy file. The output shown in these examples has been cropped for legibility. For a description of each parameter, see xref:../integrators-guide/chap-scripting.adoc#custom-endpoint-scripts["Writing Custom Endpoint Scripts"] in the __Integrator's Guide__.
++
+The following command performs a read on the echo endpoint and returns the request structure of a read request:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/endpoint/echo
+{
+ "_id": "",
+ "method": "read",
+ "resourceName": "",
+ "parameters": {},
+ "context": {
+ ...
+ }
+}
+----
++
+The following command performs a query on the echo endpoint and returns the request structure of a query request:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/endpoint/echo?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "method": "query",
+ "resourceName": "",
+ "pagedResultsCookie": null,
+ "pagedResultsOffset": 0,
+ "pageSize": 0,
+ "queryExpression": null,
+ "queryId": "query-all-ids",
+ "queryFilter": "null",
+ "parameters": {},
+ "content": null,
+ "context": {
+ ...
+ }
+ }
+ ],
+ ...
+}
+----
++
+The following command sends a create request to the echo endpoint. No user is actually created. The endpoint script merely returns the request structure of a create request. The `content` parameter in this case provides the JSON object that was sent with the request:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --data '{
+ "userName":"steve",
+ "givenName":"Steve",
+ "sn":"Carter",
+ "telephoneNumber":"0828290289",
+ "mail":"scarter@example.com",
+ "password":"Passw0rd"
+ }' \
+ --request POST \
+ "http://localhost:8080/openidm/endpoint/echo?_action=create"
+{
+ "_id": "",
+ "method": "create",
+ "resourceName": "",
+ "newResourceId": null,
+ "parameters": {},
+ "content": {
+ "userName": "steve",
+ "givenName": "Steve",
+ "sn": "Carter",
+ "telephoneNumber": "0828290289",
+ "mail": "scarter@example.com",
+ "password": "Passw0rd"
+ },
+ "context": {
+ ...
+ }
+}
+----
++
+The following command sends a patch request to the echo endpoint.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --data '[
+ {
+ "operation":"replace",
+ "field":"/givenName",
+ "value":"Steven"
+ }
+ ]' \
+ --request PATCH \
+ "http://localhost:8080/openidm/endpoint/echo"
+{
+ "_id": "",
+ "method": "patch",
+ "resourceName": "",
+ "revision": null,
+ "patch": [
+ {
+ "operation": "replace",
+ "field": "/givenName",
+ "value": "Steven"
+ }
+ ],
+ "parameters": {},
+ "context": {
+ ...
+ }
+}
+----
+
+====
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-fullstack-sample.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-fullstack-sample.adoc
new file mode 100644
index 000000000..b551d51a4
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-fullstack-sample.adoc
@@ -0,0 +1,338 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-fullstack-sample]
+== Full Stack Sample - Using OpenIDM in the ForgeRock Identity Platform
+
+This sample demonstrates the integration of three ForgeRock products: OpenIDM, OpenDJ, and OpenAM. With this sample, you can see how you can use OpenAM for authentication, for users maintained with OpenIDM, based on a data store of users in OpenDJ.
+
+It may take some time to set up this sample. The instructions that follow describe how you set up OpenDJ with a custom data store, sync that to OpenIDM. You will also configure OpenAM to use that same instance of OpenDJ. When you finish this sample, you will know how make OpenIDM, OpenDJ, and OpenAM work together. When your setup is complete, OpenIDM will authorize, and OpenAM will protect your users.
+
+Now let us get started. In this sample, you will integrate OpenDJ as the data store for both OpenIDM and OpenAM.
+
+OpenAM requires the use of a Fully-Qualified Domain Name (FQDN). For this sample, you may set up FQDNs for OpenAM, OpenDJ, and OpenIDM on either an appropriate DNS server or the `hosts` file for each system.
+
+This sample assumes that you have assigned the following FQDNs to the OpenAM, OpenDJ, and OpenIDM systems, respectively:
+
+* `openam.example.com`
+
+* `opendj.example.com`
+
+* `openidm.example.com`
+
+This sample assumes that you set up OpenIDM in a "two-way" mapping with an instance of OpenDJ, in a fashion similar to Sample 2b or 2c. It also assumes that you configure OpenAM to use the same instance of OpenDJ as its data store.
+
+To prepare this sample, you should first prepare OpenDJ and OpenAM. You will then start and customize OpenIDM configuration files via the Admin UI, or alternatively, via a text editor.
+
+[#external-ldap-config-full-stack]
+=== External OpenDJ Configuration
+
+Configure the OpenDJ server as described in xref:chap-ldap-samples.adoc#external-ldap-config-2["LDAP Server Configuration"].
+
+You need to configure the OpenDJ server with write access. This allows you to create users from OpenIDM or OpenAM on the same LDAP server. When you configure the LDAP server, import the LDIF file associated with the Full Stack sample: (`openidm/samples/fullStack/data/Example.ldif.`)
+
+When you configure OpenAM, you need the following information to configure OpenDJ as an external data store:
+
+* Access URL and port for the LDAP server, such as opendj.example.com:1389.
+
+* LDAP Bind DN, normally `cn=Directory Manager`
+
+* LDAP Bind Password, which should match the password configured the LDAP server.
+
+* LDAP SSL/TLS, which assumes that you've configured OpenDJ to communicate over the secure LDAP port, such as 1636.
+
+
+
+[#external-fullstack-openam-config]
+=== OpenAM Server Configuration
+
+This sample assumes that you will configure OpenAM on a separate system from OpenIDM.
+
+Install OpenAM as described in link:../../../openam/13/install-guide/#configure-openam-custom[To Custom Configure OpenAM, window=\_blank].
+
+During the installation process, include the parameters described in xref:#external-ldap-config-full-stack["External OpenDJ Configuration"]. Alternatively, if you already have an operational instance of OpenAM, set up an external instance of OpenDJ as described in link:../../../openam/13/admin-guide/#realm-data-store[To Configure a Data Store, window=\_blank].
+
+
+[#install-fullstack]
+=== Install the Sample
+
+Prepare OpenIDM, as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for the Full Stack Sample.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/fullStack
+----
+
+
+[#configure-fullstack-sample]
+=== Configuring OpenIDM for the Full Stack Sample
+
+You will need to change three configuration files, related to authentication and provisioning. These files are
+
+* `fullStack/conf/authentication.json`
+
+* `fullStack/conf/ui-configuration.json`
+
+* `fullStack/conf/provisioner.openicf-ldap.json`
+
+You can make these changes on configuration files or via the Admin UI. In the following sections, you will see how to do so using both methods.
+
+[NOTE]
+====
+Before configuring the `OPENAM_SESSION` module, be sure to configure at least one regular user with the `openidm-admin` authorization role.
+
+In the Admin UI, click Manage > User > select a user > Authorization Roles > Add Authorization Roles.
+
+image::images/openidm-admin-role.png[]
+Making this change ensures that you can still access OpenIDM after you have activated the `OPENAM_SESSION` module.
+
+You'll also have to modify the session module as described in the following section: xref:../integrators-guide/chap-auth.adoc#supported-session-modules["Supported Session Module"] in the __Integrator's Guide__. Specifically, you'll need to limit token lifetime properties to 5 seconds.
+====
+
+[#configure-fullstack-ui]
+==== Configuring the OPENAM_SESSION Module in the UI
+
+Now you can configure the `OPENAM_SESSION` module. To do so, take the following steps:
+
+* Navigate to `\https://openidm.example.com:8443/admin`.
+
+* Click Configure > System Preferences > Authentication.
+
+* Scroll down to the OpenAM Session module. Click the Edit icon to view Basic Properties for that module.
++
+
+image::images/openam-auth-basic.png[]
+
+* OpenIDM does not use OpenAM for authentication until you set `Module Enabled` to true. Make sure that the `OpenAM Deployment Url` matches the FQDN of your instance of OpenAM, such as `\https://openam.example.com:8443/openam`.
++
+For detailed information on the options associated with the `OPENAM_SESSION` module, see xref:../integrators-guide/appendix-auth-modules.adoc#openam-module-details["OPENAM_SESSION Module Configuration Options"] in the __Integrator's Guide__.
+
+* Select Advanced Properties to access additional options. Typically, you should only have to modify the `OpenAM Login URL` to match the Login URL of the OpenAM server. If you are working with the OpenAM Top-Level Realm, as described in link:../../../openam/13/admin-guide/#chap-realms[Configuring Realms, window=\_blank], and the given FQDN (`openam.example.com`), with an OpenAM web archive file of `openam.war` the following URL should work in this text box: `\https://openam.example.com:8443/openam/XUI/#login`
++
+
+image::images/openam-auth-adv.png[]
+
+When configured, the Admin UI writes the properties to the `authentication.json` and `ui-configuration.json` files. To identify which property is saved in which file, see xref:../integrators-guide/appendix-auth-modules.adoc#openam-module-details["OPENAM_SESSION Module Configuration Options"] in the __Integrator's Guide__.
+
+
+[#configure-fullstack-config]
+==== Configuring the OPENAM_SESSION Module with Configuration Files
+
+To configure the `OPENAM_SESSION` module, you will need to configure two different files: `authentication.json` and `ui-configuration.json`. You can find those files in the `samples/fullStack/conf` directory.
+
+[#configure-fullstack-authjson]
+===== Changes to authentication.json
+
+Under `"authModules"`, find the `"OPENAM_SESSION"` authentication module. The default version of the `authentication.json` file includes one entry which you will probably change, to match the URL of your instance of OpenAM:
+
+[source, javascript]
+----
+"authModules" : [
+...
+ {
+ "name" : "OPENAM_SESSION",
+ "properties" : {
+ "openamDeploymentUrl" : "http://example.com:8081/openam",
+ "groupRoleMapping" : {
+ "openidm-admin" : [
+ "cn=idmAdmins,ou=Groups,dc=example,dc=com"
+ ]
+ },
+ "openamSSOTokenCookieName" : "iPlanetDirectoryPro",
+ "openamUserAttribute" : "uid",
+ "queryOnResource" : "system/ldap/account",
+ "propertyMapping" : {
+ "authenticationId" : "uid",
+ "groupMembership" : "ldapGroups"
+ },
+ "defaultUserRoles" : [
+ "openidm-authorized"
+ ],
+ "groupComparisonMethod" : "ldap",
+ "augmentSecurityContext" : {
+ "type" : "text/javascript",
+ "file" : "auth/populateAsManagedUser.js"
+ },
+ "truststoreType" : "&{openidm.truststore.type}",
+ "truststoreFile" : "&{openidm.truststore.location}",
+ "truststorePassword" : "&{openidm.truststore.password}"
+ },
+ "enabled" : false
+ }
+]
+----
+Based on a standard `openidm-admin` user and a URL for OpenAM of openam.example.com, you would change the first part of the code snippet to:
+
+[source, javascript]
+----
+"authModules" : [
+...
+ {
+ "name" : "OPENAM_SESSION",
+ "properties" : {
+ "openamDeploymentUrl" : "https://openam.example.com:8443/openam",
+ "groupRoleMapping" : {
+ "openidm-admin" : [
+ "cn=idmAdmins,ou=Groups,dc=example,dc=com"
+ ]
+ },
+----
+Remember to include the configured OpenAM webapps subdirectory, typically `/openam`, in the `"openamDeploymentUrl"`. After the Java EE container used for OpenAM starts, it unpacks a file such as `openam.war` so that you can access it on the `/openam` endpoint.
+
+The `openamDeploymentUrl` shown above assumes that you are using SSL. If you have a signed certificate, you should import that into the OpenIDM truststore file. For more information, see xref:../integrators-guide/chap-security.adoc#security-management-service["Accessing the Security Management Service"] in the __Integrator's Guide__.
+
+Look at the `enabled` property. By default the `OPENAM_SESSION` module is disabled, as shown here:
+
+[source, console]
+----
+"enabled" : false
+----
+To enable the module, change `false` to `true`.
+
+
+[#configure-fullstack-uiconfig]
+===== Changes to ui-configuration.json
+
+For the `OPENAM_SESSION` module, you may want to modify some of the properties in the following excerpt of the `ui-configuration.json` configuration file.
+
+[source, javascript]
+----
+"defaultNotificationType" : "info",
+"openamLoginUrl" : "http://example.com:8081/openam/XUI/#login/",
+"openamUseExclusively" : false,
+"openamAuthEnabled" : true,
+"openamLoginLinkText" : "Login with OpenAM"
+----
+When `openamAuthEnabled` is true, you should also change the `"openamLoginUrl"` URL to match the login URL of your instance of OpenAM. If you want users to connect, securely, to the openam.example.com FQDN, on the top-level OpenAM realm, change this property to `"https://openam.example.com:8443/openam/XUI/#/login/"`.
+
+For details on each of these properties, see xref:../integrators-guide/appendix-auth-modules.adoc#openam-module-details["OPENAM_SESSION Module Configuration Options"] in the __Integrator's Guide__.
+
+
+
+[#configure-fullstack-sample-prov]
+==== Configure Provisioning for the Full Stack Sample
+
+This section describes how you might customize the `provisioner.openicf-ldap.json` file.
+
+If you want to configure this provisioner from the Admin UI, navigate to `\https://openidm.example.com:8443/admin`, and edit the LDAP connector.
+
+image::images/openam-djconn.png[]
+Edit connector details as required. For consistency, with OpenAM requirements, change the `Host name or IP` to match the FQDN of your configured instance of OpenDJ, opendj.example.com. Be consistent with the `Port` number; if you set this port to 1389, configure OpenDJ to communicate over the same port. Configure OpenAM to use the same data store.
+
+Open the noted provisioner file from the `samples/fullStack/conf` directory. The default version of this file should look similar to the following:
+
+[source, javascript]
+----
+"configurationProperties" : {
+ "host" : "localhost",
+ "port" : 1389,
+ "ssl" : false,
+ "principal" : "cn=Directory Manager",
+ "credentials" : {
+ "$crypto" : {
+ "value" : {
+ "iv" : "XUfvN7eE471b/1MG8bF60g==",
+ "data" : "Y4M22LQehQ95MRQTJCmKdw==",
+ "cipher" : "AES/CBC/PKCS5Padding",
+ "key" : "openidm-sym-default"
+ },
+ "type" : "x-simple-encryption"
+ }
+},
+"baseContexts" : [
+ "dc=example,dc=com"
+],
+"baseContextsToSynchronize" : [
+ "dc=example,dc=com"
+],
+----
+This snippet already matches the noted base context of `"dc=example,dc=com"` with a principal of `"cn=Directory Manager"`.
+
+Make sure that the following settings are consistent with the way you have configured OpenDJ and OpenAM.
+
+Change the `"localhost"` entry to the FQDN where OpenDJ is installed. In this case, that FQDN is `opendj.example.com`. Depending on whether you want to set up communications over a regular or secure LDAP port, you might change the `"port"` number to to something like 1389 or 1636. The following excerpt illustrates the change to an LDAP connector configuration:
+
+[source, javascript]
+----
+"configurationProperties" : {
+ "host" : "opendj.example.com",
+ "port" : 1389,
+ "ssl" : false,
+ "principal" : "cn=Directory Manager",
+ "credentials" : "password",
+ "$crypto" : {
+ "value" : {
+ "iv" : "XUfvN7eE371b/1MG8bF60g==",
+ "data" : "Y4M77LQehQ95MRQTJCmKdw==",
+ "cipher" : "AES/CBC/PKCS5Padding",
+ "key" : "openidm-sym-default"
+ },
+ "type" : "x-simple-encryption"
+ }
+ },
+ "baseContexts" : [
+ "dc=example,dc=com"
+ ],
+ "baseContextsToSynchronize" : [
+ "dc=example,dc=com"
+ ],
+...
+----
+If you want to configure secure communications between OpenIDM and OpenDJ, do remember to configure OpenDJ to communicate securely.
+
+When configuring OpenDJ, you may have a self-signed certificate. You may even have a certificate from a Certificate Authority. In either case, import that OpenDJ certificate into the OpenIDM truststore. For more information, see xref:../integrators-guide/chap-security.adoc#security-management-service["Accessing the Security Management Service"] in the __Integrator's Guide__.
+
+
+
+[#run-fullstack]
+=== Run the Sample
+
+The mapping configuration file (`sync.json`) for this sample includes two mappings, `systemLdapAccounts_managedUser`, which synchronizes users from the source LDAP server with the target OpenIDM repository, and `managedUser_systemLdapAccounts`, which synchronizes changes from the OpenIDM repository to the LDAP server.
+
+You can run this part of the sample by using the `curl` command-line utility, or by using the OpenIDM Administration UI.
+
+This part of the operation is identical to that shown in the relevant part of Sample 2b, xref:chap-ldap-samples.adoc#run-sample2b["Run the Sample"].
+
+After you complete the steps described in Sample 2b, do one more thing. Navigate to the Admin UI at `\https://openidm.example.com:8443/admin`, and select Manage > User. Select a specific user, such as `bjensen`. Click Change Password and change that password.
+
+Return to the Admin UI, select Mappings, and select the managedUser_systemLdapAccounts mapping. Click Reconcile Now to propagate the password you just changed to OpenDJ. You are now ready for the next section.
+
+
+[#verify-openam]
+=== Verify the Sample on OpenAM
+
+When you reconciled data stores for this OpenIDM sample, you should see the standard users for this sample reconciled into the OpenAM Data store.
+
+In OpenAM, access the list of users. Navigate to OpenAM at `\https://openam.example.com:8443/openam`. Log in with the administrative account, which by default is `amadmin`. Navigate to Access Control > Realm > Subjects > User. You should see the same users as you see in the OpenIDM Self-Service UI.
+
+[#openam-user-list]
+image::images/fullstack-openam-users.png[]
+Log out of OpenAM.
+
+Return to OpenIDM. In the login window that appears, click Login with OpenAM.
+
+You should be redirected to to the OpenAM login screen at `\https://openam.example.com:8443/openam`.
+
+Login as user `bjensen`, with the password that you just changed in OpenIDM. If successful, you should now be logged into the __OpenIDM__ Self-Service UI screen, as user `bjensen`.
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-google-sample.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-google-sample.adoc
new file mode 100644
index 000000000..3dea43830
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-google-sample.adoc
@@ -0,0 +1,498 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-google-sample]
+== Google Sample - Connecting to Google With the Google Apps Connector
+
+OpenICF provides a Google Apps Connector that enables you to interact with Google's web applications.
+
+[NOTE]
+====
+The Google Apps Connector, and this corresponding sample, are provided only with the OpenIDM Enterprise build, available on the link:https://backstage.forgerock.com[ForgeRock Backstage, window=\_blank] site.
+====
+This sample demonstrates the creation of users and groups on an external Google system, using OpenIDM's REST interface. The sample requires that you have a Google Apps account. Obtaining a Google Apps account is described in the link:https://support.google.com/a/answer/53926?hl=en[Google documentation, window=\_blank].
+
+[#google-apps-]
+=== Before You Start
+
+To set up OpenIDM to connect to your Google Apps account, you must have a Google Apps project (or create a new project) that authorizes consent for OpenIDM.
+
+====
+
+. Log in to the Google Apps Developers Console (at https://console.developers.google.com/start) and update your project or create a new project for OpenIDM.
+
+. Enable the following APIs for your OpenIDM project:
++
+
+* Admin SDK API
+
+* Enterprise License Manager API
+
+
+. Set up an OAuth2 Client ID.
++
+The Google Apps connector uses OAuth2 to authorize the connection to the Google service. Set up an OAuth2 Client ID as follows:
++
+
+.. In the Google Apps Developers Console, select Credentials > New Credentials > OAuth Client ID.
+
+.. Click Configure Consent Screen and enter a Product Name.
++
+This is the name that will be shown for all applications registered in this project.
++
+For the purposes of this example, we use the Product Name `OpenIDM`.
++
+Click Save.
+
+.. Select Credentials > OAuth Client ID > Web application.
++
+Under Authorized redirect URIs, enter the callback URL (the URL at which your clients will access your application). The default OpenIDM callback URL is `\https://localhost:8443/admin/oauth.html`. Click Create to set up the callback URL.
++
+Click Create again to set up the client ID.
++
+This step generates an OAuth Client ID and Client, similar to the following:
++
+
+image::images/oauth-credentials.png[]
++
+Copy and paste these values into a text file as you will need them when you configure the Google Apps connector.
+
+
+====
+
+
+[#configure-google-apps-connector]
+=== Configuring the Google Apps Connector
+
+This procedure uses the OpenIDM Admin UI to set up the Google Apps connector.
+
+====
+
+. To configure the connector, start OpenIDM with the Google Apps sample configuration:
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/google-connector
+Executing ./startup.sh...
+Using OPENIDM_HOME: /path/to/openidm
+Using PROJECT_HOME: /path/to/openidm/samples/google-connector/
+Using OPENIDM_OPTS: -Xmx1024m -Xms1024m
+Using LOGGING_CONFIG: -Djava.util.logging.config.file=/path/to/openidm/conf/logging.properties
+Using boot properties at /path/to/openidm/samples/google-connector/conf/boot/boot.properties
+-> OpenIDM ready
+----
+
+. Log in to the Admin UI at the URL `\https://localhost:8443/admin` as the default administrative user (`openidm-admin`) with password `openidm-admin`.
++
+This URL reflects the host on which OpenIDM is installed and corresponds to the callback URL that you specified in the previous section. The URL must be included in the list of Authorized redirect URIs for your project.
+
+. Select Configure > Connectors and click on the Google Apps connector.
+
+. On the Details tab, set the Enabled field to True.
+
+. Enter the Oauth2 Client ID and Client Secret that you obtained in the previous section.
+
+. Click Save Connector Changes.
+
+. You are redirected to Google's Login page.
++
+When you have logged in, Google requests that you allow access from your project, in this case, OpenIDM.
++
+
+image::images/google-apps-allow.png[]
++
+Click Allow.
++
+If you click Deny here, you will need to return to the Connector Configuration > Details tab in the Admin UI and save your changes again.
++
+When you allow access, you are redirected to the Connectors page in the OpenIDM Admin UI, where the Google Apps Connector should now be Active.
++
+
+image::images/google-apps-active.png[]
+
+====
+
+
+[#running-the-google-apps-sample]
+=== Running the Google Apps Sample
+
+This procedure uses create, read, update, and delete (CRUD) operations to the Google resource, to verify that the connector is working as expected. The procedure uses a combination of REST commands, to manage objects on the Google system, and the Admin UI, to manage reconciliation from the Google system to the manage user repository.
+
+The sample configuration has one mapping __from__ the Google system __to__ the managed user repository.
+
+All of the commands shown here assume that your domain is `example.com`. Adjust the examples to manage your domain.
+
+====
+
+. Create a user entry on your Google resource, over REST.
++
+When you create resources for Google, note that the equals (`=`) character cannot be used in any attribute value.
++
+The following command creates an entry for user `Sam Carter`:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "__NAME__": "samcarter@example.com",
+ "__PASSWORD__" : "password",
+ "givenName" : "Sam",
+ "familyName": "Carter",
+ "agreedToTerms": true,
+ "changePasswordAtNextLogin" : false
+ }' \
+ "https://localhost:8443/openidm/system/google/__ACCOUNT__?_action=create"
+{
+ "_id": "103567435255251233551",
+ "_rev": "\"iwpzoDgSq9BJw-XzORg0bILYPVc/LWHPMXXG8M0cjQAPITM95Y636cM\"",
+ "orgUnitPath": "/",
+ "isAdmin": false,
+ "fullName": "Sam Carter",
+ "customerId": "C02rsqddz",
+ "relations": null,
+ "nonEditableAliases": null,
+ "suspensionReason": null,
+ "includeInGlobalAddressList": true,
+ "givenName": "Sam",
+ "addresses": null,
+ "isDelegatedAdmin": false,
+ "changePasswordAtNextLogin": false,
+ "isMailboxSetup": true,
+ "__NAME__": "samcarter@example.com",
+ "agreedToTerms": true,
+ "externalIds": null,
+ "ipWhitelisted": false,
+ "aliases": null,
+ "lastLoginTime": [
+ "1970-01-01T00:00:00.000Z"
+ ],
+ "organizations": null,
+ "suspended": false,
+ "deletionTime": null,
+ "familyName": "Carter",
+ "ims": null,
+ "creationTime": [
+ "2016-02-02T12:52:30.000Z"
+ ],
+ "thumbnailPhotoUrl": null,
+ "emails": [
+ {
+ "address": "samcarter@example.com",
+ "primary": true
+ }
+ ],
+ "phones": null
+}
+----
++
+Note the ID of the new user (`103567435255251233551` in this example). You will need this ID for the update commands in this section.
+
+. Reconcile the Google resource with the managed user repository.
++
+This step should create the new user, Sam Carter (and any other users in your Google resource) in the OpenIDM managed user repository.
++
+To run reconciliation follow these steps:
++
+
+.. In the Admin UI, select Configure > Mappings.
+
+.. Click on the sourceGoogle__ACCOUNT___managedUser mapping, and click Reconcile Now.
+
+.. Select Manage > User and verify that the user Sam Carter has been created in the repository.
+
+
+. Update Sam Carter's phone number in your Google resource by sending a PUT request with the updated data, and specifying the user `_id` in the request:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PUT \
+ --header "If-Match : *" \
+ --data '{
+ "__NAME__": "samcarter@example.com",
+ "__PASSWORD__" : "password",
+ "givenName" : "Sam",
+ "familyName": "Carter",
+ "agreedToTerms": true,
+ "changePasswordAtNextLogin" : false,
+ "phones" :
+ [
+ {
+ "value": "1234567890",
+ "type": "home"
+ },
+ {
+ "value": "0987654321",
+ "type":"work"
+ }
+ ]
+ }' \
+ "https://localhost:8443/openidm/system/google/__ACCOUNT__/103567435255251233551"
+{
+ "_id": "103567435255251233551",
+ "_rev": "\"iwpzoDgSq9BJw-XzORg0bILYPVc/vfSJgHt-STUUto4lM_4ESO9izR4\"",
+...
+ "emails": [
+ {
+ "address": "samcarter@example.com",
+ "primary": true
+ }
+ ],
+ "phones": [
+ {
+ "value": "1234567890",
+ "type": "home"
+ },
+ {
+ "value": "0987654321",
+ "type": "work"
+ }
+ ]
+}
+----
+
+. Read Sam Carter's entry from your Google resource by including his `_id` in the URL:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/system/google/__ACCOUNT__/103567435255251233551"
+{
+ "_id": "103567435255251233551",
+ "__NAME__": "samcarter@example.com",
+...
+ "phones": [
+ {
+ "value": "1234567890",
+ "type": "home"
+ },
+ {
+ "value": "0987654321",
+ "type": "work"
+ }
+ ]
+}
+----
+
+. Create a group entry on your Google resource:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "__NAME__": "testGroup@example.com",
+ "__DESCRIPTION__": "Group used for google-connector sample.",
+ "name": "TestGroup"
+ }' \
+ "https://localhost:8443/openidm/system/google/__GROUP__?_action=create"
+
+{
+ "_id": "00meukdy40gpg98",
+ "_rev": "\"iwpzoDgSq9BJw-XzORg0bILYPVc/LLhHx2plMJPKeY1-h6eX_OVDi4c\"",
+ "adminCreated": true,
+ "__NAME__": "testgroup@example.com",
+ "aliases": null,
+ "nonEditableAliases": null,
+ "__DESCRIPTION__": "Group used for google-connector sample.",
+ "name": "TestGroup",
+ "directMembersCount": 0
+}
+----
+
+. Add Sam Carter to the test group you have just created. Include the `Member` endpoint, and Sam Carter's `_id` in the URL. Specify the `_id` of the group you created as the value of the `groupKey` in the JSON payload:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PUT \
+ --data '{
+ "groupKey" : "00meukdy40gpg98",
+ "role": "MEMBER",
+ "__NAME__": "samcarter@example.com",
+ "email": "samcarter@example.com",
+ "type": "MEMBER"
+ }' \
+ "https://localhost:8443/openidm/system/google/Member/103567435255251233551"
+{
+ "_id": "00meukdy40gpg98/samcarter@example.com",
+ "_rev": "\"iwpzoDgSq9BJw-XzORg0bILYPVc/CPNpkRnowkGWRvNQvUK9ev6gQ90\"",
+ "__NAME__": "00meukdy40gpg98/samcarter@example.com",
+ "role": "MEMBER",
+ "email": "samcarter@example.com",
+ "type": "USER",
+ "groupKey": "103567435255251233551"
+}
+----
+
+. Read the group entry by specifying the group `_id` in the request URL. Notice that the group has one member (`"directMembersCount": 1`).
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/system/google/__GROUP__/00meukdy40gpg98"
+
+{
+ "_id": "00meukdy40gpg98",
+ "_rev": "\"iwpzoDgSq9BJw-XzORg0bILYPVc/chUdq5m5_cycV2G4sdl7ZKAF75A\"",
+ "adminCreated": true,
+ "__NAME__": "testgroup@example.com",
+ "aliases": null,
+ "nonEditableAliases": [
+ "testGroup@example.test-google-a.com"
+ ],
+ "__DESCRIPTION__": "Group used for google-connector sample.",
+ "name": "TestGroup",
+ "directMembersCount": 1
+}
+----
+
+. Delete the group entry.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "https://localhost:8443/openidm/system/google/__GROUP__/00meukdy40gpg98"
+{
+ "_id": "00meukdy40gpg98",
+ "_rev": "\"iwpzoDgSq9BJw-XzORg0bILYPVc/chUdq5m5_cycV2G4sdl7ZKAF75A\"",
+ "adminCreated": true,
+ "__NAME__": "testgroup@example.com",
+ "aliases": null,
+ "nonEditableAliases": [
+ "testGroup@example.com.test-google-a.com"
+ ],
+ "__DESCRIPTION__": "Group used for google-connector sample.",
+ "name": "TestGroup",
+ "directMembersCount": 1
+}
+----
++
+The delete request returns the complete group object.
+
+. Delete Sam Carter, to return your Google resource to its original state.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "https://localhost:8443/openidm/system/google/__ACCOUNT__/103567435255251233551"
+{
+ "_id": "103567435255251233551",
+ "_rev": "\"iwpzoDgSq9BJw-XzORg0bILYPVc/ah6xBLujMAHieSWSisPa1CV6T3Q\"",
+ "orgUnitPath": "/",
+ "isAdmin": false,
+ "fullName": "Sam Carter",
+ "customerId": "C02rsqddz",
+ "relations": null,
+ "nonEditableAliases": [
+ "samcarter@example.com.test-google-a.com"
+ ],
+ "suspensionReason": null,
+ "includeInGlobalAddressList": true,
+ "givenName": "Sam",
+ "addresses": null,
+ "isDelegatedAdmin": false,
+ "changePasswordAtNextLogin": false,
+ "isMailboxSetup": true,
+ "__NAME__": "samcarter@example.com",
+ "agreedToTerms": true,
+ "externalIds": null,
+ "ipWhitelisted": false,
+ "aliases": null,
+ "lastLoginTime": [
+ "1970-01-01T00:00:00.000Z"
+ ],
+ "organizations": null,
+ "suspended": false,
+ "deletionTime": null,
+ "familyName": "Carter",
+ "ims": null,
+ "creationTime": [
+ "2016-02-02T12:52:30.000Z"
+ ],
+ "thumbnailPhotoUrl": null,
+ "emails": [
+ {
+ "address": "samcarter@example.com",
+ "primary": true
+ }
+ ],
+ "phones": [
+ {
+ "value": "1234567890",
+ "type": "home"
+ },
+ {
+ "value": "0987654321",
+ "type": "work"
+ }
+ ]
+}
+----
+
+====
+In this sample, you used the Google Apps connector to add and delete user and group objects in your Google application, and to reconcile users from your Google application to the OpenIDM managed user repository. You can expand on this sample by customizing the connector configuration to provide additional synchronization functionality between OpenIDM and your Google applications. For more information on configuring connectors, see xref:../integrators-guide/chap-resource-conf.adoc#chap-resource-conf["Connecting to External Resources"] in the __Integrator's Guide__.
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-groovy-samples.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-groovy-samples.adoc
new file mode 100644
index 000000000..550df458a
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-groovy-samples.adoc
@@ -0,0 +1,1491 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-groovy-samples]
+== Samples That Use the Groovy Connector Toolkit to Create Scripted Connectors
+
+OpenIDM 4.5 includes a generic Groovy Connector Toolkit that enables you to run Groovy scripts on any external resource.
+
+The Groovy Connector Toolkit is not a complete connector, in the traditional sense. Rather, it is a framework within which you must write your own Groovy scripts to address the requirements of your implementation. Specific scripts are provided within these samples, which demonstrate how the Groovy Connector Toolkit can be used. These scripts cannot be used "as is" in your deployment, but are a good starting point on which to base your customization.
+
+To facilitate creating your own scripted connectors with the Groovy Connector Toolkit, OpenIDM provides a scripted connector __bundler__. The first sample in this chapter uses the connector bundler to create a new connector, and its configuration. The connector bundler is described in detail in the link:http://openicf.forgerock.org/doc/bootstrap/dev-guide/index.html#chap-custom-bundler[OpenICF Developers Guide, window=\_blank].
+
+[#more-sample3]
+=== Sample 3 - Using the Custom Scripted Connector Bundler to Build a ScriptedSQL Connector
+
+This sample demonstrates the following OpenIDM functionality:
+
+* Custom scripted connector bundler
++
+The sample uses the custom scripted connector bundler to create a new custom connector. The connector bundler generates a scripted connector, the connector configuration and the Groovy scripts required to communicate with an external MySQL database (HRDB).
+
+* Complex data types
++
+Complex data types can be stored, retrieved and synchronized like any other object property. They are stored in the managed data as JSON objects, represented as a string, but can be mapped to external resources in any format required. You can customize the mapping to do additional work with or transformations on the complex data types.
++
+This sample defines one complex data type, `cars`, discussed in more detail later in this section.
+
+* Event hooks to perform actions
++
+The mapping from the internal repository to the external `hrdb` database (`managedUser_systemHrdb`), (defined in the `sync.json` file), includes two script hooks. The first hook is for an `onCreate` event and the second is for an `onUpdate` event. Using these event hooks, OpenIDM logs a statement to the log when a user is created or updated in the external system. In this sample, the script source is included in the mapping. However, a script can also be called from an external file. For more information on event hooks, see xref:../integrators-guide/appendix-scripting.adoc#script-places["Places to Trigger Scripts"] in the __Integrator's Guide__.
+
+* Custom scripted endpoints
++
+All scripted connectors support the configuration of custom scripted endpoints. These are configured in the provisioner configuration file and allow you to execute custom scripts over REST. This example uses a custom scripted endpoint to reset the database and populate it with data. Custom scripted endpoints are illustrated in the the custom script step< of xref:#build-custom-connector["Building the Custom ScriptedSQL Connector"].
+
+
+[CAUTION]
+====
+Because MySQL cannot "un-hash" user passwords there is no way for a reconciliation operation to retrieve and store the password from MySQL and store it in the managed user object. This issue might impact configurations that support multiple external resources in that passwords might not be synchronized immediately after reconciliation from MySQL to the managed/user repository. Users who are missing from managed/user will be created by the reconciliation but their passwords will be empty. When those users are synchronized to other external resources, they will have empty passwords in those resources. Additional scripting might be required to handle this situation, depending on the requirements of your deployment.
+====
+The Groovy scripts required for the sample are located in the `sample3/tools` directory. You will need to customize these scripts to address the requirements of your specific deployment, however, the sample scripts are a good starting point on which to base your customization.
+
+The scripted connector bundler takes a configuration file, in JSON format (`sample3/data/scriptedsql.json`). This file includes the details of the connection to the MySQL server, and the list of object types and properties that will be used in the sample. You can use this configuration file as the basis for creating your own connector with the custom scripted connector bundler.
+
+[#sample3-before-you-start]
+==== Before You Start
+
+Before you start with this sample, complete the following steps:
+
+* Prepare a fresh installation of OpenIDM. (See xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"]).
+
+* Download and install the link:https://maven.apache.org/install.html[Apache Maven, window=\_blank] build tool.
+
+* Configure an external MySQL database, as follows:
++
+
+. Download link:http://dev.mysql.com/downloads/connector/j/5.1.html[MySQL Connector/J, window=\_blank], version 5.1 or later from the MySQL website. Unpack the delivery, and copy the .jar into the `openidm/bundle` directory.
++
+
+[source, console]
+----
+$ cp mysql-connector-java-version-bin.jar /path/to/openidm/bundle/
+----
+
+. Set up MySQL to listen on localhost, port 3306. You will connect to the database as user `root` with password `password`.
++
+If want to use an existing MySQL instance that runs on a different host or port, adjust the configuration file for the sample (`sample3/data/scriptedsql.json`) before you launch the connector bundler. The default generated configuration is as follows:
++
+
+[source, javascript]
+----
+"configurationProperties" : {
+ "username" : "root",
+ "password" : "password",
+ "driverClassName" : "com.mysql.jdbc.Driver",
+ "url" : "jdbc:mysql://localhost:3306/hrdb",
+----
+
+. Create the `hrdb` database, with which OpenIDM will synchronize its managed user repository.
++
+
+[source, console]
+----
+$ mysql -u root -p
+Enter password:
+Welcome to the MySQL monitor. Commands end with ; or \g.
+Your MySQL connection id is 58
+Server version: 5.7.10
+
+Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
+
+Oracle is a registered trademark of Oracle Corporation and/or its
+affiliates. Other names may be trademarks of their respective
+owners.
+
+Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
+mysql> CREATE DATABASE hrdb CHARACTER SET utf8 COLLATE utf8_bin;
+Query OK, 1 row affected (0.00 sec)
+mysql> quit
+Bye
+----
+
+
+
+
+[#build-custom-connector]
+==== Building the Custom ScriptedSQL Connector
+
+This section uses the custom scripted connector bundler to generate the classes and configuration files required to build a new connector. The custom connector that you build in this section will be used to complete the sample.
+
+====
+
+. Create a new directory named `create-connector` in the `openidm/samples/sample3` directory and change to that new directory.
++
+
+[source, console]
+----
+$ mkdir /path/to/openidm/samples/sample3/create-connector
+$ cd /path/to/openidm/samples/sample3/create-connector
+----
+
+. Run the custom scripted connector bundler .jar, with the configuration file for this sample (`sample3/data/scriptedsql.json`).
++
+
+[source, console]
+----
+$ java -jar ../../../tools/custom-scripted-connector-bundler-4.5.1-20.jar -c ../data/scriptedsql.json
+Custom Scripted Connector Bundler for OpenIDM v4.5.1-20
+Generating connector sources for HRDB-ScriptedSQLConnector
+----
++
+This step generates a Maven project (`pom.xml` file) and a `src` directory that contains the packages to be bundled into the connector.
+
+. In addition to the generated packages, you must add the scripts required to perform operations on your resource. The scripts to access the resource illustrated in this sample are provided in the `sample3/tools` directory. Copy these scripts into the generated `resources/script/hrdb/` directory, so that they can be bundled with the connector.
++
+
+[source, console]
+----
+$ cp ../tools/* src/main/resources/script/hrdb/
+----
++
+You can customize these scripts before you bundle them, to suit the requirements of your deployment. For more information about writing Groovy scripts to interact with a resource, see the link:http://openicf.forgerock.org/doc/bootstrap/dev-guide/index.html#chap-groovy-connectors[OpenICF Developer's Guide, window=\_blank].
+
+. Use the Maven build tool to build the custom connector, with the configuration and scripts that you provided in the previous steps.
++
+To run this command, you must be in the `create-connector` directory, in which your Maven project (`pom.xml`) is located.
++
+
+[source, console]
+----
+$ mvn install
+[INFO] Scanning for projects...
+Downloading: http://maven.forgerock.org/repo/releases/org/forgerock/openicf/connectors/
+ connectors-parent/1.5.0.0/connectors-parent-1.5.0.0.pom
+Downloaded: http://maven.forgerock.org/repo/releases/org/forgerock/openicf/connectors/
+ connectors-parent/1.5.0.0/connectors-parent-1.5.0.0.pom (21 KB at 9.2 KB/sec)
+[INFO]
+[INFO] ------------------------------------------------------------------------
+[INFO] Building 1.4.1.0
+[INFO] ------------------------------------------------------------------------
+...
+[INFO] Writing OBR metadata
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 48.313 s
+[INFO] Finished at: 2015-12-10T14:03:02+02:00
+[INFO] Final Memory: 37M/320M
+[INFO] ------------------------------------------------------------------------
+----
++
+This step generates a connector .jar file (`hrdb-connector-1.4.1.0.jar`) in the `target` directory. This connector .jar will be used in the rest of this sample.
+
+. Copy the new connector .jar file to the `openidm/connectors` directory, so that it can be picked up by OpenIDM.
++
+
+[source, console]
+----
+$ cd /path/to/openim/samples/sample3
+$ cp create-connector/target/hrdb-connector-1.4.1.0.jar ../../connectors/
+----
++
+You now have a custom-built connector that includes all the required files for it to be displayed in the OpenIDM Admin UI. The bundled connector also includes the scripts and provisioner configuration that enable it to be used with OpenIDM.
+
+. Extract the connector configuration file (`provisioner.openicf-hrdb.json`) from the bundled connector into your sample's `conf` directory.
++
+
+[source, console]
+----
+$ jar -xvf ../../connectors/hrdb-connector-1.4.1.0.jar conf/provisioner.openicf-hrdb.json
+ inflated: conf/provisioner.openicf-hrdb.json
+----
+
+. The generated connector configuration file includes no system actions by default.
++
+Edit the value of the `"systemActions"` property in the connector configuration file, to call a custom script (`tools/ResetDatabaseScript.groovy`) over the REST interface. This script will reset the `hrdb` database and populate it with sample data.
++
+The edited excerpt of the `conf/provisioner.openicf-hrdb.json` file should appear as follows:
++
+
+[source, javascript]
+----
+"systemActions": [
+ {
+ "scriptId": "ResetDatabase",
+ "actions": [
+ {
+ "systemType": ".*HRDBConnector",
+ "actionType": "Groovy",
+ "actionFile": "tools\/ResetDatabaseScript.groovy"
+ }
+ ]
+ }
+],
+----
++
+Currently, only Groovy scripts are supported for these types of actions.
+
+. Finally, add the generated HTML template file to the UI extensions folder, to enable the new connector to be viewed and configured in the Admin UI.
++
+Inside the connector jar, locate the file that contains the string `1.4.html`.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ jar -tvf connectors/hrdb-connector-1.4.1.0.jar | grep "1.4.html"
+ 12775 Thu Dec 10 14:00:22 SAST 2015 ui/org.forgerock.openicf.connectors.hrdb.HRDBConnector_1.4.html
+----
++
+Create a new extension directory for the connector template.
++
+
+[source, console]
+----
+$ mkdir -p ui/admin/extension/templates/connector
+----
++
+Extract the HTML template file that you found in the preceding step and then move it into that directory
++
+
+[source, console]
+----
+$ jar -xvf connectors/hrdb-connector-1.4.1.0.jar ui/org.forgerock.openicf.connectors.hrdb.HRDBConnector_1.4.html
+inflated: ui/org.forgerock.openicf.connectors.hrdb.HRDBConnector_1.4.html
+$ mv ui/org.forgerock.openicf.connectors.hrdb.HRDBConnector_1.4.html ui/admin/extension/templates/connector
+----
+
+====
+
+
+[#run-sample3]
+==== Run the Sample
+
+
+====
+
+. Start OpenIDM with the configuration for sample 3.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample3
+Executing ./startup.sh...
+Using OPENIDM_HOME: /path/to/openidm
+Using PROJECT_HOME: /path/to/openidm/samples/sample3/
+Using OPENIDM_OPTS: -Xmx1024m -Xms1024m
+Using LOGGING_CONFIG: -Djava.util.logging.config.file=/path/to/openidm/samples/sample3//conf/logging.properties
+Using boot properties at /path/to/openidm/samples/sample3/conf/boot/boot.properties
+-> OpenIDM ready
+----
+
+. Run the custom script described in the previous section to reset the database and populate it with sample data.
++
+You can run the script again, at any point, to reset the database.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/system/hrdb?_action=script&scriptId=ResetDatabase"
+{
+ "actions": [
+ {
+ "result": "Database reset successful."
+ }
+ ]
+}
+----
++
+The `hrdb` database should now be populated with sample data.
++
+You can review the contents of the database as follows:
++
+
+[source, console]
+----
+$ mysql -u root -p
+Enter password:
+...
+mysql > use hrdb;
+Reading table information for completion of table and column names
+You can turn off this feature to get a quicker startup with -A
+
+Database changed
+mysql > select * from users;
+
++----+--------+--------------+-----------+----------+---------------+--------...
+| id | uid | password | firstname | lastname | fullname | email ...
++----+--------+------------------------------------------+-----------+-------...
+| 1 | bob | e38ad2149... | Bob | Fleming | Bob Fleming | Bob.Fle...
+| 2 | rowley | 2aa60a8ff... | Rowley | Birkin | Rowley Birkin | Rowley....
+| 3 | louis | 1119cfd37... | Louis | Balfour | Louis Balfour | Louis.B...
+| 4 | john | a1d7584da... | John | Smith | John Smith | John.Sm...
+| 5 | jdoe | edba955d0... | John | Doe | John Doe | John.Do...
++----+--------+------------------------------------------+-----------+-------...
+5 rows in set (0.00 sec)
+----
++
+
+[NOTE]
+======
+The passwords in the output shown above are hashed to the SHA-1 standard, as they cannot be read into OpenIDM as clear text. The SHA-1 Hash function is used for compatibility reasons. Use a more secure algorithm in a production database.
+======
+
+====
+
+
+[#reconcile-sample3]
+==== Reconciling the Repository
+
+
+====
+
+. The mapping configuration file (`sync.json`) for this sample includes the mapping `systemHrdb_managedUser`, which synchronizes users from the source `hrdb` database with the target OpenIDM repository.
++
+You can test this part of the sample by using the `curl` command-line utility, or the OpenIDM Administration UI.
++
+
+* To reconcile the repository by using the Administration UI:
++
+
+.. Log in to the Admin UI at the URL `\https://localhost:8443/admin` as the default administrative user (`openidm-admin`) with password `openidm-admin`.
++
+
+[WARNING]
+======
+To protect your deployment in production, change the default administrative password. To do so, select Self-Service from the dropdown list at the top right of the screen and click Change Password.
+Return to the Admin View to continue with the sample. (Select Admin View from the top right dropdown list.)
+======
+
+.. Select Configure > Mappings.
++
+The Mappings page shows two configured mappings, one from the `hrdb` database to the OpenIDM repository (`managed/user`), and one in the opposite direction.
+
+.. Click the first mapping (systemHrdb_managedUser) and click Reconcile Now.
+
+
+* To reconcile the repository by using the command-line, launch the reconciliation operation with the following command:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request POST \
+"http://localhost:8080/openidm/recon?_action=recon&mapping=systemHrdb_managedUser&waitForCompletion=true"
+{
+ "state": "SUCCESS",
+ "_id": "f3c618aa-cc3b-49ed-9a3a-00b012db2513"
+}
+----
+
++
+The reconciliation operation creates the five users from the MySQL database in the OpenIDM repository.
+
+. Retrieve the list of users from the repository.
++
+
+* To retrieve the users in the repository from the Admin UI:
++
+
+.. Select Manage > User to display the User List.
++
+The five users from the `hrdb` database have been reconciled to the OpenIDM repository.
+
+.. To retrieve the details of a specific user, click that user entry.
+
+
+* To retrieve the users from the repository by using the command-line, query the IDs in the repository as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "9d7c304a-fd89-4b58-bd6a-99b2a6a94691",
+ "_rev": "1"
+ },
+ {
+ "_id": "53479e98-5460-421c-9e81-0f3a7cc45881",
+ "_rev": "1"
+ },
+ {
+ "_id": "4103b904-c7d6-45c2-a9ca-8e563a975fa8",
+ "_rev": "1"
+ },
+ {
+ "_id": "1ea17866-aaed-4c51-b3a8-5fa8eb600e04",
+ "_rev": "1"
+ },
+ {
+ "_id": "074588a6-64f8-4cce-bb2f-33490aab90ae",
+ "_rev": "1"
+ }
+ ],
+ "resultCount": 5,
+ "pagedResultsCookie": null,
+ "totalPagedResultsPolicy": "NONE",
+ "totalPagedResults": -1,
+ "remainingPagedResults": -1
+}
+----
++
+To retrieve a complete user record, query the userName of the individual user entry. The following query returns the record for the user `Rowley Birkin`:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/?_queryId=for-userName&uid=rowley"
+{
+ "result": [
+ {
+ "_id": "53479e98-5460-421c-9e81-0f3a7cc45881",
+ "_rev": "1",
+ "mail": "Rowley.Birkin@example.com",
+ "userName": "rowley",
+ "sn": "Birkin",
+ "organization": "SALES",
+ "givenName": "Rowley",
+ "cars": [
+ {
+ "year": "2013",
+ "make": "BMW",
+ "model": "328ci"
+ },
+ {
+ "year": "2010",
+ "make": "Lexus",
+ "model": "ES300"
+ }
+ ],
+ "accountStatus": "active",
+...
+ }
+----
+
++
+Regardless of how you have retrieved Rowley Birkin's entry, note the `cars` property in this user's entry. This property demonstrates a complex object, stored in JSON format in the user entry, as a list that contains multiple objects. In the MySQL database, the `car` table joins to the `users` table through a `cars.users_id` column. The Groovy scripts read this data from MySQL and repackage it in a way that OpenIDM can understand. With support for complex objects, the data is passed through to OpenIDM as a list of `car` objects. Data is synchronized from OpenIDM to MySQL in the same way. Complex objects can also be nested to any depth.
++
+Group membership (not demonstrated here) is maintained with a traditional "join table" in MySQL (`groups_users`). OpenIDM does not maintain group membership in this way, so the Groovy scripts do the work to translate membership between the two resources.
+
+====
+
+
+[#sample3-paging]
+==== Using Paging With Sample 3
+
+All OpenICF connectors from version 1.4 onwards support the use of paging parameters to restrict query results. The following command indicates that only two records should be returned (`_pageSize=2`) and that the records should be sorted according to their `timestamp` and `_id` (`_sortKeys=timestamp,id`). Including the `timestamp` in the sort ensures that, as you page through the set, changes to records that have already been visited are not lost. Instead, those records are pushed onto the last page:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/hrdb/account?_queryFilter=uid+sw+%22%22&_pageSize=2&_sortKeys=timestamp,id"
+{
+ "result": [
+ {
+ "_id": "1",
+ "email": "Bob.Fleming@example.com",
+ "cars": [
+ {
+ "year": "1979",
+ "make": "Ford",
+ "model": "Pinto"
+ }
+ ],
+ "uid": "bob",
+ "organization": "HR",
+ "firstName": "Bob",
+ "fullName": "Bob Fleming",
+ "lastName": "Fleming"
+ },
+ {
+ "_id": "2",
+ "email": "Rowley.Birkin@example.com",
+ "cars": [
+ {
+ "year": "2013",
+ "make": "BMW",
+ "model": "328ci"
+ }
+ ],
+ "uid": "rowley",
+ "organization": "SALES",
+ "firstName": "Rowley",
+ "fullName": "Rowley Birkin",
+ "lastName": "Birkin"
+ }
+ ],
+ "resultCount": 2,
+ "pagedResultsCookie": "2015-12-10 14:16:46.0,2",
+ "totalPagedResultsPolicy": "NONE",
+ "totalPagedResults": -1,
+ "remainingPagedResults": -1
+}
+----
+The `pagedResultsCookie` is used by the server to keep track of the position in the search results. You can ignore the `"remainingPagedResults": -1` in the output. The real value of this property is not returned because the scripts that the connector uses do not do any counting of the records in the resource.
+
+Using the `pagedResultsCookie` from the previous step, run a similar query, to retrieve the following set of records in the database. Note that the value of the `pagedResultsCookie` must be URL-encoded, as shown in the following example:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/hrdb/account?_queryId=query-all-ids&_pageSize=2&_sortKeys=timestamp,id&_pagedResultsCookie=2015-12-10+14%3A16%3A46.0%2C2"
+{
+ "result": [
+ {
+ "_id": "3",
+ "uid": "louis"
+ },
+ {
+ "_id": "4",
+ "uid": "john"
+ }
+ ],
+ "resultCount": 2,
+ "pagedResultsCookie": "2015-12-10 14:16:46.0,4",
+ "totalPagedResultsPolicy": "NONE",
+ "totalPagedResults": -1,
+ "remainingPagedResults": -1
+}
+----
+For more information about paging support, see xref:../integrators-guide/chap-data.adoc#paging-query-results["Paging and Counting Query Results"] in the __Integrator's Guide__.
+
+
+
+[#sample-scripted-rest]
+=== Sample - Using the Groovy Connector Toolkit to Connect to OpenDJ With ScriptedREST
+
+This sample uses the Groovy Connector Toolkit to implement a ScriptedREST connector, which interacts with the OpenDJ REST API.
+
+The Groovy Connector Toolkit is bundled with OpenIDM 4.5, in the JAR `openidm/connectors/groovy-connector-1.4.2.1.jar`.
+
+The connector configuration file for this sample (`samples/scriptedrest2dj/conf/provisioner.openicf-scriptedrest.json`) indicates the ScriptedREST implementation of the Groovy connector as follows:
+
+[source, javascript]
+----
+{
+ "name": "scriptedrest",
+ "connectorRef": {
+ "connectorHostRef": "#LOCAL",
+ "connectorName": "org.forgerock.openicf.connectors.scriptedrest.ScriptedRESTConnector",
+ "bundleName": "org.forgerock.openicf.connectors.groovy-connector",
+ "bundleVersion": "[1.4.0.0,2.0.0.0)"
+ },
+...
+----
+The Groovy scripts required for the sample are located in the `samples/scriptedrest2dj/tools` directory. You will need to customize these scripts to address the requirements of your specific deployment, however, the sample scripts are a good starting point on which to base your customization.
+
+[IMPORTANT]
+====
+The Rest2ldap HTTP endpoint provided with OpenDJ is an evolving interface. As such, compatibility between versions is not guaranteed. This sample is designed to work with OpenDJ 3.0.0 and does not work, out of the box, with OpenDJ 3.5.0.
+====
+
+[#sample-scripted-rest-opendj]
+==== Setting Up OpenDJ
+
+This sample assumes an OpenDJ server, running on the localhost. Follow these steps to install and configure an OpenDJ instance.
+
+====
+
+. Download and extract the OpenDJ zip archive from link:https://forgerock.org/downloads/[https://forgerock.org/downloads/, window=\_blank].
+
+. Install OpenDJ using the command-line setup, as follows:
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ ./setup --cli \
+ --hostname localhost \
+ --ldapPort 1389 \
+ --rootUserDN "cn=Directory Manager" \
+ --rootUserPassword password \
+ --adminConnectorPort 4444 \
+ --addBaseEntry \
+ --baseDN dc=com \
+ --acceptLicense \
+ --no-prompt
+...
+Configuring Directory Server ..... Done.
+Creating Base Entry dc=com ..... Done.
+Starting Directory Server ....... Done.
+...
+----
++
+The sample assumes the following configuration:
++
+
+* The server is installed on the localhost.
+
+* The server listens for LDAP connections on port 1389.
+
+* The administration connector port is 4444.
+
+* The root user DN is `cn=Directory Manager`.
+
+* The root user password is `password`.
+
+
+. Configure the OpenDJ server for replication.
++
+To enable LiveSync, this server must be configured for replication, even if it does not actually participate in a replication topology. The following commands configure the server for replication.
++
+
+[source, console]
+----
+$ cd /path/to/opendj/bin
+$ ./dsconfig create-replication-server \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --provider-name "Multimaster Synchronization" \
+ --set replication-port:8989 \
+ --set replication-server-id:2 \
+ --type generic \
+ --trustAll \
+ --no-prompt
+
+$ ./dsconfig create-replication-domain \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --provider-name "Multimaster Synchronization" \
+ --domain-name example_com \
+ --set base-dn:dc=example,dc=com \
+ --set replication-server:localhost:8989 \
+ --set server-id:3 \
+ --type generic \
+ --trustAll \
+ --no-prompt
+----
+
+. Enable HTTP access to the OpenDJ directory server as follows:
++
+
+[source, console]
+----
+$ ./dsconfig set-connection-handler-prop \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --handler-name "HTTP Connection Handler" \
+ --set enabled:true \
+ --set listen-port:8090 \
+ --no-prompt \
+ --trustAll
+----
+
+. Enable the OpenDJ HTTP access log.
++
+
+[source, console]
+----
+$ ./dsconfig set-log-publisher-prop \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --publisher-name "File-Based HTTP Access Logger" \
+ --set enabled:true \
+ --no-prompt \
+ --trustAll
+----
+
+. Import the LDIF data required for the sample.
++
+
+[source, console]
+----
+$ ./ldapmodify \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --hostname localhost \
+ --port 1389 \
+ --filename /path/to/openidm/samples/scriptedrest2dj/data/ldap.ldif
+Processing ADD request for dc=example,dc=com
+ADD operation successful for DN dc=example,dc=com
+Processing ADD request for ou=Administrators,dc=example,dc=com
+ADD operation successful for DN ou=Administrators,dc=example,dc=com
+Processing ADD request for uid=idm,ou=Administrators,dc=example,dc=com
+ADD operation successful for DN uid=idm,ou=Administrators,dc=example,dc=com
+Processing ADD request for ou=People,dc=example,dc=com
+ADD operation successful for DN ou=People,dc=example,dc=com
+Processing ADD request for ou=Groups,dc=example,dc=com
+ADD operation successful for DN ou=Groups,dc=example,dc=com
+----
+
+. To configure the mapping between JSON resources and LDAP entries, copy the the configuration file for the HTTP connection handler (`scriptedrest2dj/data/http-config.json`) to OpenDJ's configuration directory.
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ cp /path/to/openidm/samples/scriptedrest2dj/data/http-config.json config/
+----
+
+. Restart OpenDJ for the configuration change to take effect.
++
+
+[source, console]
+----
+$ cd /path/to/opendj/bin
+$ ./stop-ds --restart
+Stopping Server...
+The Directory Server has started successfully
+----
+
+====
+OpenDJ is now configured for this sample.
+
+
+[#sample-scripted-rest-running]
+==== Running the Sample
+
+This section illustrates the basic CRUD operations on users and groups using the ScriptedREST connector and the OpenDJ REST API. Note that the power of the Groovy connector is in the associated Groovy scripts, and their application in your particular deployment. The scripts provided with this sample are specific to the sample and customization of the scripts is required.
+
+====
+
+. Start OpenIDM with the configuration for the ScriptedREST sample.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/scriptedrest2dj/
+----
+
+. Check the connector configuration is correct by obtaining the status of the connector, over REST.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/system/scriptedrest?_action=test"
+{
+ "name": "scriptedrest",
+ "enabled": true,
+ "config": "config/provisioner.openicf/scriptedrest",
+ "objectTypes": [
+ "__ALL__",
+ "account",
+ "group"
+ ],
+ "connectorRef": {
+ "bundleName": "org.forgerock.openicf.connectors.groovy-connector",
+ "connectorName": "org.forgerock.openicf.connectors.scriptedrest.ScriptedRESTConnector",
+ "bundleVersion": "[1.4.0.0,2.0.0.0)"
+ },
+ "displayName": "Scripted REST Connector",
+ "ok": true
+}
+----
+
+. Create a group entry on the OpenDJ server.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "_id" : "group1"
+ }' \
+ "http://localhost:8080/openidm/system/scriptedrest/group?_action=create"
+{
+ "_id": "group1",
+ "cn": "group1",
+ "members": null,
+ "lastModified": null,
+ "created": "2014-09-24T17:34:27Z",
+ "displayName": "group1"
+}
+----
+
+. Create a user entry on the OpenDJ server.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "givenName" : "Steven",
+ "familyName" : "Carter",
+ "emailAddress" : "scarter@example.com",
+ "telephoneNumber" : "444-444-4444",
+ "password" : "Passw0rd",
+ "displayName" : "Steven.Carter",
+ "uid" : "scarter"
+ }' \
+ http://localhost:8080/openidm/system/scriptedrest/account?_action=create
+{
+ "_id": "scarter",
+ "displayName": "Steven.Carter",
+ "uid": "scarter",
+ "groups": null,
+ "familyName": "Carter",
+ "emailAddress": "steven.carter@example.com",
+ "givenName": "Steven",
+ "created": "2014-09-24T17:35:46Z",
+ "telephoneNumber": "444-444-4444"
+}
+----
++
+Notice that at this stage, the user is not a member of any group.
+
+. Update Steven Carter's entry, by modifying his telephone number.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --header "If-Match: *" \
+ --request PUT \
+ --data '{
+ "givenName" : "Steven",
+ "familyName" : "Carter",
+ "emailAddress" : "scarter@example.com",
+ "telephoneNumber" : "555-555-5555",
+ "password" : "Passw0rd",
+ "displayName" : "Steven.Carter",
+ "uid" : "scarter"
+ }' \
+ http://localhost:8080/openidm/system/scriptedrest/account/scarter
+{
+ "_id": "scarter",
+ "displayName": "Steven.Carter",
+ "uid": "scarter",
+ "groups": null,
+ "familyName": "Carter",
+ "emailAddress": "steven.carter@example.com",
+ "givenName": "Steven",
+ "created": "2014-09-24T17:35:46Z",
+ "telephoneNumber": "555-555-5555"
+}
+----
+
+. Add Steven Carter to the group you created previously, by updating the group entry.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --header "If-Match: *" \
+ --request PUT \
+ --data '{
+ "_id" : "group1",
+ "members" : [{"_id" : "scarter"}]
+ }' \
+ http://localhost:8080/openidm/system/scriptedrest/group/group1
+{
+ "_id": "group1",
+ "cn": "group1",
+ "members": [
+ {
+ "displayName": "Steven.Carter",
+ "_id": "scarter"
+ }
+ ],
+ "lastModified": "2014-09-24T17:31:42Z",
+ "created": "2014-09-24T17:27:37Z",
+ "displayName": "group1"
+}
+----
+
+. Read Steven Carter's entry, to verify that he is now a member of group1.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ http://localhost:8080/openidm/system/scriptedrest/account/scarter
+{
+ "_id": "scarter",
+ "displayName": "Steven.Carter",
+ "uid": "scarter",
+ "groups": [
+ {
+ "_id": "group1"
+ }
+ ],
+ "familyName": "Carter",
+ "emailAddress": "steven.carter@example.com",
+ "givenName": "Steven",
+ "created": "2014-09-24T17:31:04Z",
+ "telephoneNumber": "555-555-5555"
+}
+----
+
+. Read the group entry to verify its members.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ http://localhost:8080/openidm/system/scriptedrest/group/group1
+{
+ "_id": "group1",
+ "cn": "group1",
+ "members": [
+ {
+ "displayName": "Steven.Carter",
+ "_id": "scarter"
+ }
+ ],
+ "lastModified": "2014-09-24T17:31:42Z",
+ "created": "2014-09-24T17:27:37Z",
+ "displayName": "group1"
+}
+----
+
+. Delete the user and group entries, returning the OpenDJ server to its initial state.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ http://localhost:8080/openidm/system/scriptedrest/account/scarter
+{
+ "_id": "scarter"
+}
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ http://localhost:8080/openidm/system/scriptedrest/group/group1
+{
+ "_id": "group1"
+}
+----
+
+====
+
+
+
+[#sample-scripted-crest]
+=== Using the Groovy Connector Toolkit to Connect to OpenDJ With ScriptedCREST
+
+This sample uses the Groovy Connector Toolkit to implement a ScriptedCREST connector, which interacts with the ForgeRock Commons REST (CREST) API to connect to an OpenDJ instance. The main difference between a CREST-based API and a generic REST API is that the CREST API is inherently recognizable by all ForgeRock products. As such, the sample can leverage CREST resources in the groovy scripts, to create CREST requests.
+
+The Groovy Connector Toolkit is bundled with OpenIDM 4.5, in the JAR `openidm/connectors/groovy-connector-1.4.2.1.jar`.
+
+The connector configuration file for this sample (`samples/scriptedcrest2dj/conf/provisioner.openicf-scriptedcrest.json`) indicates the ScriptedCREST implementation of the Groovy Connector Toolkit as follows:
+
+[source, javascript]
+----
+{
+ "name": "scriptedcrest",
+ "connectorRef": {
+ "connectorHostRef": "#LOCAL",
+ "connectorName": "org.forgerock.openicf.connectors.scriptedcrest.ScriptedCRESTConnector",
+ "bundleName": "org.forgerock.openicf.connectors.groovy-connector",
+ "bundleVersion": "[1.4.0.0,2.0.0.0)"
+ },
+...
+----
+The Groovy scripts required for the sample are located in the `samples/scriptedcrest2dj/tools` directory. You will need to customize these scripts to address the requirements of your specific deployment, however, the sample scripts are a good starting point on which to base your customization.
+
+[IMPORTANT]
+====
+The Rest2ldap HTTP endpoint provided with OpenDJ is an evolving interface. As such, compatibility between versions is not guaranteed. This sample is designed to work with OpenDJ 3.0.0 and does not work, out of the box, with OpenDJ 3.5.0.
+====
+
+[#sample-scripted-crest-opendj]
+==== Setting Up OpenDJ
+
+This sample assumes an OpenDJ server, running on the localhost. Follow these steps to install and configure an OpenDJ instance.
+
+====
+
+. Download and extract the OpenDJ zip archive from link:https://forgerock.org/downloads/[https://forgerock.org/downloads/, window=\_blank].
+
+. Install OpenDJ using the command-line setup, as follows:
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ ./setup --cli \
+ --hostname localhost \
+ --ldapPort 1389 \
+ --rootUserDN "cn=Directory Manager" \
+ --rootUserPassword password \
+ --adminConnectorPort 4444 \
+ --addBaseEntry \
+ --baseDN dc=com \
+ --acceptLicense \
+ --no-prompt
+...
+Configuring Directory Server ..... Done.
+Creating Base Entry dc=com ..... Done.
+Starting Directory Server ....... Done.
+...
+----
++
+The sample assumes the following configuration:
++
+
+* The server is installed on the localhost.
+
+* The server listens for LDAP connections on port 1389.
+
+* The administration connector port is 4444.
+
+* The root user DN is `cn=Directory Manager`.
+
+* The root user password is `password`.
+
+
+. Configure the OpenDJ server for replication.
++
+To enable liveSync, this server must be configured for replication, even if it does not actually participate in a replication topology. The following commands configure the server for replication.
++
+
+[source, console]
+----
+$ cd /path/to/opendj/bin
+$ ./dsconfig create-replication-server \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --provider-name "Multimaster Synchronization" \
+ --set replication-port:8989 \
+ --set replication-server-id:2 \
+ --type generic \
+ --trustAll \
+ --no-prompt
+
+$ ./dsconfig create-replication-domain \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --provider-name "Multimaster Synchronization" \
+ --domain-name example_com \
+ --set base-dn:dc=example,dc=com \
+ --set replication-server:localhost:8989 \
+ --set server-id:3 \
+ --type generic \
+ --trustAll \
+ --no-prompt
+----
+
+. Enable HTTP access to the OpenDJ directory server as follows:
++
+
+[source, console]
+----
+$ ./dsconfig set-connection-handler-prop \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --handler-name "HTTP Connection Handler" \
+ --set enabled:true \
+ --set listen-port:8090 \
+ --no-prompt \
+ --trustAll
+----
+
+. Enable the OpenDJ HTTP access log.
++
+
+[source, console]
+----
+$ ./dsconfig set-log-publisher-prop \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --publisher-name "File-Based HTTP Access Logger" \
+ --set enabled:true \
+ --no-prompt \
+ --trustAll
+----
+
+. Import the LDIF data required for the sample.
++
+
+[source, console]
+----
+$ ./ldapmodify \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --hostname localhost \
+ --port 1389 \
+ --filename /path/to/openidm/samples/scriptedcrest2dj/data/ldap.ldif
+Processing ADD request for dc=example,dc=com
+ADD operation successful for DN dc=example,dc=com
+Processing ADD request for ou=Administrators,dc=example,dc=com
+ADD operation successful for DN ou=Administrators,dc=example,dc=com
+Processing ADD request for uid=idm,ou=Administrators,dc=example,dc=com
+ADD operation successful for DN uid=idm,ou=Administrators,dc=example,dc=com
+Processing ADD request for ou=People,dc=example,dc=com
+ADD operation successful for DN ou=People,dc=example,dc=com
+Processing ADD request for ou=Groups,dc=example,dc=com
+ADD operation successful for DN ou=Groups,dc=example,dc=com
+----
+
+. To configure the mapping between JSON resources and LDAP entries, copy the the configuration file for the HTTP connection handler (`scriptedcrest2dj/data/http-config.json`) to OpenDJ's configuration directory.
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ cp /path/to/openidm/samples/scriptedcrest2dj/data/http-config.json config/
+----
+
+. Restart OpenDJ for the configuration change to take effect.
++
+
+[source, console]
+----
+$ cd /path/to/opendj/bin
+$ ./stop-ds --restart
+Stopping Server...
+The Directory Server has started successfully
+----
+
+====
+OpenDJ is now configured for this sample.
+
+
+[#sample-scripted-crest-running]
+==== Running the Sample
+
+This section illustrates the basic CRUD operations on users and groups using the ScriptedCREST connector implementation and the OpenDJ REST API. Note that the power of the Groovy connector is in the associated Groovy scripts, and their application in your specific deployment. The scripts provided with this sample are specific to the sample and customization of the scripts is required.
+
+====
+
+. Start OpenIDM with the configuration for the ScriptedCREST sample.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/scriptedcrest2dj/
+----
+
+. Check the connector configuration is correct by obtaining the status of the connector, over REST.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/system/scriptedcrest?_action=test"
+{
+ "ok": true,
+ "connectorRef": {
+ "bundleVersion": "[1.4.0.0,2.0.0.0)",
+ "bundleName": "org.forgerock.openicf.connectors.groovy-connector",
+ "connectorName": "org.forgerock.openicf.connectors.scriptedcrest.ScriptedCRESTConnector"
+ },
+ "objectTypes": [
+ "groups",
+ "users"
+ ],
+ "config": "config/provisioner.openicf/scriptedcrest",
+ "enabled": true,
+ "name": "scriptedcrest"
+}
+----
+
+. Create a group entry on the OpenDJ server.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "_id" : "group1"
+ }' \
+ "http://localhost:8080/openidm/system/scriptedcrest/groups?_action=create"
+{
+ "_rev": "0000000028f53bdf",
+ "_id": "group1",
+ "displayName": "group1",
+ "meta": {
+ "created": "2014-10-17T07:43:13Z"
+ }
+}
+----
+
+. Create a user entry on the OpenDJ server.
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-Type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{
+ "name": {
+ "familyName": "Carter",
+ "givenName" : "Steven"
+ },
+ "contactInformation": {
+ "emailAddress" : "scarter@example.com",
+ "telephoneNumber" : "444-444-4444"
+ },
+ "password" : "TestPassw0rd",
+ "displayName" : "Steven.Carter",
+ "_id" : "scarter"
+ }' \
+ "http://localhost:8080/openidm/system/scriptedcrest/users?_action=create"
+{
+ "_rev": "00000000d84482de",
+ "meta": {
+ "created": "2014-10-17T08:07:46Z"
+ },
+ "userName": "scarter@example.com",
+ "contactInformation": {
+ "emailAddress": "scarter@example.com",
+ "telephoneNumber": "444-444-4444"
+ },
+ "name": {
+ "givenName": "Steven",
+ "familyName": "Carter"
+ },
+ "displayName": "Steven.Carter",
+ "_id": "scarter"
+}
+----
++
+Notice that at this stage, the user is not a member of any group.
+
+. Update Steven Carter's entry, by modifying his telephone number.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --header "If-Match: *" \
+ --request PUT \
+ --data '{
+ "name": {
+ "familyName": "Carter",
+ "givenName" : "Steven"
+ },
+ "contactInformation": {
+ "emailAddress" : "scarter@example.com",
+ "telephoneNumber" : "555-555-5555"
+ },
+ "password" : "TestPassw0rd",
+ "displayName" : "Steven.Carter",
+ "_id" : "scarter"
+ }' \
+ "http://localhost:8080/openidm/system/scriptedcrest/users/scarter"
+{
+ "_rev": "00000000eb8ba31c",
+ "meta": {
+ "created": "2014-10-17T08:07:46Z",
+ "lastModified": "2014-10-17T08:25:05Z"
+ },
+ "userName": "scarter@example.com",
+ "contactInformation": {
+ "emailAddress": "scarter@example.com",
+ "telephoneNumber": "555-555-5555"
+ },
+ "name": {
+ "givenName": "Steven",
+ "familyName": "Carter"
+ },
+ "displayName": "Steven.Carter",
+ "_id": "scarter"
+}
+----
+
+. Add Steven Carter to the group you created previously, by updating the members of the group entry.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --header "If-Match: *" \
+ --request PUT \
+ --data '{
+ "_id" : "group1",
+ "members" : [{"_id" : "scarter"}]
+ }' \
+ "http://localhost:8080/openidm/system/scriptedcrest/groups/group1"
+{
+ "_rev": "0000000011ed6ea1",
+ "members": [
+ {
+ "displayName": "Steven.Carter",
+ "_id": "scarter"
+ }
+ ],
+ "_id": "group1",
+ "displayName": "group1",
+ "meta": {
+ "created": "2014-10-17T07:43:13Z",
+ "lastModified": "2014-10-17T08:26:41Z"
+ }
+}
+----
+
+. Read Steven Carter's entry, to verify that he is now a member of group1.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/scriptedcrest/users/scarter"
+{
+ "_rev": "00000000eb8ba31c",
+ "groups": [
+ {
+ "_id": "group1"
+ }
+ ],
+ "meta": {
+ "created": "2014-10-17T08:07:46Z",
+ "lastModified": "2014-10-17T08:25:05Z"
+ },
+ "userName": "scarter@example.com",
+ "contactInformation": {
+ "emailAddress": "scarter@example.com",
+ "telephoneNumber": "555-555-5555"
+ },
+ "name": {
+ "givenName": "Steven",
+ "familyName": "Carter"
+ },
+ "displayName": "Steven.Carter",
+ "_id": "scarter"
+}
+----
+
+. Read the group entry to verify its members.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/scriptedcrest/groups/group1"
+{
+ "_rev": "0000000011ed6ea1",
+ "members": [
+ {
+ "displayName": "Steven.Carter",
+ "_id": "scarter"
+ }
+ ],
+ "_id": "group1",
+ "displayName": "group1",
+ "meta": {
+ "created": "2014-10-17T07:43:13Z",
+ "lastModified": "2014-10-17T08:26:41Z"
+ }
+}
+----
+
+. Delete the user and group entries, returning the OpenDJ server to its initial state.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/system/scriptedcrest/users/scarter"
+{
+ "_id": "scarter"
+}
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/system/scriptedcrest/groups/group1"
+{
+ "_id": "group1"
+}
+----
+
+====
+
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-kerberos-sample.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-kerberos-sample.adoc
new file mode 100644
index 000000000..169de9df0
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-kerberos-sample.adoc
@@ -0,0 +1,493 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-kerberos-sample]
+== Scripted Kerberos Connector Sample
+
+New in OpenIDM 4.5.0, the scripted Kerberos connector sample demonstrates how to manage Kerberos user principals and how to reconcile user principals with OpenIDM managed user objects.
+
+The connector configuration (`/path/to/openidm/samples/kerberos/conf/provisioner.openicf-kerberos.json)`) assumes that OpenIDM is running on a host that is separate from the Kerberos host.
+
+This sample assumes that the default realm is `EXAMPLE.COM` and that there is an existing user principal `openidm/admin`. Adjust the sample to match your Kerberos realm and principals.
+
+[#edit-kerberos-connector]
+=== Editing the Kerberos Connector Configuration
+
+Before you run this sample, edit the connector configuration file to match your Kerberos environment. Specifically, set the correct values for the following properties:
+--
+
+`host`::
+The host name or IP address of the machine on which Kerberos is running.
+
+`port`::
+The SSH port on that machine.
+
++
+Default: `22` (the default SSH port)
+
+`user`::
+The username of the account that is used to connect to the SSH server.
+
+`password`::
+The password of the account that is used to connect to the SSH server.
+
+`prompt`::
+A string that represents the remote SSH session prompt. This must be the exact prompt string, in the format `username@target:`, for example `root@localhost:~$`. The easiest way to obtain this string is to `ssh` into the machine and copy paste the prompt.
+
+`customConfiguration`::
+The details of the admin user principal and the default realm.
+
++
+This example assumes an admin user principal of `openidm/admin`.
+
++
+For more information on setting this property, see xref:../connectors-guide/chap-kerberos.adoc#customConfiguration[customConfiguration] in the __Connectors Guide__.
+
+[#customSensitiveConfiguration]
+`customSensitiveConfiguration`::
+The password for the user principal.
+
++
+For more information on setting this property, see xref:../connectors-guide/chap-kerberos.adoc#customSensitiveConfiguration[customSensitiveConfiguration] in the __Connectors Guide__.
+
+--
+Your connector configuration should look something like the following:
+
+[source, javascript]
+----
+...
+ "configurationProperties" : {
+ "host" : "192.0.2.0",
+ "port" : 22,
+ "user" : "admin",
+ "password" : "Passw0rd",
+ "prompt" : "admin@myhost:~$",
+ "sudoCommand" : "/usr/bin/sudo",
+ "echoOff" : true,
+ "terminalType" : "vt102",
+ "setLocale" : false,
+ "locale" : "en_US.utf8",
+ "connectionTimeout" : 5000,
+ "expectTimeout" : 5000,
+ "authenticationType" : "PASSWORD",
+ "throwOperationTimeoutException" : true,
+ "customConfiguration" : "kadmin { cmd = '/usr/sbin/kadmin.local'; user='openidm/admin'; default_realm='EXAMPLE.COM' }",
+ "customSensitiveConfiguration" : "kadmin { password = 'Passw0rd'}",
+ ...
+----
+OpenIDM will encrypt all passwords in the configuration when it starts up, or whenever it reloads the configuration file.
+
+For information about the complete Kerberos connector configuration, see xref:../connectors-guide/chap-kerberos.adoc#ssh-kerberos-config["Configuring the Kerberos Connector"] in the __Connectors Guide__.
+
+[CAUTION]
+====
+Do not modify the value of the `scriptRoots` or `classpath` properties unless you have extracted the scripts from the connector bundle and placed them on the filesystem.
+====
+
+
+[#kerberos-sample-run]
+=== Running the Kerberos Sample
+
+The commands in this section achieve the following:
+
+. Start OpenIDM and check that the connector can reach the Kerberos server.
+
+. Create two users in the OpenIDM managed repository.
+
+. Reconcile the managed repository with the Kerberos server so that the new users are created in Kerberos.
+
+. Retrieve the details of one of the new Kerberos principals from the server.
+
+. Delete one of the managed users.
+
+. Reconcile the managed repository again and note that the corresponding Kerberos principal has been deleted.
+
+
+====
+
+. Start OpenIDM with the configuration for the Kerberos sample:
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+----
++
+
+[source, console]
+----
+$ startup.sh -p samples/kerberos
+----
+
+. Test that your connector configuration is correct and that OpenIDM can reach your Kerberos server, with the following command:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/system?_action=test"
+[
+ {
+ "name": "kerberos",
+ "enabled": true,
+ "config": "config/provisioner.openicf/kerberos",
+ "objectTypes": [
+ "__ALL__",
+ "account"
+ ],
+ "connectorRef": {
+ "bundleName": "org.forgerock.openicf.connectors.kerberos-connector",
+ "connectorName": "org.forgerock.openicf.connectors.kerberos.KerberosConnector",
+ "bundleVersion": "1.4.0.0"
+ },
+ "displayName": "Kerberos Connector",
+ "ok": true
+ }
+]
+----
++
+If the command returns `"ok": true`, as in the preceding output, your configuration is correct and you can continue with the sample.
+
+. Retrieve a list of the existing user principals in the Kerberos database:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/kerberos/account?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "K/M@EXAMPLE.COM",
+ "principal": "K/M@EXAMPLE.COM"
+ },
+ {
+ "_id": "kadmin/admin@EXAMPLE.COM",
+ "principal": "kadmin/admin@EXAMPLE.COM"
+ },
+ {
+ "_id": "kadmin/changepw@EXAMPLE.COM",
+ "principal": "kadmin/changepw@EXAMPLE.COM"
+ },
+ {
+ "_id": "kadmin/krb1.example.com@EXAMPLE.COM",
+ "principal": "kadmin/krb1.example.com@EXAMPLE.COM"
+ },
+ {
+ "_id": "kiprop/krb1.example.com@EXAMPLE.COM",
+ "principal": "kiprop/krb1.example.com@EXAMPLE.COM"
+ },
+ {
+ "_id": "krbtgt/EXAMPLE.COM@EXAMPLE.COM",
+ "principal": "krbtgt/EXAMPLE.COM@EXAMPLE.COM"
+ },
+ {
+ "_id": "openidm/admin@EXAMPLE.COM",
+ "principal": "openidm/admin@EXAMPLE.COM"
+ }
+ ],
+ ...
+}
+----
+
+. Create two new managed users, either over REST or by using the Admin UI.
++
+The following command creates users bjensen and scarter over REST. To create similar users by using the Admin UI, select Managed > User and click New User:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-type: application/json" \
+ --request POST \
+ --data '{
+ "userName": "bjensen",
+ "givenName": "Barbara",
+ "sn" : "Jensen",
+ "password" : "Passw0rd",
+ "displayName" : "Barbara Jensen",
+ "mail" : "bjensen@example.com"
+ }' \
+ "http://localhost:8080/openidm/managed/user?_action=create"
+{
+ "_id": "ce3d9b8f-1d15-4950-82c1-f87596aadcb6",
+ "_rev": "2",
+ "userName": "bjensen",
+ "givenName": "Barbara",
+ "sn": "Jensen",
+ "displayName": "Barbara Jensen",
+ "mail": "bjensen@example.com",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-type: application/json" \
+ --request POST \
+ --data '{
+ "userName": "scarter",
+ "givenName": "Steven",
+ "sn" : "Carter",
+ "password" : "Passw0rd",
+ "displayName" : "Steven Carter",
+ "mail" : "scarter@example.com"
+ }' \
+ "http://localhost:8080/openidm/managed/user?_action=create"
+{
+ "_id": "a204ca60-b0fc-42f8-bf93-65bb30131361",
+ "_rev": "2",
+ "userName": "scarter",
+ "givenName": "Steven",
+ "sn": "Carter",
+ "displayName": "Steven Carter",
+ "mail": "scarter@example.com",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
+
+. Run a reconciliation operation between the managed user repository and the Kerberos database to create the new users bjensen and scarter in Kerberos. You can run the reconciliation over REST, or using the Admin UI.
++
+The following command creates runs the reconciliation over REST:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=managedUser_systemKerberos"
+{
+ "_id": "862ab9ba-d1d9-4058-b6bc-a23a94b68776-234",
+ "state": "ACTIVE"
+}
+----
++
+To run the reconciliation by using the Admin UI, select Configure > Mappings, click on the `managedUser_systemKerberos` mapping, and click Reconcile Now.
+
+. Retrieve the list of Kerberos user principals again. You should now see bjensen and scarter in this list:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/kerberos/account?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "bjensen@EXAMPLE.COM",
+ "principal": "bjensen@EXAMPLE.COM"
+ },
+ {
+ "_id": "scarter@EXAMPLE.COM",
+ "principal": "scarter@EXAMPLE.COM"
+ },
+ ...
+ {
+ "_id": "openidm/admin@EXAMPLE.COM",
+ "principal": "openidm/admin@EXAMPLE.COM"
+ }
+ ],
+ ...
+}
+----
+
+. Retrieve bjensen's complete user principal from the Kerberos server:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/kerberos/account/bjensen@EXAMPLE.COM"
+{
+ "_id": "bjensen@EXAMPLE.COM",
+ "lastFailedAuthentication": "[never]",
+ "passwordExpiration": "[none]",
+ "lastSuccessfulAuthentication": "[never]",
+ "maximumTicketLife": "0 days 10:00:00",
+ "lastModified": "Tue May 24 04:05:45 EDT 2016 (openidm/admin@EXAMPLE.COM)",
+ "policy": "user [does not exist]",
+ "expirationDate": "[never]",
+ "failedPasswordAttempts": "0",
+ "maximumRenewableLife": "7 days 00:00:00",
+ "principal": "bjensen@EXAMPLE.COM",
+ "lastPasswordChange": "Tue May 24 04:05:45 EDT 2016"
+}
+----
++
+Note the default values for properties such as `maximumRenewableLife`. These values are set in your connector configuration. For more information, see xref:../connectors-guide/chap-kerberos.adoc#ssh-kerberos-config["Configuring the Kerberos Connector"] in the __Connectors Guide__.
++
+To perform this step in the Admin UI, select Manage > User, click bjensen's entry, and click the Linked Systems tab to display her corresponding entry on the Kerberos server.
+
+. Delete the managed user bjensen by specifying her managed object ID in the DELETE request.
++
+First, obtain her ID by querying for her userName:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=userName+eq+'bjensen'"
+{
+ "result": [
+ {
+ "_id": "ce3d9b8f-1d15-4950-82c1-f87596aadcb6",
+ "_rev": "3",
+ "userName": "bjensen",
+ "givenName": "Barbara",
+ "sn": "Jensen",
+ "displayName": "Barbara Jensen",
+ "mail": "bjensen@example.com",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+ }
+ ],
+ ...
+}
+----
++
+Now delete the user with ID `ce3d9b8f-1d15-4950-82c1-f87596aadcb6`. This ID will obviously be different in your example.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/user/ce3d9b8f-1d15-4950-82c1-f87596aadcb6"
+{
+ "_id": "ce3d9b8f-1d15-4950-82c1-f87596aadcb6",
+ "_rev": "3",
+ "userName": "bjensen",
+ "givenName": "Barbara",
+ "sn": "Jensen",
+ "displayName": "Barbara Jensen",
+ "mail": "bjensen@example.com",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
++
+To delete bjensen's managed user entry by using the Admin UI, select Manage > User, click on bjensen's entry, select the checkbox next to her entry, and click Delete Selected.
+
+. Reconcile the managed user repository and the Kerberos database again:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=managedUser_systemKerberos"
+{
+ "_id": "862ab9ba-d1d9-4058-b6bc-a23a94b68776-584",
+ "state": "ACTIVE"
+}
+----
+
+. Retrieve the list of Kerberos user principals again. The Kerberos principal for bjensen should have been been removed from the list:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/kerberos/account?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "K/M@EXAMPLE.COM",
+ "principal": "K/M@EXAMPLE.COM"
+ },
+ {
+ "_id": "kadmin/admin@EXAMPLE.COM",
+ "principal": "kadmin/admin@EXAMPLE.COM"
+ },
+ {
+ "_id": "kadmin/changepw@EXAMPLE.COM",
+ "principal": "kadmin/changepw@EXAMPLE.COM"
+ },
+ {
+ "_id": "kadmin/krb1.example.com@EXAMPLE.COM",
+ "principal": "kadmin/krb1.example.com@EXAMPLE.COM"
+ },
+ {
+ "_id": "kiprop/krb1.example.com@EXAMPLE.COM",
+ "principal": "kiprop/krb1.example.com@EXAMPLE.COM"
+ },
+ {
+ "_id": "krbtgt/EXAMPLE.COM@EXAMPLE.COM",
+ "principal": "krbtgt/EXAMPLE.COM@EXAMPLE.COM"
+ },
+ {
+ "_id": "scarter@EXAMPLE.COM",
+ "principal": "scarter@EXAMPLE.COM"
+ },
+ {
+ "_id": "openidm/admin@EXAMPLE.COM",
+ "principal": "openidm/admin@EXAMPLE.COM"
+ }
+ ],
+ ...
+}
+----
+
+====
+
+[NOTE]
+====
+Some user IDs in Kerberos include characters such as a forward slash (`/`) and an "at sign" (`@`) that prevent them from being used directly in a REST URL. For example, `openidm/system/kerberos/account/kadmin/admin@EXAMPLE.COM`, where the ID is `kadmin/admin@EXAMPLE.COM`. To retrieve such entries directly over REST, you must URL-encode the Kerberos ID as follows:
+
+[source, console]
+----
+"http://localhost:8080/openidm/system/kerberos/account/kadmin%2Fadmin%40EXAMPLE.COM"
+----
+====
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-ldap-samples.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-ldap-samples.adoc
new file mode 100644
index 000000000..9c857dcea
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-ldap-samples.adoc
@@ -0,0 +1,2703 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-ldap-samples]
+== LDAP Samples - Reconciling Data Between OpenIDM and One or More LDAP Directories
+
+This chapter walks you through the LDAP samples (those samples labeled 2, 2b, 2c, 2d, 5, 5b and 6 in the `openidm/samples` directory). For a complete list of the samples provided with OpenIDM, and an overview of each sample, see xref:chap-overview.adoc#chap-overview["Overview of the OpenIDM Samples"].
+
+[#more-sample-2]
+=== Sample 2 - LDAP One Way
+
+Sample 2 resembles xref:chap-xml-samples.adoc#more-sample-1["First OpenIDM Sample - Reconciling an XML File Resource"], but in sample 2 OpenIDM is connected to a local LDAP server. The sample has been tested with link:http://www.forgerock.org/opendj.html[OpenDJ, window=\_blank], but should work with any LDAPv3-compliant server.
+
+Sample 2 demonstrates how OpenIDM can pick up new or changed objects from an external resource. The sample contains only one mapping, from the external LDAP server resource to the OpenIDM repository. The sample therefore does not push any changes made to OpenIDM managed user objects out to the LDAP server.
+
+[#external-ldap-config-2]
+==== LDAP Server Configuration
+
+Sample 2 expects the following configuration for the external LDAP server:
+
+* The LDAP server runs on the local host.
+
+* The LDAP server listens on port 1389.
+
+* A user with DN `cn=Directory Manager` and password `password` has read access to the LDAP server.
+
+* Directory data for that server is stored under base DN `dc=example,dc=com`.
+
+* User objects for that server are stored under base DN `ou=People,dc=example,dc=com`.
+
+* User objects have the object class `inetOrgPerson`.
+
+* User objects have the following attributes:
++
+
+** `cn`
+
+** `description`
+
+** `givenName`
+
+** `mail`
+
+** `sn`
+
+** `telephoneNumber`
+
+** `uid`
+
+** `userPassword`
+
++
+An example user object follows.
++
+
+[source, ldif]
+----
+dn: uid=jdoe,ou=People,dc=example,dc=com
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+objectClass: top
+givenName: John
+uid: jdoe
+cn: John Doe
+telephoneNumber: 1-415-523-0772
+sn: Doe
+mail: jdoe@example.com
+description: Created by OpenIDM
+userPassword: password
+----
+
+The following steps provide setup instructions for an OpenDJ server. Adjust these instructions if you are using an alternative LDAP server.
+
+====
+
+. Download OpenDJ from ForgeRock's link:https://forgerock.org/downloads/[download site, window=\_top] and extract the zip archive.
++
+The LDIF data for this sample is provided in the file `openidm/samples/sample2/data/Example.ldif`. You will need to import this data during your OpenDJ setup.
+
+. Install OpenDJ using the command-line setup.
++
+Substitute the `--ldifFile` argument with the path to the `Example.ldif` file in your OpenIDM installation:
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ ./setup --cli \
+--hostname localhost \
+--ldapPort 1389 \
+--rootUserDN "cn=Directory Manager" \
+--rootUserPassword password \
+--adminConnectorPort 4444 \
+--baseDN dc=com \
+--ldifFile /path/to/openidm/samples/sample2/data/Example.ldif \
+--acceptLicense \
+--no-prompt
+...
+Configuring Directory Server ..... Done.
+Importing LDIF file /path/to/openidm/samples/sample2/data/Example.ldif ...... Done.
+Starting Directory Server ...... Done..
+...
+----
+
+====
+
+
+[#install-sample2]
+==== Install the Sample
+
+Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for sample 2.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample2
+----
+
+
+[#run-sample2]
+==== Reconcile the Repository
+
+The mapping configuration file (`sync.json`) for this sample includes the mapping `systemLdapAccounts_managedUser`, which synchronize users from the source LDAP server with the target OpenIDM repository.
+
+You can run this part of the sample by using the `curl` command-line utility, or by using the OpenIDM Administration UI. This section provides instructions for both methods.
+
+[#d5830e1489]
+.Run the Sample Using the Command Line
+====
+
+. Reconcile the repository by running the following command:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemLdapAccounts_managedUser&waitForCompletion=true"
+{
+ "_id": "b1394d10-29b0-4ccf-81d8-c88948ea121c-4",
+ "state": "SUCCESS"
+}
+----
++
+The reconciliation operation creates the two users from the LDAP server in the OpenIDM repository, assigning the new objects random unique IDs.
+
+. To retrieve the users from the repository, query their IDs as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "f52df646-7108-45e1-9342-1a17f257b497",
+ "_rev": "1"
+ },
+ {
+ "_id": "f7fccf54-e76a-404c-93f0-7486d30f1dc3",
+ "_rev": "1"
+ }
+ ],
+...
+}
+----
+
+. To retrieve individual user objects, include the ID in the URL, for example:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/0a5546d6-149b-4f8b-b3be-4afa8a267d45"
+{
+ "_id": "f7fccf54-e76a-404c-93f0-7486d30f1dc3",
+ "_rev": "1",
+ "displayName": "Barbara Jensen",
+ "description": "Created for OpenIDM",
+ "givenName": "Barbara",
+ "mail": "bjensen@example.com",
+ "sn": "Jensen",
+ "telephoneNumber": "1-360-229-7105",
+ "userName": "bjensen",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
+
+====
+
+[#d5830e1528]
+.Run the Sample Using the Admin UI
+====
+
+. Log in to the Admin UI at the URL `\https://localhost:8443/admin` as the default administrative user (`openidm-admin`) with password `openidm-admin`.
++
+
+[WARNING]
+======
+To protect your deployment in production, change the default administrative password. To do so, navigate to the Self-Service UI at `\https://localhost:8443/` and click Change Password.
+======
+
+. Click Configure > Mappings.
++
+This page shows one configured mapping, from the `ldap` server to the OpenIDM repository (`managed/user`).
++
+
+image::images/sample2-mappings.png[]
+
+. Click anywhere on the mapping and click Reconcile Now.
++
+The reconciliation operation creates the two users from the LDAP server in the OpenIDM repository.
+
+. Retrieve the users in the repository. Click Manage > User.
++
+You should now see two users from the LDAP server, reconciled to the OpenIDM repository.
+
+. When you click a username, you can view the details of that user account.
+
+====
+
+
+
+[#more-sample-2b]
+=== Sample 2b - LDAP Two Way
+
+Like sample 2, sample 2b connects to an external LDAP server. However, sample 2b has two mappings configured, one from the LDAP server to the OpenIDM repository, and the other from the OpenIDM repository to the LDAP server.
+
+[#external-ldap-config-2b]
+==== External LDAP Configuration
+
+As demonstrated for sample 2, you can use OpenDJ as an LDAP server. The LDIF data for this sample is provided in the file `openidm/samples/sample2b/data/Example.ldif`. You will need to import this data during your OpenDJ setup.
+
+Configure the LDAP server as for sample 2, xref:#external-ldap-config-2["LDAP Server Configuration"], but import the LDIF file that is specific to Sample 2b during the setup. The LDAP user must have write access to create users from OpenIDM on the LDAP server.
+
+
+[#install-sample2b]
+==== Install the Sample
+
+Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for sample 2b.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample2b
+----
+
+
+[#run-sample2b]
+==== Run the Sample
+
+The mapping configuration file (`sync.json`) for this sample includes two mappings, `systemLdapAccounts_managedUser`, which synchronizes users from the source LDAP server with the target OpenIDM repository, and `managedUser_systemLdapAccounts`, which synchronizes changes from the OpenIDM repository to the LDAP server.
+
+You can run this part of the sample by using the `curl` command-line utility, or by using the OpenIDM Administration UI. This section provides instructions for both methods.
+
+[#d5830e1629]
+.Run the Sample Using the Command Line
+====
+
+. Reconcile the repository over the REST interface by running the following command:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemLdapAccounts_managedUser&waitForCompletion=true"
+{
+ "state": "SUCCESS",
+ "_id": "027e25e3-7a33-4858-9080-161c2b40a6bf-2"
+}
+----
++
+The reconciliation operation returns a reconciliation run ID and the status of the operation. Reconciliation creates user objects from LDAP in the OpenIDM repository, assigning the new objects random unique IDs.
+
+. To retrieve the users from the repository, query their IDs as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "d460ed00-74f9-48fb-8cc1-7829be60ddac",
+ "_rev": "1"
+ },
+ {
+ "_id": "74fe2d25-4eb1-4148-a3ae-ff80f194b3a6",
+ "_rev": "1"
+ }
+ ],
+...
+}
+----
+
+. To retrieve individual user objects, include the ID in the URL, for example:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/d460ed00-74f9-48fb-8cc1-7829be60ddac"
+{
+ "_id": "d460ed00-74f9-48fb-8cc1-7829be60ddac",
+ "_rev": "1",
+ "displayName": "Barbara Jensen",
+ "description": "Created for OpenIDM",
+ "givenName": "Barbara",
+ "mail": "bjensen@example.com",
+ "telephoneNumber": "1-360-229-7105",
+ "sn": "Jensen",
+ "userName": "bjensen",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
+
+. Test the second mapping by creating a user in the OpenIDM repository.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "mail":"fdoe@example.com",
+ "sn":"Doe",
+ "telephoneNumber":"555-1234",
+ "userName":"fdoe",
+ "givenName":"Felicitas",
+ "description":"Felicitas Doe",
+ "displayName":"fdoe"}' \
+ "http://localhost:8080/openidm/managed/user?_action=create"
+{
+ "_id": "90d1f388-d8c3-4438-893c-be4e498e7a1c",
+ "_rev": "1",
+ "mail": "fdoe@example.com",
+ "sn": "Doe",
+ "telephoneNumber": "555-1234",
+ "userName": "fdoe",
+ "givenName": "Felicitas",
+ "description": "Felicitas Doe",
+ "displayName": "fdoe",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
+
+. By default, __implicit synchronization__ is enabled for mappings __from__ the `managed/user` repository __to__ any external resource. This means that when you update a managed object, any mappings defined in the `sync.json` file that have the managed object as the source are automatically executed to update the target system. For more information, see xref:../integrators-guide/chap-synchronization.adoc#synchronization-mappings-file["Mapping Source Objects to Target Objects"] in the __Integrator's Guide__.
++
+Test that the implicit synchronization has been successful by querying the users in the LDAP directory over REST, as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "uid=jdoe,ou=People,dc=example,dc=com",
+ "dn": "uid=jdoe,ou=People,dc=example,dc=com"
+ },
+ {
+ "_id": "uid=bjensen,ou=People,dc=example,dc=com",
+ "dn": "uid=bjensen,ou=People,dc=example,dc=com"
+ },
+ {
+ "_id": "uid=fdoe,ou=People,dc=example,dc=com",
+ "dn": "uid=fdoe,ou=People,dc=example,dc=com"
+ }
+ ],
+...
+}
+----
++
+Note the new entry for user `fdoe`.
+
+. Query the complete entry by including `fdoe`'s ID in the URL.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account/uid=fdoe,ou=People,dc=example,dc=com"
+{
+ "_id": "uid=fdoe,ou=People,dc=example,dc=com",
+ "mail": "fdoe@example.com",
+ "employeeType": null,
+ "ldapGroups": [],
+ "telephoneNumber": "555-1234",
+ "givenName": "Felicitas",
+ "cn": "fdoe",
+ "dn": "uid=fdoe,ou=People,dc=example,dc=com",
+ "uid": "fdoe",
+ "sn": "Doe",
+ "description": "Felicitas Doe"
+}
+----
+
+====
+
+[#d5830e1727]
+.Run the Sample Using the Admin UI
+====
+
+. Log in to the Admin UI at the URL `\https://localhost:8443/admin` as the default administrative user (`openidm-admin`) with password `openidm-admin`.
++
+
+[WARNING]
+======
+To protect your deployment in production, change the default administrative password. To do so, navigate to the Self-Service UI at `\https://localhost:8443/` and click Change Password.
+======
+
+. Click Configure > Mappings.
++
+This tab shows two configured mappings, one from the `ldap` server to the OpenIDM repository (`managed/user`) and one from the OpenIDM repository to the `ldap` server.
+
+. Click anywhere on the first mapping and click Reconcile Now.
++
+The reconciliation operation creates the two users from the LDAP server in the OpenIDM repository.
+
+. Retrieve the users in the repository. Click Manage > User.
+
+. You should see two users from the LDAP server, reconciled to the OpenIDM repository.
+
+. To retrieve the details of a specific user, click that username in the User List page.
+
+. Add a new user in the OpenIDM repository by clicking New User in the User List page.
++
+Complete the user details and click Save.
+
+. By default, __implicit synchronization__ is enabled for mappings __from__ the `managed/user` repository __to__ any external resource. This means that when you update a managed object, any mappings defined in the `sync.json` file that have the managed object as the source are automatically executed to update the target system. For more information, see xref:../integrators-guide/chap-synchronization.adoc#synchronization-mappings-file["Mapping Source Objects to Target Objects"] in the __Integrator's Guide__.
++
+To test that the implicit synchronization has been successful, look at `fdoe`'s record, and click the Linked Systems tab. The information under this tab includes the external resource to which this user entry is mapped.
+
+====
+
+
+
+[#more-sample-2c]
+=== Sample 2c - Synchronizing LDAP Group Membership
+
+Like sample 2b, sample 2c connects to an external LDAP server and has mappings from the LDAP server to the OpenIDM repository, and from the OpenIDM repository to the LDAP server. However, in sample 2c, LDAP group memberships are synchronized, in addition to user entries.
+
+As demonstrated for sample 2, you can use OpenDJ as an LDAP server. The LDIF data for this sample is provided in the file `openidm/samples/sample2c/data/Example.ldif`.
+
+[#external-ldap-config-2c]
+==== External LDAP Configuration
+
+Configure the LDAP server as for sample 2, xref:#external-ldap-config-2["LDAP Server Configuration"]. The LDAP user must have write access to create users from OpenIDM on the LDAP server. When you configure the LDAP server, import the LDIF file customized for this sample, `openidm/samples/sample2c/data/Example.ldif`. This file includes two LDAP groups:
+
+[source, ldif]
+----
+dn: ou=Groups,dc=example,dc=com
+ou: Groups
+objectClass: organizationalUnit
+objectClass: top
+
+dn: cn=openidm,ou=Groups,dc=example,dc=com
+uniqueMember: uid=jdoe,ou=People,dc=example,dc=com
+cn: openidm
+objectClass: groupOfUniqueNames
+objectClass: top
+
+dn: cn=openidm2,ou=Groups,dc=example,dc=com
+uniqueMember: uid=bjensen,ou=People,dc=example,dc=com
+cn: openidm2
+objectClass: groupOfUniqueNames
+objectClass: top
+----
+The users with DNs `uid=jdoe,ou=People,dc=example,dc=com` and `uid=bjensen,ou=People,dc=example,dc=com` are also imported with the `Example.ldif` file.
+
+
+[#install-sample2c]
+==== Install the Sample
+
+Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for sample 2c.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample2c
+----
+
+
+[#run-sample2c]
+==== Run the Sample
+
+The mapping configuration file (`sync.json`) for this sample includes two mappings, `systemLdapAccounts_managedUser`, which synchronizes users from the source LDAP server with the target OpenIDM repository, and `managedUser_systemLdapAccounts`, which synchronizes changes from the OpenIDM repository to the LDAP server.
+
+You can run this part of the sample by using the `curl` command-line utility, or by using the OpenIDM Administration UI. This section provides instructions for both methods.
+
+[#d5830e1874]
+.Run the Sample Using the Command Line
+====
+
+. Reconcile the repository over the REST interface by running the following command:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemLdapAccounts_managedUser&waitForCompletion=true"
+{
+ "_id": "6652c292-5309-40e5-b272-b74d67dd95c9-4",
+ "state": "SUCCESS"
+}
+----
++
+The reconciliation operation returns a reconciliation run ID and the status of the operation. Reconciliation creates user objects from LDAP in the OpenIDM repository, assigning the new objects random unique IDs.
+
+. To retrieve the users from the repository, query their IDs as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "b63fb9a7-99bc-4eb4-8bfd-15f14a756e5b",
+ "_rev": "1"
+ },
+ {
+ "_id": "8462fe0c-2ab2-459a-a25e-474474889c9e",
+ "_rev": "1"
+ }
+ ],
+...
+}
+----
+
+. To retrieve individual user objects, include the ID in the URL. The following request retrieves the user object for John Doe:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/8462fe0c-2ab2-459a-a25e-474474889c9e"
+{
+ "_id": "8462fe0c-2ab2-459a-a25e-474474889c9e",
+ "_rev": "1",
+ "displayName": "John Doe",
+ "description": "Created for OpenIDM",
+ "givenName": "John",
+ "mail": "jdoe@example.com",
+ "telephoneNumber": "1-415-599-1100",
+ "sn": "Doe",
+ "userName": "jdoe",
+ "ldapGroups": [
+ "cn=openidm,ou=Groups,dc=example,dc=com"
+ ],
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
++
+Note that John Doe's user object contains an `ldapGroups` property, the value of which indicates his groups on the LDAP server:
++
+
+[source]
+----
+"ldapGroups":["cn=openidm,ou=Groups,dc=example,dc=com"]
+----
+
+. Update John Doe's `ldapGroups` property, to change his membership from the `openidm` group to the `openidm2` group.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '[
+ {
+ "operation":"replace",
+ "field":"/ldapGroups",
+ "value": ["cn=openidm2,ou=Groups,dc=example,dc=com"]
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user?_action=patch&_queryId=for-userName&uid=jdoe"
+{
+ "displayName": "John Doe",
+ "description": "Created for OpenIDM",
+ "givenName": "John",
+ "mail": "jdoe@example.com",
+ "telephoneNumber": "1-415-599-1100",
+ "sn": "Doe",
+ "userName": "jdoe",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": [],
+ "_id": "8462fe0c-2ab2-459a-a25e-474474889c9e",
+ "_rev": "2",
+ "ldapGroups": [
+ "cn=openidm2,ou=Groups,dc=example,dc=com"
+ ]
+}
+----
++
+This command changes John Doe's `ldapGroups` property in the OpenIDM repository, from `"cn=openidm,ou=Groups,dc=example,dc=com"` to `"cn=openidm2,ou=Groups,dc=example,dc=com"`. As a result of implicit synchronization, the change is propagated to the LDAP server. John Doe is removed from the first LDAP group and added to the second LDAP group in OpenDJ.
+
+. You can verify this change by querying John Doe's record on the LDAP server, as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account/uid=jdoe,ou=People,dc=example,dc=com"
+{
+ "_id": "uid=jdoe,ou=People,dc=example,dc=com",
+ "telephoneNumber": "1-415-599-1100",
+ "description": "Created for OpenIDM",
+ "sn": "Doe",
+ "dn": "uid=jdoe,ou=People,dc=example,dc=com",
+ "ldapGroups": [
+ "cn=openidm2,ou=Groups,dc=example,dc=com"
+ ],
+ "uid": "jdoe",
+ "cn": "John Doe",
+ "givenName": "John",
+ "mail": "jdoe@example.com"
+}
+----
+
+====
+
+[#d5830e1964]
+.Run the Sample Using the Admin UI
+====
+
+. Log in to the Admin UI at the URL `\https://localhost:8443/admin` as the default administrative user (`openidm-admin`) with password `openidm-admin`.
++
+
+[WARNING]
+======
+To protect your deployment in production, change the default administrative password. To do so, navigate to the Self-Service UI at `\https://localhost:8443/` and click Change Password.
+======
+
+. Click Configure > Mappings.
++
+This window shows two configured mappings, one from the `ldap` server to the OpenIDM repository (`managed/user`) and one from the OpenIDM repository to the `ldap` server.
+
+. Click anywhere on the first mapping and click Reconcile Now.
++
+The reconciliation operation creates the two users from the LDAP server in the OpenIDM repository.
+
+. Click Manage > User. Examine the users reconciled from the LDAP server to the internal repository.
+
+. Examine the two users from the LDAP server that have been reconciled to the OpenIDM repository.
+
+. To retrieve the details of a specific user, click that username. In this case, click on user `jdoe`.
++
+Examine the information stored for user `jdoe`. Click the Linked Systems tab. The Linked Resource item indicates the external resource on which John Doe's managed object is mapped, in this case, `ldap account`.
++
+In this linked resource, John Doe's `ldapGroups` are displayed. Currently, John Doe is a member of `cn=openidm,ou=Groups,dc=example,dc=com`.
+
+. Update John Doe's `ldapGroups` property to change his membership from the `openidm` group to the `openidm2` group. Currently, you can only do this over the REST interface, as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '[
+ {
+ "operation":"replace",
+ "field":"/ldapGroups",
+ "value": ["cn=openidm2,ou=Groups,dc=example,dc=com"]
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user?_action=patch&_queryId=for-userName&uid=jdoe"
+{
+ "displayName": "John Doe",
+ "description": "Created for OpenIDM",
+ "givenName": "John",
+ "mail": "jdoe@example.com",
+ "telephoneNumber": "1-415-599-1100",
+ "sn": "Doe",
+ "userName": "jdoe",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": [],
+ "_id": "8462fe0c-2ab2-459a-a25e-474474889c9e",
+ "_rev": "2",
+ "ldapGroups": [
+ "cn=openidm2,ou=Groups,dc=example,dc=com"
+ ]
+}
+----
++
+This command changes John Doe's `ldapGroups` property in the OpenIDM repository, from `"cn=openidm,ou=Groups,dc=example,dc=com"` to `"cn=openidm2,ou=Groups,dc=example,dc=com"`. As a result of implicit synchronization, the change is propagated to the LDAP server. John Doe is removed from the first LDAP group and added to the second LDAP group in OpenDJ.
+
+. You can verify this change by reloading John Doe's user information, clicking Linked Systems, and examining the value of his `ldapGroups` property.
+
+====
+
+
+
+[#more-sample-2d]
+=== Sample 2d - Synchronizing LDAP Groups
+
+Sample 2d also connects to an external LDAP server. This sample focuses on LDAP Group synchronization.
+
+As demonstrated for sample 2, you can use OpenDJ as an LDAP server. Before installing OpenDJ, you may need an LDIF file. The OpenIDM installation includes the following LDIF file, customized for this sample: `openidm/samples/sample2d/data/Example.ldif`. If you need a copy of this file, download and install OpenIDM as described in xref:../install-guide/chap-install.adoc#install-openidm["To Install OpenIDM Services"] in the __Installation Guide__.
+
+[#external-ldap-config-2d]
+==== External LDAP Configuration
+
+Configure the LDAP server as for sample 2, xref:#external-ldap-config-2["LDAP Server Configuration"]. The LDAP user must have write access to create users from OpenIDM on the LDAP server.
+
+In addition, two LDAP Groups should be created, as found in the following LDIF file: `openidm/samples/sample2d/data/Example.ldif` (if they have not already been added through sample 2c):
+
+[source, ldif]
+----
+dn: ou=Groups,dc=example,dc=com
+ou: Groups
+objectClass: organizationalUnit
+objectClass: top
+
+dn: cn=openidm,ou=Groups,dc=example,dc=com
+uniqueMember: uid=jdoe,ou=People,dc=example,dc=com
+cn: openidm
+objectClass: groupOfUniqueNames
+objectClass: top
+
+dn: cn=openidm2,ou=Groups,dc=example,dc=com
+uniqueMember: uid=bjensen,ou=People,dc=example,dc=com
+cn: openidm2
+objectClass: groupOfUniqueNames
+objectClass: top
+----
+The user with dn `uid=jdoe,ou=People,dc=example,dc=com` is also imported with the `Example.ldif` file.
+
+There is an additional user, `bjensen` in the sample LDIF file. This user is essentially a "dummy" user, provided for compliance with RFC 4519, which stipulates that every `groupOfUniqueNames` object must contain at least one `uniqueMember`. `bjensen` is not actually used in this sample.
+
+
+[#install-sample2d]
+==== Install the Sample
+
+Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for sample 2d.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample2d
+----
+
+
+[#run-sample2d]
+==== Running the Sample
+
+The mapping configuration file (`sync.json`) for this sample includes three mappings:
+
+* `systemLdapAccounts_managedUser`
++
+Synchronizes users from the source LDAP server with the target OpenIDM repository,
+
+* `managedUser_systemLdapAccounts`
++
+Synchronizes changes from the OpenIDM repository to the LDAP server.
+
+* `systemLdapGroups_managedGroup`
++
+Synchronizes groups from the source LDAP server with the target OpenIDM repository.
+
+Due to the similarity with other OpenIDM samples, especially samples 2b and 2c, the focus of this sample is on the mapping unique to this sample, `systemLdapGroups_managedGroup`.
+
+You can run this part of the sample by using the `curl` command-line utility, or by using the OpenIDM Administration UI. This section provides instructions for both methods.
+
+[#d5830e2171]
+.Run the Sample Using the Command Line
+====
+
+. Reconcile the repository over the REST interface for the group mapping, `systemLdapGroups_managedGroup` by running the following command:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemLdapGroups_managedGroup&waitForCompletion=true"
+----
++
+The reconciliation operation returns a reconciliation run ID, and the status of the operation.
+
+. With the configuration of sample 2d, OpenIDM creates group objects from LDAP in OpenIDM. To list group objects by ID, run a query over the REST interface.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/group?_queryFilter=true"
+----
++
+The resulting JSON object should include content similar to the following.
++
+
+[source, javascript]
+----
+{
+ "result" : [ {
+ "dn" : "cn=openidm,ou=Groups,dc=example,dc=com",
+ "_id" : "837df489-35d6-48d1-81a5-23792b49838a",
+ "_rev" : "1",
+ "description" : [ ],
+ "uniqueMember" : [ "uid=jdoe,ou=People,dc=example,dc=com" ],
+ "name" : [ "openidm" ]
+ }, {
+ "dn" : "cn=openidm2,ou=Groups,dc=example,dc=com",
+ "_id" : "7575c1c7-86cf-43bc-bf1d-5c9cfc539124",
+ "_rev" : "1",
+ "description" : [ ],
+ "uniqueMember" : [ "uid=bjensen,ou=People,dc=example,dc=com" ],
+ "name" : [ "openidm2" ]
+ } ],
+...
+ }
+----
+
+====
+
+[#d5830e2200]
+.Run the Sample Using the Admin UI
+====
+
+. Log in to the Admin UI at the URL `\https://localhost:8443/admin` as the default administrative user (`openidm-admin`) with password `openidm-admin`.
++
+
+[WARNING]
+======
+To protect your deployment in production, change the default administrative password. To do so, navigate to the Self-Service UI at `\https://localhost:8443/` and click Change Password.
+======
+
+. Click Configure > Mappings.
++
+This page shows three configured mappings, from the `ldap` server accounts repository to the OpenIDM repository (`managed/user`), from the OpenIDM repository back to the `ldap` server, and from the `ldap` server group accounts repository to the OpenIDM `managed/group` repository.
+
+. Click anywhere on the third mapping and click Reconcile Now.
++
+The reconciliation operation creates the two groups from the LDAP server in the OpenIDM repository.
+
+. Retrieve the groups in the repository by clicking the Association tab below the mapping. Scroll down to Data Association Management.
++
+
+image::images/sample2d-groups.png[]
++
+The three groups from the LDAP server (source) have been reconciled to the OpenIDM repository (target).
+
+====
+
+
+
+[#more-sample-5]
+=== Sample 5 - Synchronization of Two LDAP Resources
+
+Sample 5 demonstrates the flow of data from one external resource to another. The resources are named `LDAP` and `AD` but in the sample, both resources are simulated with XML files.
+
+You can optionally configure an outbound email service, if you want to receive emailed reconciliation summaries, as described in the following section.
+
+[#email-sample5]
+==== Configure Email for the Sample
+
+If you do not configure the email service, the functionality of the sample does not change. However, you might see the following message in the OSGi console when you run a reconciliation operation:
+
+[source, console]
+----
+Email service not configured; report not generated.
+----
+
+====
+To configure OpenIDM to send a reconciliation summary by email, follow these steps:
+
+. Copy the template `external.email.json` file from the `samples/misc` directory to the `conf` directory of Sample 5:
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ cp samples/misc/external.email.json samples/sample5/conf
+----
+
+. Edit the `external.email.json` file for outbound email, as described in xref:../integrators-guide/chap-mail.adoc#chap-mail["Sending Email"] in the __Integrator's Guide__.
+
+. Edit the `reconStats.js` file from the `sample5/script` directory. Near the start of the file, configure the OpenIDM email service to send statistics to the email addresses of your choice:
++
+
+[source, javascript]
+----
+var email = {
+ //UPDATE THESE VALUES
+ from : "openidm@example.com",
+ to : "youremail@example.com",
+ cc : "idmadmin2@example.com,idmadmin3@example.com",
+ subject : "Recon stats for " + global.mappingName,
+ type : "text/html"
+},
+template,
+...
+----
+
+====
+
+
+[#install-sample5]
+==== Install the Sample
+
+No external configuration is required for this sample. Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration of sample 5.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample5
+----
+The XML files that simulate the resources are located in the `openidm/samples/sample5/data/` folder. When you start OpenIDM with the sample 5 configuration, OpenIDM creates the `xml_AD_Data.xml` file, which does not contain users until you run reconciliation.
+
+
+[#run-sample5]
+==== Run the Sample
+
+Run a reconciliation operation, to synchronize the contents of the simulated LDAP resource to the OpenIDM repository.
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemLdapAccounts_managedUser&waitForCompletion=true"
+----
+This command creates a user in the repository. It is not necessary to run a second reconciliation operation to synchronize the AD resource. Automatic synchronization propagates any change made to managed users in the OpenIDM repository to the simulated AD resource.
+
+Review the contents of `xml_AD_Data.xml`. It should now contain information for the same user that was present in the startup version of the `xml_LDAP_Data.xml` file.
+
+Alternatively, you can list users in the AD resource with the following command:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ad/account?_queryId=query-all-ids"
+
+ {
+ "result" : [ {
+ "name" : "DDOE1",
+ "__UID__" : "8dad9df3-820d-41ea-a3ab-a80c241bbc98",
+ "_id" : "8dad9df3-820d-41ea-a3ab-a80c241bbc98"
+ } ],
+...
+}
+----
+You can use the `_id` of the user to read the user information from the AD resource, for example:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ad/account/8dad9df3-820d-41ea-a3ab-a80c241bbc98"
+{
+ "email" : [ "mail1@example.com" ],
+ "name" : "DDOE1",
+ "__UID__" : "8dad9df3-820d-41ea-a3ab-a80c241bbc98",
+ "firstname" : "Darth",
+ "lastname" : "Doe",
+ "_id" : "8dad9df3-820d-41ea-a3ab-a80c241bbc98"
+}[
+----
+To verify that the sample is working, repeat the process. Set up a second user in the `xml_LDAP_Data.xml` file. An example of how that file might appear with a second user (`GDOE1`) is shown here:
+
+[source, xml]
+----
+
+
+
+ 1
+ TestPassw0rd2
+ Darth
+ Created By XML1
+ DDOE1
+ mail1@example.com
+ Doe
+
+
+ 2
+ TestPassw0rd2
+ Garth
+ Created By XML1
+ GDOE1
+ mail2@example.com
+ Doe
+
+
+----
+Rerun the reconciliation and query REST commands shown previously. The reconciliation operation creates the new user from the simulated LDAP resource in the OpenIDM repository. An implicit synchronization operation then creates that user in the AD resource.
+
+
+
+[#more-sample-5b]
+=== Sample 5b - Failure Compensation With Multiple Resources
+
+The compensated synchronization mechanism depicted in this sample can help manage the risks associated with synchronizing data across multiple resources.
+
+Typically, when a managed/user object is changed, implicit synchronization replays that change to all configured external resources. If synchronization fails for one target resource (for example, due to a policy validation failure on the target, or the target being unavailable), the synchronization operation stops at that point. The effect is that a record might be changed in the repository, and in the targets on which synchronization was successful, but not on the failed target, or any targets that would have been synchronized after the failure. This situation can result in disparate data sets across resources. While a reconciliation operation would eventually bring all targets back in sync, reconciliation can be an expensive operation with large data sets.
+
+The compensated synchronization mechanism ensures that either all resources are synchronized successfully, or that the original change is rolled back. This mechanism uses an `onSync` script hook configured with a `compensate.js` script that can be used to "revert" the partial change to managed/user and to the corresponding external resources.
+
+Sample 5b is similar to sample 5 in that it simulates two external resources with XML files (located in the `sample5b/data` directory). The `xml_LDAP_Data.xml` file simulates an LDAP data source. OpenIDM creates the `xml_AD_Data.xml` file when you start OpenIDM with the sample. Sample 5b adds the `onSync` script hook to the process, configured in the `sample5b/conf/managed.json` file.
+
+The following excerpt of the `managed.json` file shows the `onSync` hook, which calls the `compensate.js` script, provided in the `/path/to/openidm/bin/defaults/script` directory.
+
+[source, javascript]
+----
+...
+},
+"onSync" : {
+ "type" : "text/javascript",
+ "file" : "compensate.js"
+},
+----
+You can use the `onSync` script hook to ensure that changes made in the repository are synchronized to all external resources, or that no changes are made. For more information about how implicit synchronization uses the `onSync` script hook, see xref:../integrators-guide/chap-synchronization.adoc#sync-failure-compensation["Configuring Synchronization Failure Compensation"] in the __Integrator's Guide__.
+
+You can optionally configure an outbound email service for this sample, if you want to receive emailed reconciliation summaries. The email service configuration is identical to that of sample 5 (xref:#email-sample5["Configure Email for the Sample"]).
+
+[#install-sample5b]
+==== Install the Sample
+
+No external configuration is required for this sample. Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration of sample 5b.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample5b
+----
+The XML files that simulate an external LDAP and AD resource are now located in the `openidm/samples/sample5b/data/` directory. The simulated AD data store file, `xml_AD_Data.xml`, does not contain users until you run reconciliation.
+
+Run the sample in exactly the same way that you did for Sample 5, following the steps in xref:#run-sample5["Run the Sample"]. Those steps will reconcile a user to your internal managed user repository.
+
+Unless you run the steps in xref:#run-sample5["Run the Sample"], you will not be able to run the steps in the next section.
+
+
+[#onsync-sample5b]
+==== Demonstrate onSync
+
+To demonstrate integration of the samples with the OpenIDM UI, this sample uses the UI to view and make changes to user objects in the repository. However, you can also use the REST interface to make these changes, as shown in the previous section.
+
+Log into the OpenIDM UI as the administrative user. On a local system, navigate to `\https://localhost:8443/admin`. The default administrative account and password are both `openidm-admin`.
+
+Select Manage > User. Make a change to the data of an existing user (`DDOE1`). As a result of the implicit synchronization from the managed object repository, that change is reflected almost immediately on the external resources. For sample 5b, you should see the changes in both XML files in the `sample5b/data` directory. Alternatively, you can query the external resources over REST, as described previously.
+
+The synchronization is successful, across all configured external resources, so the updated user record can be seen in both the `xml_LDAP_Data.xml` and `xml_AD_Data.xml` files.
+
+The next step is to simulate a problem connecting to the LDAP resource. One way to do so on the local system is to rename the LDAP data file so that it is unreadable. On a Linux system, the following command, as an administrative user, would serve that purpose:
+
+[source, console]
+----
+$ cd /path/to/openidm/samples/sample5b/data
+$ sudo mv xml_LDAP_Data.xml xml_LDAP_Data.xml.bak
+----
+In the UI, now try another update to user `DDOE1`. With the modified filename of the simulated LDAP resource, implicit synchronization cannot write to this resource. An error similar to the following is displayed in the log file, `openidm0.log.0`:
+
+[source, console]
+----
+Data file does not exist:
+/path/to/openidm/samples/sample5b/data/xml_LDAP_Data.xml
+----
+Although the AD resource is available, implicit synchronization will not reach this resource, because the mapping is specified __after__ the managed/user to LDAP mapping in the `sync.json` file.
+
+When the implicit synchronization operation fails for the LDAP resource, the `onSync` hook invokes the `compensate.js` script. This script attempts to revert the original change by performing another update to DDOE1 in the repository (managed/user). This change, in turn, triggers another automatic synchronization to the AD and LDAP resources.
+
+Because the LDAP resource is still unreadable, the synchronization to LDAP fails again, which triggers the `compensate.js` script again. This time, however, the script recognizes that the change was originally called as a result of a compensation and aborts.
+
+The original synchronization error from the first update is thrown from the script and the UI should display that error. If you refresh the UI, and view that user entry again, you will notice that the change to the entry has been reverted.
+
+Note that if you change the name of the AD resource file (to make it unavailable), a change to a managed/user entry will be synchronized successfully with the LDAP resource (because that mapping appears first in `sync.json`). The synchronization will fail for the AD resource. In this case, the change will be reverted on both the managed/user entry, and the LDAP resource.
+
+
+
+[#more-sample-6]
+=== Sample 6 - LiveSync With an AD Server
+
+Sample 6 resembles sample 5, but demonstrates LiveSync from an external resource. Sample 6 includes configuration files for two scenarios, depending on whether you have a live Active Directory (AD) service, or whether you need to simulate an AD service with an OpenDJ server. Each scenario is associated with a file in the `sample6/alternatives` directory. Depending on your scenario, copy the corresponding file to the `sample6/conf` directory.
+--
+
+Active AD Deployment::
+If you have an actual AD deployment available, copy the `provisioner.openicf-realad.json` file to the `conf/` subdirectory. You can then configure synchronization between an OpenDJ Directory Server and an active AD deployment.
+
++
+As this sample demonstrates synchronization __from__ the AD server __to__ OpenDJ, data on the AD server is not changed.
+
+Simulated AD Deployment::
+If you need to simulate an AD deployment, copy the `provisioner.openicf-fakead.json` file to the `conf/` subdirectory. You can then configure synchronization between an OpenDJ Directory server and a simulated AD server.
+
++
+This sample simulates an AD server on the same instance of OpenDJ, using a different base DN.
+
+--
+The options shown in the associated configuration files can be easily modified to work with any standard LDAP server.
+
+[#more-sample6-realad]
+==== Sample 6 with an Active AD Deployment
+
+If you have an existing, active instance of AD, set up OpenDJ, as described in the link:../../../opendj/3.5/install-guide[OpenDJ Installation Guide, window=\_blank].
+
+During installation, populate OpenDJ with the data in the `Example.ldif` file, available in the `sample6/data` directory.
+
+The actions run in this sample should not change any data on the AD server.
+
+
+[#d5830e2637]
+==== Sample 6 with a Simulated AD Deployment
+
+In this sample, an AD deployment is simulated with a different baseDN (`dc=fakead,dc=com`) on the same OpenDJ server instance. You can also simulate the AD server with a separate OpenDJ instance, running on the same host, as long as the two instances communicate on different ports. The data for the simulated AD instance is contained in the file `AD.ldif`. The data for the OpenDJ instance is contained in the file `Example.ldif`.
+
+
+[#external-resource-sample6]
+==== External Configuration
+
+
+[#prepare-livesync-sample6]
+===== Prepare OpenDJ For LiveSync
+
+This sample assumes a replicated OpenDJ server on the localhost system. When configured, OpenDJ replication includes an External Change Log (ECL), required to support LiveSync. LiveSync detects changes in OpenDJ by reading the ECL.
+
+Follow these steps to install and configure an OpenDJ instance.
+
+====
+
+. Download and extract the OpenDJ zip archive from link:https://forgerock.org/downloads/[https://forgerock.org/downloads/, window=\_blank].
+
+. Install OpenDJ using the command-line setup, and import the data file for this sample, as follows:
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ ./setup --cli \
+--hostname localhost \
+--ldapPort 1389 \
+--rootUserDN "cn=Directory Manager" \
+--rootUserPassword password \
+--adminConnectorPort 4444 \
+--baseDN dc=com \
+--ldifFile /path/to/openidm/samples/sample6/data/Example.ldif \
+--acceptLicense \
+--no-prompt
+...
+Configuring Directory Server ..... Done.
+Creating Base Entry dc=com ..... Done.
+Starting Directory Server ....... Done.
+...
+----
++
+The sample assumes the following configuration:
++
+
+* The OpenDJ server is installed on the localhost.
+
+* The server listens for LDAP connections on port 1389.
+
+* The administration connector port is 4444.
+
+* The root user DN is `cn=Directory Manager`.
+
+* The root user password is `password`.
+
+
+. Configure the OpenDJ server for replication.
++
+To enable liveSync, this server must be configured for replication, even if it does not actually participate in a replication topology. The following commands configure the server for replication.
++
+
+[source, console]
+----
+$ ./dsconfig create-replication-server \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --provider-name "Multimaster Synchronization" \
+ --set replication-port:8989 \
+ --set replication-server-id:2 \
+ --type generic \
+ --trustAll \
+ --no-prompt
+
+ $ ./dsconfig create-replication-domain \
+ --hostname localhost \
+ --port 4444 \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --provider-name "Multimaster Synchronization" \
+ --domain-name fakead_com \
+ --set base-dn:dc=fakead,dc=com \
+ --set replication-server:localhost:8989 \
+ --set server-id:3 \
+ --type generic \
+ --trustAll \
+ --no-prompt
+----
+
+====
+Once OpenDJ is configured, you can proceed with either an active or simulated AD deployment.
+
+
+[#external-resource-sample6-active]
+===== External Configuration for an Active AD Deployment
+
+To configure an active AD deployment for sample 6, open the `provisioner.openicf-realad.json` file in a text editor. Update it as needed. At minimum, you should check and if needed update the following parameters in that file, as shown in the following table:
+
+[#realad-json-configuration]
+.Key Parameters for an Active AD Configuration
+[cols="40%,60%"]
+|===
+|Option |Description
+
+a|host
+a|The hostname/IP address of the AD server
+
+a|port
+a|The LDAP port; the default is 389.
+
+a|ssl
+a|By default, SSL is not used.
+
+a|principal
+a|The full DN of the account to bind with, such as "CN=Administrator,CN=Users,DC=example,DC=com"
+
+a|credentials
+a|If a password is used, replace null with that password. When OpenIDM starts, it encrypts that password in the `provisioner.openicf-realad.conf` file.
+
+a|baseContexts
+a|The DNs for account containers, such as ["CN=Users,DC=Example,DC=com"]
+
+a|baseContextsToSynchronize
+a|Set to the same value as `baseContexts`
+
+a|accountSearchFilter
+a|Default searches for active user (not computer) accounts
+
+a|accountSynchronizationFilter
+a|Default synchronizes with active user (not computer) accounts
+|===
+If you do not want to filter out computer and disabled user accounts, set the `accountSearchFilter` and `accountSynchronizationFilter` to `null`.
+
+
+[#external-resource-sample6-simulated]
+===== External Configuration for a Simulated AD Deployment
+
+Not everyone has a testable instance of AD readily available. For such administrators, you can use the `AD.ldif` file from the `data/` subdirectory to simulate an AD deployment.
+
+If you have not already done so, copy the `provisioner.openicf-fakead.json` file to the `conf` subdirectory.
+
+As previously mentioned, you can use a separate OpenDJ instance to simulate the AD server. However, the following instructions assume that the simulated AD server runs on the same OpenDJ instance.
+
+Open the `provisioner.openicf-fakead.json` file and note the following:
+
+* OpenDJ directory server uses port 1389 by default for users who cannot use privileged ports, so this is the port that is specified in the provisioner file. Adjust the port if your OpenDJ server is listening on a different port.
+
+* The simulated AD server uses the base DN `dc=fakead,dc=com`.
+
+To load the data for the simulated AD instance, launch the OpenDJ control panel, add the simulated AD baseDN (`dc=fakead,dc=com`), and then import the `sample6/data/AD.ldif` file. When you import the `AD.ldif` file, select "Append to Existing Data", not "Overwrite Existing Data". Otherwise, the data in the `dc=example,dc=com` baseDN will be overwritten.
+
+Alternatively, you could run the following command:
+
+[source, console]
+----
+$ cd /path/to/opendj/bin
+$ ./ldapmodify \
+--defaultAdd \
+--bindDN "cn=Directory Manager" \
+--bindPassword password \
+--hostname localhost \
+--port 1389 \
+--filename /path/to/openidm/samples/sample6/data/AD.ldif
+----
+
+
+
+[#install-sample6]
+==== Start OpenIDM with Sample 6
+
+Now that OpenDJ and a real or simulated AD database is configured, prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"]. You can then start OpenIDM with the configuration for sample 6.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample6
+----
+
+
+[#run-sample6]
+==== Run the Sample
+
+The following sections show how to run the sample with command-based reconciliation with a REST call, and to configure scheduled reconciliation with LiveSync.
+
+[#run-sample6-reconciliation]
+===== Using Reconciliation
+
+Now that OpenIDM is in operation, review the entries in the OpenDJ data store. When you run reconciliation, any entries that share the same `uid` with the AD data store will be updated with the contents from AD.
+
+If you have set up the simulated AD data store as described in xref:#external-resource-sample6-simulated["External Configuration for a Simulated AD Deployment"], compare the entries for `uid=jdoe` as shown in the `AD.ldif` and `Example.ldif` files. Note the different values of `givenName` for `uid=jdoe`.
+
+Run reconciliation over the REST interface. If you have followed the instructions for the simulated AD data store, the following command takes the information for user `jdoe` imported from the `AD.ldif` file, with a `givenName` of Johnny, and synchronizes that information to the LDAP database, overwriting the `givenName` of John for that same user `jdoe`.
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemAdAccounts_managedUser&waitForCompletion=true"
+----
+The reconciliation operation returns a reconciliation run ID, and the status of the operation.
+
+[source, console]
+----
+{
+ "state": "SUCCESS",
+ "_id": "985ee939-fbe1-4607-a757-00b404b4ef77"
+}
+----
+The reconciliation operation synchronizes the data in the AD server with the OpenIDM repository (managed/user). That information is then automatically synchronized to the OpenDJ server, as described in xref:../integrators-guide/chap-synchronization.adoc#handling-sync["Synchronization Situations and Actions"] in the __Integrator's Guide__.
+
+After reconciliation, list all users in the OpenDJ server data store.
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryId=query-all-ids"
+----
+The result should resemble the following JSON object.
+
+[source, javascript]
+----
+{
+ "result": [ {
+ "dn" : "uid=jdoe,ou=People,dc=example,dc=com",
+ "_id" : "uid=jdoe,ou=People,dc=example,dc=com"
+ }, {
+ "dn" : "uid=bjensen,ou=People,dc=example,dc=com",
+ "_id" : "uid=bjensen,ou=People,dc=example,dc=com"
+ } ],
+...
+}
+----
+You see only two entries, as the `uid=jdoe` entry from `dc=fakead,dc=com` overwrites the original LDAP entry for `uid=jdoe` in the reconciled LDAP data store.
+
+To read the user object in the OpenDJ server, run the `ldapsearch` command. The following example returns the entry for user `uid=jdoe`:
+
+[source, console]
+----
+$ ./ldapsearch \
+ --port 1389 \
+ --baseDN dc=example,dc=com \
+ "(uid=jdoe)"
+----
+
+
+[#run-sample6-live-sync]
+===== Using LiveSync
+
+You can trigger a reconciliation operation by configuring a schedule, or by launching the operation directly over the REST interface. You can also launch a LiveSync operation over REST, but LiveSync requires a configured schedule to poll for changes. When this sample's default LiveSync schedule (`schedule-activeSynchroniser_systemAdAccount.json`) is enabled, a LiveSync operation is launched every 15 seconds.
+
+LiveSync pushes changes made in the AD data store to the OpenIDM repository, automatically.
+
+The LiveSync schedule is disabled by default. To activate LiveSync, change the value of the `"enabled"` property from `false` to `true`.
+
+[source, javascript]
+----
+{
+ "enabled" : false,
+ "type" : "cron",
+ "schedule" : "0/15 * * * * ?",
+ "invokeService" : "provisioner",
+ "invokeContext" : {
+ "action" : "liveSync",
+ "source" : "system/ad/account"
+ },
+ "invokeLogLevel" : "debug"
+}
+----
+
+[#test-live-sync]
+.Testing LiveSync
+====
+Now you can test LiveSync. This procedure assumes that you have configured OpenDJ using the parameters and commands described in this section.
+
+. Create an LDIF file with a new user entry (`uid=bsmith`) that will be added to the simulated AD data store.
+
+. The following is the contents of a sample `bsmith.ldif` file for demonstration purposes:
++
+
+[source, console]
+----
+dn: uid=bsmith,ou=People,dc=fakead,dc=com
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: top
+givenName: Barry
+description: Created to see LiveSync work
+uid: bsmith
+cn: Barry
+sn: Smith
+mail: bsmith@example.com
+telephoneNumber: 1-415-523-0772
+userPassword: passw0rd
+----
++
+
+. Navigate to the `/path/to/opendj/bin` directory.
+
+. Use the `ldapmodify` command to add the `bsmith.ldif` file to the directory.
++
+
+[source, console]
+----
+$ ./ldapmodify \
+ --port 1389 \
+ --defaultAdd \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --filename /path/to/bsmith.ldif
+----
+
+. Now you can test synchronization by viewing the new user in the OpenIDM repository. The easiest way to do this, is through OpenIDM UI. You should be able to log into the UI with any of the accounts in the AD data store. For this example, log into the UI as user `bsmith`, with password `passw0rd`. The fact that you can log into the UI as this new user indicates that LiveSync has synchronized the user from the AD data store to the managed/user repository.
+
+. Implicit synchronization pushes this change out to the OpenDJ data store. To test this synchronization operation, search the OpenDJ baseDN for the new user entry.
++
+
+[source, console]
+----
+$ ./ldapsearch \
+ --port 1389 \
+ --baseDN ou=people,dc=example,dc=com \
+ "(uid=bsmith)"
+----
+
+====
+
+
+
+
+[#sample-historical-accounts]
+=== Linking Historical Accounts
+
+This sample demonstrates the retention of inactive (historical) LDAP accounts that have been linked to a corresponding managed user account. The sample builds on sample 2b and uses the LDAP connector to connect to an OpenDJ instance. You can use any LDAP-v3 compliant directory server.
+
+In this sample, OpenIDM is the source resource. Managed users in the OpenIDM repository maintain a list of the accounts that they have been linked to on the local LDAP server. This list is stored in the `historicalAccounts` field of the managed user entry. The list contains a reference to all past and current LDAP accounts. Each LDAP account in the list is represented as a __relationship__ and includes information about the date the accounts were linked or unlinked, and whether the account is currently active. For more information about relationship objects, see xref:../integrators-guide/chap-users-groups-roles.adoc#managing-relationships["Managing Relationships Between Objects"] in the __Integrator's Guide__.
+This sample includes the following custom scripts, in its `script` directory:
+
+* `onLink-managedUser_systemLdapAccounts.js`
++
+When a managed user object is linked to a target LDAP object, this script creates the relationship entry in the managed user's `historicalAccounts` property. The script adds two relationship properties:
++
+
+** `linkDate` — specifies the date that the link was created.
+
+** `active` — boolean true/false. When set to true, this property indicates that the target object is __currently__ linked to the managed user account.
+
+
+* `onUnlink-managedUser_systemLdapAccounts.js`
++
+When a managed user object is unlinked from a target LDAP object, this script updates that relationship entry's properties with an `unlinkDate` that specifies when the target was unlinked, and sets the `active` property to false, indicating that the target object is no longer linked.
+
+* `check_account_state_change.js`
++
+During LiveSync or reconciliation, this script checks if the LDAP account state has changed. If the state has changed, the script updates the historical account properties to indicate the new state (enabled or disabled), and the date that the state was changed. This date can only be approximated and is set to the time that the change was detected by the script.
+
+* `ldapBackCorrelationQuery.js`
++
+This script correlates entries in the LDAP directory with managed user identities in OpenIDM.
+
+
+[#ldap-config-historical-accounts]
+==== Configuring the LDAP Server
+
+This sample expects the configuration for the external LDAP server to be the same as described in xref:#external-ldap-config-2["LDAP Server Configuration"].
+
+The following steps provide setup instructions for an OpenDJ server. Adjust these instructions if you are using an alternative LDAP server.
+
+The LDIF data for this sample is provided in the file `openidm/samples/historicalaccountlinking/data/Example.ldif`. You will need to import this data during your OpenDJ setup.
+
+Although there is only one LDAP server in this example, you must enable __replication__ on that server, so that the server has an external change log. The change log is required for LiveSync between OpenDJ and OpenIDM.
+
+====
+
+. Download OpenDJ from ForgeRock's link:https://forgerock.org/downloads/[download site, window=\_top] and extract the zip archive.
+
+. Install OpenDJ, using either the UI or the command-line setup.
++
+
+* If you use the UI, make sure that you enable replication, and import the Example.ldif file during the install.
+
+* If you use the command-line setup, import the Example.ldif file during the setup, then enable replication when the server has started:
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ ./setup --cli \
+--hostname localhost \
+--ldapPort 1389 \
+--rootUserDN "cn=Directory Manager" \
+--rootUserPassword password \
+--adminConnectorPort 4444 \
+--baseDN dc=com \
+--ldifFile /path/to/openidm/samples/historicalaccountlinking/data/Example.ldif \
+--acceptLicense \
+--no-prompt
+...
+Configuring Directory Server ..... Done.
+Importing LDIF file /path/to/openidm/samples/sample2/data/Example.ldif ...... Done.
+Starting Directory Server ...... Done..
+...
+----
++
+
+[source, console]
+----
+$ bin/dsconfig create-replication-server \
+--hostname localhost \
+--port 4444 \
+-D "cn=Directory Manager" \
+-w password \
+--trustAll \
+--no-prompt \
+--provider-name "Multimaster Synchronization" \
+--set replication-port:8989 \
+--set replication-server-id:2 \
+--type generic
+$ bin/dsconfig create-replication-domain \
+--hostname localhost \
+--port 4444 \
+-D "cn=Directory Manager" \
+-w password \
+--trustAll \
+--no-prompt \
+--provider-name "Multimaster Synchronization" \
+--set base-dn:dc=example,dc=com \
+--set replication-server:localhost:8989 \
+--set server-id:3 \
+--type generic \
+--domain-name example_com
+----
+
+
+====
+
+
+[#run-sample-historical-accounts]
+==== Running the Historical Accounts Sample
+
+This section walks you through each step of the sample to demonstrate how historical accounts are stored.
+
+====
+
+. Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for the historical accounts sample:
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/historicalaccountlinking/
+Executing ./startup.sh...
+Using OPENIDM_HOME: /path/to/openidm
+Using PROJECT_HOME: /path/to/openidm/samples/historicalaccountlinking/
+Using OPENIDM_OPTS: -Xmx1024m -Xms1024m
+Using LOGGING_CONFIG: -Djava.util.logging.config.file=
+ /path/to/openidm/samples/historicalaccountlinking//conf/logging.properties
+Using boot properties at
+ /path/to/openidm/samples/historicalaccountlinking/conf/boot/boot.properties
+-> OpenIDM ready
+----
+
+. Create a user, Joe Smith, in OpenIDM.
++
+The following command creates the user over REST, and assigns the user the ID `joesmith`:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "userName": "joe.smith",
+ "givenName": "Joe",
+ "sn" : "Smith",
+ "password" : "Passw0rd",
+ "displayName" : "Joe Smith",
+ "mail" : "joe.smith@example.com",
+ "_id" : "joesmith"
+ }' \
+ "http://localhost:8080/openidm/managed/user?_action=create"
+{
+ "_id": "joesmith",
+ "_rev": "1",
+ "userName": "joe.smith",
+ "givenName": "Joe",
+ "sn": "Smith",
+ "displayName": "Joe Smith",
+ "mail": "joe.smith@example.com",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
+
+. Verify that the user Joe Smith was created in OpenDJ.
++
+Because implicit synchronization is enabled by default, any change to the managed/user repository should be propagated to OpenDJ. For more information about implicit synchronization, see xref:../integrators-guide/chap-synchronization.adoc#sync-types["Types of Synchronization"] in the __Integrator's Guide__.
++
+The following command returns all IDs in OpenDJ and shows that user joesmith was created successfully:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "uid=jdoe,ou=People,dc=example,dc=com",
+ "dn": "uid=jdoe,ou=People,dc=example,dc=com"
+ },
+ {
+ "_id": "uid=bjensen,ou=People,dc=example,dc=com",
+ "dn": "uid=bjensen,ou=People,dc=example,dc=com"
+ },
+ {
+ "_id": "uid=joe.smith0,ou=People,dc=example,dc=com",
+ "dn": "uid=joe.smith0,ou=People,dc=example,dc=com"
+ }
+ ],
+...
+}
+----
++
+Note that Joe Smith's `uid` in OpenDJ is appended with a `0`. The `onCreate` script, defined in the mapping (`sync.json`), increments the `uid` each time a new OpenDJ entry is linked to the same managed user object.
+
+. Verify that the historical account __relationship object__ that corresponds to this linked LDAP account was created in the OpenIDM repository.
++
+The following command returns all of the `historicalAccounts` for user joesmith:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/joesmith/historicalAccounts?_queryId=query-all"
+{
+ "result": [
+ {
+ "_ref": "system/ldap/account/uid=joe.smith0,ou=People,dc=example,dc=com",
+ "_refProperties": {
+ "stateLastChanged": "Mon Nov 30 2015 14:45:22 GMT-0800 (PST)",
+ "state": "enabled",
+ "active": true,
+ "linkDate": "Mon Nov 30 2015 14:45:22 GMT-0800 (PST)",
+ "_id": "ff6913ce-a252-4dc9-a060-b8b56bb32bf4",
+ "_rev": "1"
+ }
+ }
+ ],
+...
+}
+----
++
+At this stage, Joe Smith has only one historical account link — the link to `uid=joe.smith0,ou=People,dc=example,dc=com`. Note that the relationship properties (`_refProperties`) show the following information about the linked accounts:
++
+
+* The date on which the accounts were linked
+
+* The fact that this link is currently active
+
+* The state of the account in OpenDJ (`enabled`)
+
+
+. Enable the LiveSync schedule to propagate changes made in OpenDJ to the managed user repository.
++
+To start LiveSync, set `enabled` to `true` in the `conf/schedule-liveSync.json` file:
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ more samples/historicalaccountlinking/conf/schedule-liveSync.json
+{
+ "enabled" : true,
+ "type" : "cron",
+ "schedule" : "0/15 * * * * ?",
+...
+----
+
+. Use the `manage-account` command in the `opendj/bin` directory to disable Joe Smith's account in OpenDJ:
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ bin/manage-account set-account-is-disabled \
+--port 4444 \
+--bindDN "cn=Directory Manager" \
+--bindPassword password \
+--operationValue true \
+--targetDN uid=joe.smith0,ou=people,dc=example,dc=com \
+--trustAll
+Account Is Disabled: true
+----
++
+Within 15 seconds, according to the configured schedule, LiveSync should pick up the change. OpenIDM should then adjust the `state` property in Joe Smith's managed user account.
+
+. Check Joe Smith's historical accounts again, to make sure that the state of this linked account has changed:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/joesmith/historicalAccounts?_queryId=query-all"
+{
+ "result": [
+ {
+ "_ref": "system/ldap/account/uid=joe.smith0,ou=People,dc=example,dc=com",
+ "_refProperties": {
+ "stateLastChanged": "Mon Nov 30 2015 14:54:45 GMT-0800 (PST)",
+ "state": "disabled",
+ "active": true,
+ "linkDate": "Mon Nov 30 2015 14:45:22 GMT-0800 (PST)",
+ "_id": "ff6913ce-a252-4dc9-a060-b8b56bb32bf4",
+ "_rev": "2"
+ }
+ }
+ ],
+...
+}
+----
+
+. Now, deactivate Joe Smith's managed user account by setting his `accountStatus` property to inactive.
++
+You can deactivate the account over the REST interface, or by using the Admin UI.
++
+To use the Admin UI, simply select Manage > User, select Joe Smith's account and change his Status to inactive, on his Details tab.
++
+The following command deactivates Joe Smith's account over REST:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[
+ { "operation" : "replace",
+ "field" : "accountStatus",
+ "value" : "inactive" }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/joesmith"
+{
+ "_id": "joesmith",
+ "_rev": "3",
+ "userName": "joe.smith",
+ ...
+ "accountStatus": "inactive",
+ ...
+}
+----
+
+. Request Joe Smith's historical accounts again:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/joesmith/historicalAccounts?_queryId=query-all"
+{
+ "result": [
+ {
+ "_ref": "system/ldap/account/uid=joe.smith0,ou=People,dc=example,dc=com",
+ "_refProperties": {
+ "stateLastChanged": "Mon Nov 30 2015 14:54:45 GMT-0800 (PST)",
+ "state": "disabled",
+ "active": false,
+ "linkDate": "Mon Nov 30 2015 14:45:22 GMT-0800 (PST)",
+ "unlinkDate": "Mon Nov 30 2015 14:58:30 GMT-0800 (PST)",
+ "_id": "ff6913ce-a252-4dc9-a060-b8b56bb32bf4",
+ "_rev": "3"
+ }
+ }
+ ],
+...
+}
+----
+
+. Activate Joe Smith's managed user account by setting his `accountStatus` property to active. This action should create a new entry in OpenDJ (with `uid=joe.smith1`), and a new link from Joe Smith's managed user object to that OpenDJ entry.
++
+You can activate the account over the REST interface, or by using the Admin UI, as described previously.
++
+The following command activates Joe Smith's account over REST:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[
+ { "operation" : "replace",
+ "field" : "accountStatus",
+ "value" : "active" }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/joesmith"
+{
+ "_id": "joesmith",
+ "_rev": "4",
+ "userName": "joe.smith",
+ ...
+ "accountStatus": "active",
+ ...
+}
+----
+
+. Verify that a new LDAP entry for user Joe Smith was created in OpenDJ.
++
+The following command returns all IDs in OpenDJ and shows that two OpenDJ entries for Joe Smith `uid=joe.smith0` and `uid=joe.smith1`.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "uid=jdoe,ou=People,dc=example,dc=com",
+ "dn": "uid=jdoe,ou=People,dc=example,dc=com"
+ },
+ {
+ "_id": "uid=bjensen,ou=People,dc=example,dc=com",
+ "dn": "uid=bjensen,ou=People,dc=example,dc=com"
+ },
+ {
+ "_id": "uid=joe.smith0,ou=People,dc=example,dc=com",
+ "dn": "uid=joe.smith0,ou=People,dc=example,dc=com"
+ },
+ {
+ "_id": "uid=joe.smith1,ou=People,dc=example,dc=com",
+ "dn": "uid=joe.smith1,ou=People,dc=example,dc=com"
+ }
+ ],
+...
+}
+----
+
+. Request Joe Smith's historical accounts again:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/joesmith/historicalAccounts?_queryId=query-all"
+{
+ "result": [
+ {
+ "_ref": "system/ldap/account/uid=joe.smith0,ou=People,dc=example,dc=com",
+ "_refProperties": {
+ "stateLastChanged": "Mon Nov 30 2015 14:54:45 GMT-0800 (PST)",
+ "state": "disabled",
+ "active": false,
+ "linkDate": "Mon Nov 30 2015 14:45:22 GMT-0800 (PST)",
+ "unlinkDate": "Mon Nov 30 2015 14:58:30 GMT-0800 (PST)",
+ "_id": "ff6913ce-a252-4dc9-a060-b8b56bb32bf4",
+ "_rev": "3"
+ }
+ },
+ {
+ "_ref": "system/ldap/account/uid=joe.smith1,ou=People,dc=example,dc=com",
+ "_refProperties": {
+ "stateLastChanged": "Mon Nov 30 2015 15:00:00 GMT-0800 (PST)",
+ "state": "enabled",
+ "active": true,
+ "linkDate": "Mon Nov 30 2015 15:00:00 GMT-0800 (PST)",
+ "_id": "08443775-7420-4994-bf86-9b29a986bfc9",
+ "_rev": "1"
+ }
+ }
+ ],
+ ...
+}
+----
++
+Note that Joe Smith's entry now shows two OpenDJ accounts, but that only the link to `uid=joe.smith1` is `enabled` and `active`.
+
+====
+
+
+
+[#sample-multiple-passwords]
+=== Storing 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. The 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 OpenDJ instance.
+
+* Managed user objects have two additional password fields, each mapped to one of the two LDAP servers.
+
+* The two LDAP servers have different requirements for password policy and encryption.
+
+* 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 xref:../integrators-guide/chap-passwords.adoc#password-history["Creating a Password History Policy"] in the __Integrator's Guide__, 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 the following customized configuration files in the `conf` directory:
+
+* `provisioner.openicf-ldap.json` configures the first LDAP connection.
+
+* `conf/provisioner.openicf-ldap2.json` configures the second LDAP connection.
+
+* `sync.json` provides the mappings from the OpenIDM managed user repository to the respective LDAP servers.
+
+* `managed.json` contains a customized schema for managed users that includes the additional password fields.
+
+For details of the customizations to these configuration files, see the `README` provided with the sample.
+The sample includes the following customized scripts in the `script` directory:
+
+* `onCreate-onUpdate-sync.js` performs custom mapping logic. Specifically, this script maps the pre-hashed password value and sets the target object DN on create events.
+
+* `storeFields.groovy` stores the pre-hashed values of fields in the context chain, on validate events.
+
+* `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.
+
+
+[NOTE]
+====
+This sample does not support creation of new users in the Admin UI.
+====
+
+[#multiple-passwords-history-policy]
+==== Understanding the Password History Policy
+
+The sample includes a custom password history policy. Although the sample is concerned only about the history of passwords, 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 `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 that OpenIDM must keep a history of. 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` 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 or hashed 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 historical password policy. This script compares the new field value with the values in the list of historical values for each field.
++
+The `policy.json` configuration includes this script in its `additionalFiles` list, so that the policy service loads the new 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-config-multiple-passwords]
+==== Configuring the LDAP Server
+
+This sample expects the configuration for the external LDAP server to be the same as described in xref:#external-ldap-config-2["LDAP Server Configuration"].
+
+The following steps provide setup instructions for an OpenDJ server. Adjust these instructions if you are using an alternative LDAP server.
+
+The LDIF data for this sample is provided in the file `openidm/samples/multiplepasswords/data/Example.ldif`. You will need to import this data during your OpenDJ setup.
+
+====
+
+. Download OpenDJ from ForgeRock's link:https://forgerock.org/downloads/[download site, window=\_top] and extract the zip archive.
+
+. Install OpenDJ, using either the UI or the command-line setup.
++
+If you use the UI, import the Example.ldif file during the install.
++
+If you use the command-line setup, import the Example.ldif file during the setup as follows:
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ ./setup --cli \
+--hostname localhost \
+--ldapPort 1389 \
+--rootUserDN "cn=Directory Manager" \
+--rootUserPassword password \
+--adminConnectorPort 4444 \
+--baseDN dc=com \
+--ldifFile /path/to/openidm/samples/multiplepasswords/data/Example.ldif \
+--acceptLicense \
+--no-prompt
+...
+Configuring Directory Server ..... Done.
+Importing LDIF file /path/to/openidm/samples/multiplepasswords/data/Example.ldif ...... Done.
+Starting Directory Server ...... Done.
+...
+----
+
+. Run an `ldapsearch` on the LDAP directory and look at the organizational units:
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ bin/ldapsearch \
+--hostname localhost \
+--port 1389 \
+--bindDN "cn=directory manager" \
+--bindPassword password \
+--baseDN "dc=example,dc=com" \
+"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.
+
+====
+
+
+[#run-sample-multiple-passwords]
+==== Demonstrating the Use of Multiple Accounts
+
+This section starts OpenIDM 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 OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for the multiple passwords sample.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/multiplepasswords
+----
+
+. Create a user in OpenIDM. Include a main `password` field but no additional password fields. The additional password fields (`ldapPassword` and `ldap2Password`) will be populated with the value of the main `password` field as a result of the script described previously.
++
+For the purposes of this example, we will not use the Admin UI, so that the result of each command can be clearly seen. Create the user over REST, by running the following command:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PUT \
+ --data '{
+ "userName": "jdoe",
+ "givenName": "John",
+ "sn" : "Doe",
+ "displayName" : "John Doe",
+ "mail" : "john.doe@example.com",
+ "password" : "Passw0rd"
+ }' \
+ "http://localhost:8080/openidm/managed/user/jdoe"
+{
+ "code": 403,
+ "reason": "Forbidden",
+ "message": "Policy validation failed",
+ "detail": {
+ "result": false,
+ "failedPolicyRequirements": [
+ {
+ "policyRequirements": [
+ {
+ "policyRequirement": "AT_LEAST_X_CAPITAL_LETTERS",
+ "params": {
+ "numCaps": 2
+ }
+ }
+ ],
+ "property": "ldapPassword"
+ },
+ {
+ "policyRequirements": [
+ {
+ "policyRequirement": "AT_LEAST_X_NUMBERS",
+ "params": {
+ "numNums": 2
+ }
+ }
+ ],
+ "property": "ldap2Password"
+ }
+ ]
+ }
+}
+----
++
+Notice that the create request failed with a policy validation failure on the two new password fields. Although the password met the requirement for the main `password` field, the user could not be created because the password did not meet the requirements of the `ldapPassword` and `ldap2Password` fields.
++
+You can fix this problem either by updating the `password` field to one that passes both of the new requirements, or by updating the individual password fields to specifically pass their individual validation requirements.
+
+. Now, try to create user jdoe again, this time providing individual values for each of the different password fields, that comply with the three different password policies:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PUT \
+ --data '{
+ "userName": "jdoe",
+ "givenName": "John",
+ "sn" : "Doe",
+ "displayName" : "John Doe",
+ "mail" : "john.doe@example.com",
+ "password" : "Passw0rd",
+ "ldapPassword" : "PPassw0rd",
+ "ldap2Password" : "Passw00rd"
+ }' \
+ "http://localhost:8080/openidm/managed/user/jdoe"
+{
+ "_id": "jdoe",
+ "_rev": "1",
+ "userName": "jdoe",
+ "givenName": "John",
+ "sn": "Doe",
+ "displayName": "John Doe",
+ "mail": "john.doe@example.com",
+ "ldapPassword": {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "CpbVZlXAEFL/LUqWyq9Bcks/tLVwJ0pHrc/smLWf8UmC/0BDtEKRo1o2IjB6mNFz"
+ },
+ "type": "salted-hash"
+ }
+ },
+ "ldap2Password": {
+ "$crypto": {
+ "value": {
+ "iv": "TbJlRF+cSFeOguclh8AZVg==",
+ "data": "zQ250CXfR3QJ3cBKjpCQhQ==",
+ "cipher": "AES/CBC/PKCS5Padding",
+ "key": "openidm-sym-default"
+ },
+ "type": "x-simple-encryption"
+ }
+ },
+...
+}
+----
++
+The user has been created with three different passwords that comply with three distinct password policies. The passwords have been hashed or encrypted, as defined in the `managed.json` file.
+
+. 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 xref:../integrators-guide/chap-synchronization.adoc#sync-types["Types of Synchronization"] in the __Integrator's Guide__.
+
+. Query the IDs in the LDAP directory as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryId=query-all-ids"
+{
+ "result" : [ {
+ "_id" : "uid=jdoe,ou=People,dc=example,dc=com",
+ "dn" : "uid=jdoe,ou=People,dc=example,dc=com"
+ }, {
+ "_id" : "uid=jdoe,ou=Customers,dc=example,dc=com",
+ "dn" : "uid=jdoe,ou=Customers,dc=example,dc=com"
+ } ],
+...
+}
+----
++
+Note that jdoe has two entries - one in `ou=People` and one in `ou=Customers`.
+
+. Now, see if you can search each LDAP server, as user jdoe, with the separate passwords that you created for each directory.
++
+This step will indicate that the passwords were propagated correctly to the separate LDAP servers.
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ bin/ldapsearch \
+--hostname localhost \
+--port 1389 \
+--bindDN uid=jdoe,ou=People,dc=example,dc=com \
+--bindPassword PPassw0rd \
+--baseDN dc=example,dc=com \
+uid=jdoe
+dn: uid=jdoe,ou=People,dc=example,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+uid: jdoe
+mail: john.doe@example.com
+sn: Doe
+cn: John Doe
+userPassword: {SSHA}ot11NT7zidSxXEDtNE+8qQjyfIE3CDbywKGYmQ==
+givenName: John
+
+dn: uid=jdoe,ou=Customers,dc=example,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+uid: jdoe
+mail: john.doe@example.com
+sn: Doe
+cn: John Doe
+givenName: John
+$ bin/ldapsearch \
+--hostname localhost \
+--port 1389 \
+--bindDN uid=jdoe,ou=Customers,dc=example,dc=com \
+--bindPassword Passw00rd \
+--baseDN dc=example,dc=com \
+uid=jdoe
+dn: uid=jdoe,ou=People,dc=example,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+uid: jdoe
+mail: john.doe@example.com
+sn: Doe
+cn: John Doe
+userPassword: {SSHA}ot11NT7zidSxXEDtNE+8qQjyfIE3CDbywKGYmQ==
+givenName: John
+
+dn: uid=jdoe,ou=Customers,dc=example,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+uid: jdoe
+mail: john.doe@example.com
+sn: Doe
+cn: John Doe
+givenName: John
+----
+
+. Patch jdoe's managed user entry to change his `ldapPassword`.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[ {
+ "operation" : "replace",
+ "field" : "ldapPassword",
+ "value" : "TTestw0rd"
+ } ]' \
+ "http://localhost:8080/openidm/managed/user/jdoe"
+{
+ "_id": "jdoe",
+ "_rev": "2",
+ "userName": "jdoe",
+ "givenName": "John",
+ "sn": "Doe",
+ "displayName": "John Doe",
+ ...
+ "ldapPassword": {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "Vc6hvmzXaSSdG9WroqOTg3PQVdixhpg9tD/uKT610Z/H5iC6vsoOpE0/R5FaiDUg"
+ },
+ "type": "salted-hash"
+ }
+ },
+ ...
+}
+----
+
+. Search the "first" LDAP server again, as user jdoe, with this new password to verify that the password change was propagated correctly to the LDAP server.
++
+
+[source, console]
+----
+$ cd /path/to/opendj
+$ bin/ldapsearch \
+--hostname localhost \
+--port 1389 \
+--bindDN uid=jdoe,ou=People,dc=example,dc=com \
+--bindPassword TTestw0rd \
+--baseDN dc=example,dc=com \
+uid=jdoe
+dn: uid=jdoe,ou=People,dc=example,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+userPassword: {SSHA}pXV9/eZq6L30L/lGTsMV/39Dzjv/zHqIhWpLRw==
+uid: jdoe
+mail: john.doe@example.com
+sn: Doe
+givenName: John
+cn: John Doe
+
+dn: uid=jdoe,ou=Customers,dc=example,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+uid: jdoe
+mail: john.doe@example.com
+sn: Doe
+cn: John Doe
+givenName: John
+----
+
+====
+
+
+[#run-sample-multiple-passwords-history]
+==== Demonstrating the Use of the Password History Policy
+
+This section patches managed user jdoe's entry, changing his `ldapPassword` a number of times, to demonstrate the application of the password history policy.
+
+====
+
+. Send the following patch requests, changing the value of jdoe's `ldapPassword` each time:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[ {
+ "operation" : "replace",
+ "field" : "ldapPassword",
+ "value" : "TTestw0rd1"
+ } ]' \
+ "http://localhost:8080/openidm/managed/user/jdoe"
+{
+ "_id": "jdoe",
+ "_rev": "3",
+ "userName": "jdoe",
+ "givenName": "John",
+ "sn": "Doe",
+ "displayName": "John Doe",
+ "mail": "john.doe@example.com",
+...
+ "ldapPassword": {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "uFacwvr8JsiDwlfI7I/5M+q6jTmQT8e5BaNqxLRcVR+8JxA+/fqyOc8Wo0GhzIz6"
+ },
+ "type": "salted-hash"
+ }
+ },
+}
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[ {
+ "operation" : "replace",
+ "field" : "ldapPassword",
+ "value" : "TTestw0rd2"
+ } ]' \
+ "http://localhost:8080/openidm/managed/user/jdoe"
+{
+ "_id": "jdoe",
+ "_rev": "4",
+ "userName": "jdoe",
+ "givenName": "John",
+ "sn": "Doe",
+ "displayName": "John Doe",
+ ...
+ "ldapPassword": {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "kzxz6Nc38srk28xhaBLNX1DDtVsauKnDERoXyVy/TSYtEiMWd2KitgTn7498lZs0"
+ },
+ "type": "salted-hash"
+ }
+ }
+}
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[ {
+ "operation" : "replace",
+ "field" : "ldapPassword",
+ "value" : "TTestw0rd3"
+ } ]' \
+ "http://localhost:8080/openidm/managed/user/jdoe"
+{
+ "_id": "jdoe",
+ "_rev": "5",
+ "userName": "jdoe",
+ "givenName": "John",
+ "sn": "Doe",
+ "displayName": "John Doe",
+ ...
+ "ldapPassword": {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "5NEEkfSsUHFOyEHa/C6yXl9x8s3Q5yaLYJgF02Lp/hPQ8DBKmwUU0U37cqFlQLQX"
+ },
+ "type": "salted-hash"
+ }
+ }
+}
+----
++
+User jdoe now has a __history__ of `ldapPassword` values, that contains `TTestw0rd3`, `TTestw0rd2`, `TTestw0rd1`, and `TTestw0rd`, in that order. You can see the four separate hashed values in the `fieldHistory` property of the user:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/jdoe?_fields=fieldHistory"
+{
+ "_id": "jdoe",
+ "_rev": "5",
+ "fieldHistory": {
+ ...
+ "ldapPassword": [
+ {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "k1A1udvQo2gAW/5HxFFjs+IG2p34prv36UsVP89YAVv/bALQTAUJjBhml+qrlLBx"
+ },
+ "type": "salted-hash"
+ }
+ },
+ {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "LWHaTZYSUp6hP1RChZElfHmfFBQQV+FGtZuHJxsdA/j8sOvjyqeGxk+17IFCX/Ol"
+ },
+ "type": "salted-hash"
+ }
+ },
+ {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "I4nR+Kkh0sO53Sy2V7SUc6Hv3eETC9d0HWopgDTBc9FqRZCV2C9ML0kXGJk8FhfV"
+ },
+ "type": "salted-hash"
+ }
+ },
+ {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "um9iNdwU7XEVArep2X5I0wr4rRy7nacKXNuzzOc7Oa1f+lINHKwZKxaTyBwGbpX2"
+ },
+ "type": "salted-hash"
+ }
+ }
+ ]
+ }
+}
+----
+
+. 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 resets: `TTestw0rd2`:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[ {
+ "operation" : "replace",
+ "field" : "ldapPassword",
+ "value" : "TTestw0rd2"
+ } ]' \
+ "http://localhost:8080/openidm/managed/user/jdoe"
+{
+ "code": 403,
+ "reason": "Forbidden",
+ "message": "Failed policy validation",
+ "detail": {
+ "result": false,
+ "failedPolicyRequirements": [
+ {
+ "policyRequirements": [
+ {
+ "policyRequirement": "IS_NEW"
+ }
+ ],
+ "property": "ldapPassword"
+ }
+ ]
+ }
+}
+----
++
+The password reset fails the `IS_NEW` policy requirement.
+
+. Now, reset jdoe's password to a value that was not used in the previous two updates:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PATCH \
+ --data '[ {
+ "operation" : "replace",
+ "field" : "ldapPassword",
+ "value" : "TTestw0rd"
+ } ]' \
+ "http://localhost:8080/openidm/managed/user/jdoe"
+{
+ "_id": "jdoe",
+ "_rev": "5",
+ "userName": "jdoe",
+ "givenName": "John",
+ "sn": "Doe",
+ "displayName": "John Doe",
+ ...
+ "ldapPassword": {
+ "$crypto": {
+ "value": {
+ "algorithm": "SHA-256",
+ "data": "5NEEkfSsUHFOyEHa/C6yXl9x8s3Q5yaLYJgF02Lp/hPQ8DBKmwUU0U37cqFlQLQX"
+ },
+ "type": "salted-hash"
+ }
+ }
+}
+----
++
+This time, the password reset succeeds.
+
+====
+
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-multiaccount-sample.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-multiaccount-sample.adoc
new file mode 100644
index 000000000..afa2b0a6d
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-multiaccount-sample.adoc
@@ -0,0 +1,523 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-multiaccount-sample]
+== The Multi-Account Linking Sample
+
+The sample provided in the `samples/multiaccountlinking` directory illustrates how OpenIDM addresses links from multiple accounts to one identity.
+
+This sample is based on a common use case in the insurance industry, where a company (Example.com) employs agents to sell policies to their insured customers. Most of their agents are also insured. These different roles are sometimes known as the multi-account linking conundrum.
+
+With minor changes, this sample works for other use cases. For example, you may have a hospital that employs doctors who treat patients. Some of their doctors are also patients of that hospital.
+
+[#external-ldap-config-multiaccount]
+=== External LDAP Configuration
+
+Configure the LDAP server as for sample 2, (see xref:chap-ldap-samples.adoc#external-ldap-config-2["LDAP Server Configuration"]).
+
+The LDAP user must have write access to create users from OpenIDM on the LDAP server. When you configure the LDAP server, import the appropriate LDIF file, in this case, __openidm/samples/multiaccountlinking/data/Example.ldif__.
+
+
+[#install-sample-multiaccount]
+=== Install the Sample
+
+Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the following configuration for the Multi-Account Linking sample.
+
+[source, console]
+----
+$ cd /path/to/openidm
+----
+
+[source, console]
+----
+$ ./startup.sh -p samples/multiaccountlinking
+----
+
+
+[#multiaccount-create-users]
+=== Create New Identities for the Sample
+
+For the purpose of this sample, create identities for users John Doe and Barbara Jensen. To create these identities from the Admin UI, navigate to `\https://localhost:8443/admin` and click Manage > User > New User.
+
+Alternatively, use the following REST calls to set up identities for the noted users:
+
+[source, console]
+----
+$ curl \
+--cacert self-signed.crt \
+--header "Content-Type: application/json" \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request POST \
+--data '{
+ "displayName" : "Barbara Jensen",
+ "description" : "Created for OpenIDM",
+ "givenName" : "Barbara",
+ "mail" : "bjensen@example.com",
+ "telephoneNumber" : "1-360-229-7105",
+ "sn" : "Jensen",
+ "userName" : "bjensen",
+ "accountStatus" : "active"
+}' \
+"https://localhost:8443/openidm/managed/user?_action=create"
+----
+
+[source, console]
+----
+$ curl \
+--cacert self-signed.crt \
+--header "Content-Type: application/json" \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request POST \
+--data '{
+ "displayName" : "John Doe",
+ "description" : "Created for OpenIDM",
+ "givenName" : "John",
+ "mail" : "jdoe@example.com",
+ "telephoneNumber" : "1-415-599-1100",
+ "sn" : "Doe",
+ "userName" : "jdoe",
+ "accountStatus" : "active"
+}' \
+"https://localhost:8443/openidm/managed/user?_action=create"
+----
+In the output, you will see an ID number associated with each user, in the following format:
+
+[source, console]
+----
+"_id" : "35d0a49d-2571-401f-b429-96c66b23a1c0",
+----
+Record the `_id` number for each user. You will use those numbers to assign desired roles for each user.
+
+
+[#multiaccount-create-roles]
+=== Create New Roles for the Sample
+
+For this sample, to set up links for multiple accounts on OpenIDM, you need to set up roles. To do so, set up roles for `Agent` and `Insured`. To create these roles in the Admin UI, navigate to `\https://localhost:8443/admin` and click Manage > Role > New Role.
+
+Alternatively, use the following REST calls to set up the `Agent` and `Insured` roles:
+
+[source, console]
+----
+$ curl \
+--cacert self-signed.crt \
+--header "Content-Type: application/json" \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request POST \
+--data '{
+ "name" : "Agent",
+ "description" : "Role assigned to insurance agents."
+}' \
+"https://localhost:8443/openidm/managed/role?_action=create"
+----
+
+[source, console]
+----
+$ curl \
+--cacert self-signed.crt \
+--header "Content-Type: application/json" \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request POST \
+--data '{
+ "name" : "Insured",
+ "description" : "Role assigned to insured customers."
+}' \
+"https://localhost:8443/openidm/managed/role?_action=create"
+----
+Do record the `_id` output for the `Agent` and `Insured` roles. You will use those numbers to assign desired roles for each user.
+
+[NOTE]
+====
+While you could use `PUT` to create roles with descriptive names, we recommend that you use `POST` to create roles with immutable IDs.
+====
+
+
+[#multiaccount-assign-roles]
+=== Assign Roles to Appropriate Users
+
+Now you can assign roles to appropriate users. To review, user `jdoe` is an `Agent` and user `bjensen` is `Insured`.
+
+You will need the `_id` value for each user. The `_id` values shown in the following commands are random; substitute the `_id` values that you collected when creating users.
+
+The following command adds the `Agent` role to user `jdoe`, by their `_id` values:
+
+[source, console]
+----
+$ curl \
+--cacert self-signed.crt \
+--header "Content-type: application/json" \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header "If-Match: *" \
+--request PATCH \
+--data '[
+ {
+ "operation" : "add",
+ "field" : "/roles/-",
+ "value" : {
+ "_ref" : "managed/role/287dc4b1-4b19-49ec-8b4c-28a6c12ede34"
+ }
+ }
+ ]' \
+"https://localhost:8443/openidm/managed/user/8fae84ed-1f30-4542-8087-e7fa6e89541c"
+----
+And this next command adds the `Insured` role to user `bjensen`:
+
+[source, console]
+----
+$ curl \
+--cacert self-signed.crt \
+--header "Content-type: application/json" \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header "If-Match: *" \
+--request PATCH \
+--data '[
+ {
+ "operation" : "add",
+ "field" : "/roles/-",
+ "value" : {
+ "_ref" : "managed/role/bb9302c4-5fc1-462c-8be2-b17c87175d1b"
+ }
+ }
+ ]' \
+"https://localhost:8443/openidm/managed/user/d0b79f30-946f-413a-b7d1-d813034fa345"
+----
+Now assign the `Insured` role to user `jdoe`, as that user is both an insured customer and an agent:
+
+[source, console]
+----
+$ curl \
+--cacert self-signed.crt \
+--header "Content-type: application/json" \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header "If-Match: *" \
+--request PATCH \
+--data '[
+ {
+ "operation" : "add",
+ "field" : "/roles/-",
+ "value" : {
+ "_ref" : "managed/role/006935c2-b080-45cd-8347-881df42cae0c"
+ }
+ }
+ ]' \
+"https://localhost:8443/openidm/managed/user/a3335177-7366-4656-a66c-8d6e77a5786f"
+----
+User `jdoe` should now have two managed roles:
+
+[source, console]
+----
+...
+"effectiveRoles" : [ {
+ "_ref" : "managed/role/6aabe990-ec05-403e-bc5d-ff9b217ba571",
+ "_refProperties" : {
+ "_id" : "687714b8-5854-42c7-a190-c781ea5174c5",
+ "_rev" : "1"
+ }
+}, {
+ "_ref" : "managed/role/844110ce-3686-43bb-aabf-46b17a14abaa"
+} ],
+...
+----
+
+
+[#multiaccount-background]
+=== Background: Link Qualifiers, Agents, and Insured Customers
+
+This is a good moment to take a step back, to see how this sample works, based on custom options in the `sync.json` configuration file.
+
+OpenIDM defines mappings between source and target accounts in the `sync.json` file, which allows you to create a link between one source entry and multiple target entries using a concept known as a "link qualifier," which enables one-to-many relationships in mappings and policies.
+
+For more information on resource mappings and link qualifiers, see the following sections of the Integrator’s Guide:
+
+xref:../integrators-guide/chap-synchronization.adoc#synchronization-mappings-file["Mapping Source Objects to Target Objects"] in the __Integrator's Guide__.
+
+xref:../integrators-guide/chap-synchronization.adoc#linking-multiple-targets["Mapping a Single Source Object to Multiple Target Objects"] in the __Integrator's Guide__.
+
+In this sample, we use two link qualifiers:
+
+* `insured`+
+`ou=Customers,dc=example,dc=com`
+* `agent`+
+`ou=Contractors,dc=example,dc=com`
+Assume that agents and insured customers connect via two different portals. Each group gets access to different features, depending on the portal.
+
+Agents may have two different accounts; one each for professional and personal use. While the accounts are different, the identity information for each agent should be the same for both accounts.
+
+To that end, this sample sets up link qualifiers for two categories of users: `insured` and `agent`, under the `managedUser_systemLdapAccounts` mapping:
+
+[source, javascript]
+----
+{
+ "name" : "managedUser_systemLdapAccounts",
+ "source" : "managed/user",
+ "target" : "system/ldap/account",
+ "linkQualifiers" : [
+ "insured",
+ "agent"
+ ],
+ .....
+}
+----
+You can verify this in the Admin UI. Click Configure > Mappings > `managedUser_systemLdapAccounts` > Properties > Link Qualifiers. You should see `insured` and `agent` in the list of configured Link Qualifiers.
+
+In addition, this sample also includes a transformation script between an LDAP Distinguished Name (`dn`) and the two categories of users. The following excerpt of the `sync.json` file includes that script:
+
+[source, javascript]
+----
+{
+ "target" : "dn",
+ "transform" : {
+ "type" : "text/javascript",
+ "globals" : { },
+ "source" :
+ "if (linkQualifier === 'agent') {
+ 'uid=' + source.userName + ',ou=Contractors,dc=example,dc=com';
+ } else if (linkQualifier === 'insured') {
+ 'uid=' + source.userName + ',ou=Customers,dc=example,dc=com';
+ }"
+},
+----
+The following validSource script looks through the effective roles of a user, with two objectives:
+
+* `Agent`+
+`Insured`
+* `effectiveRoles`+
+`Agent`+
+`Insured`
+
+[source, javascript]
+----
+"validSource" : {
+ "type" : "text/javascript",
+ "globals" : { },
+ "source" : "var res = false;
+ var i=0;
+
+ while (!res && i < source.effectiveRoles.length) {
+ var roleId = source.effectiveRoles[i];
+ if (roleId != null && roleId.indexOf("/") != -1) {
+ var roleInfo = openidm.read(roleId);
+ logger.warn("Role Info : {}",roleInfo);
+ res = (((roleInfo.properties.name === 'Agent')
+ &&(linkQualifier ==='agent'))
+ || ((roleInfo.properties.name === 'Insured')
+ &&(linkQualifier ==='insured')));
+ }
+ i++;
+ }
+ res"
+}
+----
+You can see how correlation queries are configured in the `sync.json` file.
+
+The structure for the correlation query specifies one of two link qualifiers: `insured` or `agent`. For each link qualifier, the correlation query defines a script that verifies if the subject `dn` belongs in a specific container. For this sample, the container (`ou`) may be Customers or Contractors.
+
+You can can avoid specifying the structure of the `dn` attribute in two places in the `sync.json` file with the following code, which leverages the expression builder to reuse the construct defined in the `dn` mapping:
+
+[source, javascript]
+----
+"correlationQuery" : [
+ {
+ "linkQualifier" : "insured",
+ "expressionTree" : {
+ "all" : [
+ "dn"
+ ]
+ },
+ "mapping" : "managedUser_systemLdapAccounts",
+ "type" : "text/javascript",
+ "file" : "ui/correlateTreeToQueryFilter.js"
+ },
+ {
+ "linkQualifier" : "agent",
+ "expressionTree" : {
+ "all" : [
+ "dn"
+ ]
+ },
+ "mapping" : "managedUser_systemLdapAccounts",
+ "type" : "text/javascript",
+ "file" : "ui/correlateTreeToQueryFilter.js"
+ }
+],
+----
+You can also leverage the expression builder in the UI. Review how the UI illustrates the expression builder. To do so, click Configure > Mapping > select a mapping > Association > Association Rules. Edit either link qualifier. You will see how the expression builder is configured for this sample.
+
+
+[#multiaccount-roles-update]
+=== Update Roles With Desired LDAP Attributes
+
+This use case illustrates how accounts frequently have different functions on target systems. For example, while agents may be members of a Contractor group, insured customers may be part of a Chat Users group (possibly for access to customer service).
+
+While an `agent` may also be an insured customer, you do not want other `insured` accounts to have the same properties (or memberships) as the `agent` account. In this sample, we ensure that OpenIDM limits role based assignments to the correct account.
+
+With the following commands you will create two managed assignments which will be used by the `agent` and `insured` roles.
+
+Record the `_id` number for each user. You will use those numbers to assign desired roles for each user.
+
+The following command will create an `agent` assignment.
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "Content-Type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{
+ "name" : "ldapAgent",
+ "description" : "LDAP Agent Assignment",
+ "mapping" : "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "ldapGroups",
+ "value": [
+ "cn=Contractors,ou=Groups,dc=example,dc=com"
+ ],
+ "assignmentOperation" : "mergeWithTarget",
+ "unassignmentOperation" : "removeFromTarget"
+ }
+ ],
+ "linkQualifiers": ["agent"]
+ }' \
+"https://localhost:8443/openidm/managed/assignment?_action=create"
+----
+Now repeat the process for the `insured` assignment, with the value set to the `Chat Users` group:
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "Content-Type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{
+ "name" : "ldapCustomer",
+ "description" : "LDAP Customer Assignment",
+ "mapping" : "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "ldapGroups",
+ "value": [
+ "cn=Chat Users,ou=Groups,dc=example,dc=com"
+ ],
+ "assignmentOperation" : "mergeWithTarget",
+ "unassignmentOperation" : "removeFromTarget"
+ }
+ ],
+ "linkQualifiers": ["insured"]
+ }' \
+"https://localhost:8443/openidm/managed/assignment?_action=create"
+----
+Now you can add the created assignments to their respective roles.
+
+Add the `insured` assignment to the insured customer role:
+
+[source, console]
+----
+$ curl \
+--cacert self-signed.crt \
+--header "Content-type: application/json" \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header "If-Match: *" \
+--request PATCH \
+--data '[
+ {
+ "operation" : "add",
+ "field" : "/assignments/-",
+ "value" : {
+ "_ref" : "managed/assignment/ee5241b2-e571-4736-8fb2-6b9caa9d0554"
+ }
+ }
+ ]' \
+"https://localhost:8443/openidm/managed/role/287dc4b1-4b19-49ec-8b4c-28a6c12ede34"
+----
+Add the `agent` assignment to the `agent` role:
+
+[source, console]
+----
+$ curl \
+--cacert self-signed.crt \
+--header "Content-type: application/json" \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header "If-Match: *" \
+--request PATCH \
+--data '[
+ {
+ "operation" : "add",
+ "field" : "/assignments/-",
+ "value" : {
+ "_ref" : "managed/assignment/12927c5d-576f-491e-ba65-e228cd218947"
+ }
+ }
+ ]' \
+"https://localhost:8443/openidm/managed/role/bb9302c4-5fc1-462c-8be2-b17c87175d1b"
+----
+
+
+[#multiaccountlinking-recon]
+=== Reconciling Managed Users to the External LDAP Server
+
+Now that you have loaded `Example.ldif` into OpenDJ, and have started OpenIDM, you can perform a reconciliation from the internal Managed Users repository to the external OpenDJ data store:
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "https://localhost:8443/openidm/recon?_action=recon&mapping=managedUser_systemLdapAccounts"
+----
+With all of the preparation work that you have done, this reconciliation will create three new accounts on the external LDAP server:
+
+* `ou=Customers,dc=example,dc=com`+
+`dn`+
+`bjensen`+
+`jdoe`
+* `ou=Contractors,dc=example,dc=com`+
+`dn`+
+`jdoe`
+Congratulations, you have just created accounts in two different areas of the LDAP Directory Information Tree.
+
+
+[#multilinking-review]
+=== Reviewing the Result
+
+You have already confirmed that user `bjensen` has a `insured` role, and user `jdoe` has both a `insured` and `agent` role. You can confirm the same result in the Admin UI:
+
+.
+. `jdoe`
+. `bjensen`+
+`jdoe`
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-overview.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-overview.adoc
new file mode 100644
index 000000000..32341a64f
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-overview.adoc
@@ -0,0 +1,180 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-overview]
+== Overview of the OpenIDM Samples
+
+This chapter lists all the samples provided with OpenIDM and gives a high-level overview of the purpose of each sample. This chapter also provides information that is required for all of the samples. Read this chapter, specifically xref:#install-samples["Installing the Samples"] and xref:#preparing-openidm["Preparing OpenIDM"] before you try any of the samples provided with OpenIDM.
+
+[#samples-provided-with-openidm]
+=== Overview of the Samples Provided With OpenIDM
+
+--
+OpenIDM provides a number of samples in the `openidm/samples` directory. This section describes the purpose of each sample:
+
+xref:chap-xml-samples.adoc#chap-xml-samples["XML Samples - Reconciling Data Between OpenIDM and an XML File"]::
+The XML samples all use the XML file connector to interact with an XML file resource. The samples demonstrate the following OpenIDM functionality:
++
+
+* xref:chap-xml-samples.adoc#more-sample-1["First OpenIDM Sample - Reconciling an XML File Resource"]
++
+The basic XML sample demonstrates a connection to an XML file that holds user data. The sample includes one mapping and shows reconciliation from an XML file to the OpenIDM repository.
+
+* xref:chap-xml-samples.adoc#more-sample-8["Logging Sample - Using Scripts to Generate Log Messages"]
++
+The logging sample demonstrates the logging capabilities available to OpenIDM scripts, and provides an alternative method for debugging scripts.
+
+* xref:chap-xml-samples.adoc#more-sample-9["Workflow Sample - Demonstrating Asynchronous Reconciling Using a Workflow"]
++
+The workflow sample uses the XML connector to demonstrate asynchronous reconciliation using the workflow mechanism.
+
+
+xref:chap-ldap-samples.adoc#chap-ldap-samples["LDAP Samples - Reconciling Data Between OpenIDM and One or More LDAP Directories"]::
+The LDAP samples all assume a connection to an LDAP directory, usually OpenDJ, or Active Directory. Samples 5 and 5b simulate an LDAP directory with an XML file, and use the XML connector. These samples demonstrate a wide variety of OpenIDM functionality and are broken down as follows:
++
+
+* xref:chap-ldap-samples.adoc#more-sample-2["Sample 2 - LDAP One Way"]
++
+This sample uses the generic LDAP connector to connect to an LDAP directory. The sample includes one mapping from the LDAP directory to the managed user repository, and demonstrates reconciliation from the external resource to the repository.
+
+* xref:chap-ldap-samples.adoc#more-sample-2b["Sample 2b - LDAP Two Way"]
++
+This sample uses the generic LDAP connector to connect to an LDAP directory. The sample includes two mappings, one from the LDAP directory to the managed user repository, and one from the repository to the LDAP directory. The sample demonstrates reconciliation in both directions.
+
+* xref:chap-ldap-samples.adoc#more-sample-2c["Sample 2c - Synchronizing LDAP Group Membership"]
++
+This sample uses the generic LDAP connector to connect to an LDAP directory. The sample includes two mappings, one from the LDAP directory to the managed user repository, and one from the repository to the LDAP directory. The sample demonstrates synchronization of group membership, that is, how the value of the `ldapGroups` property in a managed user object is mapped to the corresponding user object in LDAP.
+
+* xref:chap-ldap-samples.adoc#more-sample-2d["Sample 2d - Synchronizing LDAP Groups"]
++
+This sample uses the generic LDAP connector to connect to an LDAP directory. The sample builds on the previous sample by providing an additional mapping, from the LDAP groups object, to the managed groups object. The sample illustrates a new managed object type (groups) and shows how this object type is synchronized with group containers in LDAP.
+
+* xref:chap-ldap-samples.adoc#more-sample-5["Sample 5 - Synchronization of Two LDAP Resources"]
++
+Although this sample is grouped with the LDAP samples, it actually __simulates__ two LDAP directories with XML files, and uses the XML file connector to connect the two. The purpose of this sample is to demonstrate reconciliation directly between two external resources, without the data passing through the OpenIDM repository. The sample also demonstrates the configuration of an outbound email service to send reconciliation summaries by mail.
+
+* xref:chap-ldap-samples.adoc#more-sample-5b["Sample 5b - Failure Compensation With Multiple Resources"]
++
+This sample builds on the previous sample to demonstrate a failure compensation mechanism that relies on script event hooks. The failure compensation mechanism ensures that reconciliation changes are propagated throughout a multiple-resource deployment, or rolled back in the case of error. The purpose of this mechanism is to keep the data consistent across multiple resources.
+
+* xref:chap-ldap-samples.adoc#more-sample-6["Sample 6 - LiveSync With an AD Server"]
++
+This sample illustrates the LiveSync mechanism that pushes changes from an external resource to the OpenIDM repository. The sample uses an LDAP connector to connect to an LDAP directory, either OpenDJ or Active Directory.
+
+* xref:chap-ldap-samples.adoc#sample-historical-accounts["Linking Historical Accounts"]
++
+This sample demonstrates the retention of inactive (historical) LDAP accounts that have been linked to a corresponding managed user account. The sample builds on sample 2b and uses the LDAP connector to connect to an OpenDJ instance. You can use any LDAP-v3 compliant directory server.
+
+* xref:chap-ldap-samples.adoc#sample-multiple-passwords["Storing 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. The sample includes two target LDAP servers, each with different password policy and encryption requirements. The sample also shows how to extend the password history policy to apply to multiple password fields.
+
+
+xref:chap-groovy-samples.adoc#chap-groovy-samples["Samples That Use the Groovy Connector Toolkit to Create Scripted Connectors"]::
+The samples in this section use the Groovy Connector Toolkit to create a scripted connector. Because you can use scripted Groovy connectors to connect to a large variety of systems, the samples in this section show connections to several different external resources. The samples are broken down as follows:
++
+
+* xref:chap-groovy-samples.adoc#more-sample3["Sample 3 - Using the Custom Scripted Connector Bundler to Build a ScriptedSQL Connector"]
++
+This sample uses the __custom scripted connector bundler__ to create a new scripted connector. The connector bundler generates a scripted connector, the connector configuration and the Groovy scripts required to communicate with an external MySQL database (HRDB).
+
+* xref:chap-groovy-samples.adoc#sample-scripted-rest["Sample - Using the Groovy Connector Toolkit to Connect to OpenDJ With ScriptedREST"]
++
+This sample uses the Groovy Connector Toolkit to implement a ScriptedREST connector, which interacts with the OpenDJ REST API.
+
+* xref:chap-groovy-samples.adoc#sample-scripted-crest["Using the Groovy Connector Toolkit to Connect to OpenDJ With ScriptedCREST"]
++
+This sample uses the Groovy Connector Toolkit to create a ScriptedCREST connector, which connects to an OpenDJ instance. The main difference between a CREST-based API and a generic REST API is that the CREST API is inherently recognizable by all ForgeRock products. As such, the sample can leverage CREST resources in the groovy scripts, to create CREST requests.
+
+
+xref:chap-powershell-samples.adoc#chap-powershell-samples["Samples That Use the PowerShell Connector Toolkit to Create Scripted Connectors"]::
+This sample uses the PowerShell Connector Toolkit to create a PowerShell connector, and provides a number of PowerShell scripts that enable you to perform basic CRUD (create, read, update, delete) operations on an Active Directory server. The samples use the MS Active Directory PowerShell module.
+
+xref:chap-kerberos-sample.adoc#chap-kerberos-sample["Scripted Kerberos Connector Sample"]::
+This sample demonstrates how to use the scripted Kerberos connector to manage Kerberos user principals and to reconcile user principals with OpenIDM managed user objects.
+
+xref:chap-audit-sample.adoc#chap-audit-sample["Audit Samples"]::
+This sample uses a ScriptedSQL implementation of the Groovy Connector Toolkit to direct audit information to a MySQL database.
+
+xref:chap-roles-sample.adoc#chap-roles-sample["Roles Samples - Demonstrating the OpenIDM Roles Implementation"]::
+This sample builds on xref:chap-ldap-samples.adoc#more-sample-2["Sample 2 - LDAP One Way"], and extends that sample to demonstrate how roles are implemented in OpenIDM.
+
+xref:chap-multiaccount-sample.adoc#chap-multiaccount-sample["The Multi-Account Linking Sample"]::
+This sample illustrates how OpenIDM addresses links from multiple accounts to one identity.
+
+xref:chap-trustedfilter-sample.adoc#chap-trustedfilter-sample["The Trusted Servlet Filter Sample"]::
+This sample demonstrates how to use a custom servlet filter and the Trusted Request Attribute Authentication Module in OpenIDM. Once configured, OpenIDM can use the servlet filter to authenticate through another service.
+
+xref:chap-fullstack-sample.adoc#chap-fullstack-sample["Full Stack Sample - Using OpenIDM in the ForgeRock Identity Platform"]::
+This sample demonstrates the integration of three ForgeRock products: OpenIDM, OpenDJ, and OpenAM. With this sample, you can see how you can use OpenAM for authentication, for user identities that are maintained with OpenIDM, based on a data store of users in OpenDJ.
+
+xref:chap-workflow-samples.adoc#chap-workflow-samples["Workflow Samples"]::
+The workflow sample and use cases demonstrate how OpenIDM uses workflows to provision user accounts. The samples demonstrate the use of the Self-Service UI to enable user self-registration,
++
+
+* xref:chap-workflow-samples.adoc#example-provisioning-workflow["Sample Workflow - Provisioning User Accounts"]
++
+The provisioning workflow sample demonstrates a typical use case of a workflow — provisioning new users. The sample demonstrates the use of the Admin UI, to configure user self-service and the Self-Service UI that enables users to complete their registration process.
+
+* xref:chap-workflow-samples.adoc#workflow-use-cases["Workflow Use Cases"]
++
+The workflow use cases work together to provide a complete business story, with the same set of sample data. Each of the use cases is integrated with the Self-Service UI.
+
+
+xref:chap-google-sample.adoc#chap-google-sample["Google Sample - Connecting to Google With the Google Apps Connector"]::
+This sample uses the Google Apps Connector to manage the creation of users and groups on an external Google system, using OpenIDM's REST interface.
+
+xref:chap-salesforce-sample.adoc#chap-salesforce-sample["Salesforce Sample - Salesforce With the Salesforce Connector"]::
+This sample uses the Salesforce Connector demonstrate reconciliation of user accounts from the OpenIDM repository to Salesforce, and from Salesforce to the OpenIDM repository.
+
+xref:chap-endpoint-sample.adoc#chap-endpoint-sample["Custom Endpoint Sample"]::
+OpenIDM supports scriptable custom endpoints that enable you to launch arbitrary scripts through an OpenIDM REST URI. This sample shows how custom endpoints are configured and returns a list of variables available to each method used in a custom endpoint script.
+
+--
+
+
+[#install-samples]
+=== Installing the Samples
+
+Each sample directory in `openidm/samples/` contains a number of subdirectories, such as `conf/` and `script/`. To start OpenIDM with a sample configuration, navigate to the `/path/to/openidm` directory and use the `-p` option of the `startup` command to point to the sample whose configuration you want to use. Some, but not all samples require additional software, such as an external LDAP server or database.
+
+Many of the procedures in this guide refer to paths such as `samplex/...`. In each of these cases, the complete path is assumed to be `/path/to/openidm/samples/samplex/...`.
+
+When you move from one sample to the next, bear in mind that you are changing the OpenIDM configuration. For information on how configuration changes work, see xref:../integrators-guide/chap-configuration.adoc#changing-configuration["Changing the Default Configuration"] in the __Integrator's Guide__.
+
+The command-line examples in this chapter (and throughout the OpenIDM documentation) assume a UNIX shell. If you are running these samples on Windows, adjust the command-line examples accordingly. For an indication of what the corresponding Windows command would look like, see the examples in xref:chap-xml-samples.adoc#more-sample-1["First OpenIDM Sample - Reconciling an XML File Resource"].
+
+
+[#preparing-openidm]
+=== Preparing OpenIDM
+
+Install an instance of OpenIDM specifically to try the samples. That way you can experiment as much as you like, and discard the result if you are not satisfied.
+
+If you are using the same instance of OpenIDM for multiple samples, it is helpful to clear out the repository created for an earlier sample. To do so, shut down OpenIDM and delete the `openidm/db/openidm` directory.
+
+[source, console]
+----
+$ rm -rf /path/to/openidm/db/openidm
+----
+OpenIDM should then be ready to start with a new sample. For a number of the samples in this guide, users are created either with the UI or directly with a commons REST call. Users that have been created in the repository (managed users) should be able to log into the Self-Service UI.
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-powershell-samples.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-powershell-samples.adoc
new file mode 100644
index 000000000..7bfe7fcf3
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-powershell-samples.adoc
@@ -0,0 +1,1334 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-powershell-samples]
+== Samples That Use the PowerShell Connector Toolkit to Create Scripted Connectors
+
+OpenICF provides a generic PowerShell Connector Toolkit that enables you to run PowerShell scripts on any external resource. The PowerShell Connector Toolkit is not a complete connector, in the traditional sense. Rather, it is a framework within which you must write your own PowerShell scripts to address the requirements of your Microsoft Windows ecosystem. You can use the PowerShell Connector Toolkit to create connectors that can provision any Microsoft system. This chapter describes sample connector implementations that enable you to provision to an Active Directory server instance, and to Azure Active Directory (Azure AD).
+
+The PowerShell Connector Toolkit is available, with a subscription, from the link:https://backstage.forgerock.com/[ForgeRock Backstage, window=\_blank] site.
+
+[#powershell-ad]
+=== Connect to Active Directory
+
+This sample provides a number of PowerShell scripts that enable you to perform basic CRUD (create, read, update, delete) operations on an Active Directory server. The samples use the MS Active Directory PowerShell module. For more information on this module, see the corresponding link:http://technet.microsoft.com/en-us/library/hh852274.aspx[Microsoft documentation, window=\_blank].
+
+This sample assumes that OpenIDM is running on a Windows system on the localhost. It also assumes that Active Directory and the OpenICF .NET connector server run on a remote Windows server. The PowerShell connector runs on the .NET connector server.
+
+To use this sample for OpenIDM instances installed on UNIX systems, adjust the relevant commands shown with PowerShell prompts.
+
+[#powershell-ad-setup]
+==== Setting Up the PowerShell Active Directory Sample
+
+
+====
+Run the commands in this procedure from the PowerShell command line. The continuation character used in the command is the back-tick (`).
+
+. Install, configure, and start the .NET connector server on the machine that is running an Active Directory Domain Controller or on a workstation on which the Microsoft Active Directory PowerShell module is installed.
++
+For instructions on installing the .NET connector server, see xref:../integrators-guide/chap-resource-conf.adoc#net-connector-install["Installing the .NET Connector Server"] in the __Integrator's Guide__.
+
+. Configure OpenIDM to connect to the .NET connector server.
++
+To do so, copy the remote connector provisioner file from the `openidm\samples\provisioners` directory to your project's `conf\` directory, and edit the file according to your configuration.
++
+
+[source, console]
+----
+PS C:\ cd \path\to\openidm
+PS C:\path\to\openidm cp samples\provisioners\provisioner.openicf.connectorinfoprovider.json conf
+----
++
+For instructions on editing this file, see xref:../integrators-guide/chap-resource-conf.adoc#net-connector-openidm["Configuring OpenIDM to Connect to the .NET Connector Server"] in the __Integrator's Guide__.
+
+. Download the PowerShell Connector Toolkit archive (`mspowershell-connector-1.4.2.0.zip`) from the link:https://backstage.forgerock.com/[ForgeRock Backstage, window=\_blank] site.
++
+Extract the archive and move the `MsPowerShell.Connector.dll` to the folder in which the connector server application (`connectorserver.exe`) is located.
+
+. Copy the PowerShell scripts from the `samples\powershell2AD\tools` directory, to the machine on which the connector server is installed.
++
+
+[source, console]
+----
+PS C:\path\to\openidm>dir samples\powershell2AD\tools
+Directory: C:\path\to\openidm\samples\powershell2AD\tools
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+
+ ----- 11/15/2015 01:55 PM 2813 ADAuthenticate.ps1
+ ----- 11/15/2015 01:55 PM 10019 ADCreate.ps1
+ ----- 11/15/2015 01:55 PM 2530 ADDelete.ps1
+ ----- 11/15/2015 01:55 PM 2617 ADResolveUsername.ps1
+ ----- 11/15/2015 01:55 PM 7998 ADSchema.ps1
+ ----- 11/15/2015 01:55 PM 3706 ADSearch.ps1
+ ----- 11/15/2015 01:55 PM 4827 ADSync.ps1
+ ----- 11/15/2015 01:55 PM 2075 ADTest.ps1
+ ----- 11/15/2015 01:55 PM 10044 ADUpdate.ps1
+
+PS C:\path\to\openidm>
+----
+
+. Copy the sample connector configuration for the PowerShell connector from the `samples\provisioners` directory to your project's `conf` directory.
++
+
+[source, console]
+----
+PS C:\ cd \path\to\openidm
+PS C:\ cp samples\provisioners\provisioner.openicf-adpowershell.json conf
+----
++
+The following excerpt of the sample connector configuration shows the configuration properties:
++
+
+[source]
+----
+"configurationProperties" : {
+ "AuthenticateScriptFileName" : "C:/openidm/samples/powershell2AD/tools/ADAuthenticate.ps1",
+ "CreateScriptFileName" : "C:/openidm/samples/powershell2AD/tools/ADCreate.ps1",
+ "DeleteScriptFileName" : "C:/openidm/samples/powershell2AD/tools/ADDelete.ps1",
+ "ResolveUsernameScriptFileName" : "C:/openidm/samples/powershell2AD/tools/ADResolveUsername.ps1",
+ "SchemaScriptFileName" : "C:/openidm/samples/powershell2AD/tools/ADSchema.ps1",
+ "SearchScriptFileName" : "C:/openidm/samples/powershell2AD/tools/ADSearch.ps1",
+ "SyncScriptFileName" : "C:/openidm/samples/powershell2AD/tools/ADSync.ps1",
+ "TestScriptFileName" : "C:/openidm/samples/powershell2AD/tools/ADTest.ps1",
+ "UpdateScriptFileName" : "C:/openidm/samples/powershell2AD/tools/ADUpdate.ps1",
+ "VariablesPrefix" : "Connector",
+ "QueryFilterType" : "AdPsModule",
+ "ReloadScriptOnExecution" : true,
+ "UseInterpretersPool" : true,
+ "SubstituteUidAndNameInQueryFilter" : true,
+ "UidAttributeName" : "ObjectGUID",
+ "NameAttributeName" : "DistinguishedName",
+ "PsModulesToImport" : [ "ActiveDirectory" ],
+ "Host" : "",
+ "Port" : null,
+ "Login" : "",
+ "Password" : null
+ },
+----
++
+The sample connector configuration assumes that the scripts are located in `C:/openidm/samples/powershell2AD/tools/`. If you copied your scripts to a different location, adjust your connector configuration file accordingly.
++
+Note that the OpenICF framework requires the path to use forward slash characters and not the backslash characters that you would expect in a Windows path.
++
+Add a `CustomProperties` section to your connector configuration file, and set your base context, for example:
++
+
+[source, javascript]
+----
+"CustomProperties" : [
+ "baseContext = CN=Users,DC=example,DC=com"
+]
+----
++
+The PowerShell connector parses this string value to create a key=value pair. Because C# does not have built-in JSON support, this custom parser allows you to pass in configuration values beyond what is added to the static configuration.
++
+The host, port, login and password of the machine on which Active Directory runs do not need to be specified here. By default the Active Directory cmdlets pick up the first available Domain Controller. In addition, the scripts are executed using the credentials of the .Net connector server.
++
+
+[NOTE]
+======
+The `"ReloadScriptOnExecution"` property is set to `true` in this sample configuration. This setting causes script files to be reloaded each time the script is invoked. Having script files reloaded each time is suitable for debugging purposes. However, this property should be set to `false` in production environments, as the script reloading can have a negative performance impact.
+======
++
+In addition, make sure that the value of the `"connectorHostRef"` property in the connector configuration file matches the value that you specified in the remote connector configuration file, in step 2 of this procedure. For example:
++
+
+[source, console]
+----
+"connectorHostRef" : "dotnet",
+----
+
+====
+
+
+[#powershell-ad-test]
+==== Testing the PowerShell Active Directory Sample
+
+Because you have copied all of the required configuration files into the default OpenIDM project, you can start OpenIDM with the default configuration (that is, without the `-p` option).
+
+[source, console]
+----
+PS C:\ cd \path\to\openidm
+PS C:\ .\startup.bat
+----
+When OpenIDM has started, you can test the sample by using the `curl` command-line utility. The following examples test the scripts that were provided in the `tools` directory.
+
+====
+
+. Test the connector configuration, and whether OpenIDM is able to connect to the .NET connector server with the following request.
++
+
+[source, console]
+----
+PS C:\ curl `
+ --header "X-OpenIDM-Username: openidm-admin" `
+ --header "X-OpenIDM-Password: openidm-admin" `
+ --request POST `
+ "http://localhost:8080/openidm/system?_action=test"
+[
+ {
+ "ok": true,
+ "connectorRef": {
+ "bundleVersion": "1.4.1.0",
+ "bundleName": "MsPowerShell.Connector",
+ "connectorName": "Org.ForgeRock.OpenICF.Connectors.MsPowerShell.MsPowerShellConnector"
+ },
+ "objectTypes": [
+ "__ALL__",
+ "group",
+ "account"
+ ],
+ "config": "config/provisioner.openicf/adpowershell",
+ "enabled": true,
+ "name": "adpowershell"
+ }
+]
+----
+
+. Query the users in your Active Directory with the following request:
++
+
+[source, console]
+----
+PS C:\ curl `
+ --header "X-OpenIDM-Username: openidm-admin" `
+ --header "X-OpenIDM-Password: openidm-admin" `
+ --request GET `
+ "http://localhost:8080/openidm/system/adpowershell/account?_queryId=query-all-ids"
+{
+ "remainingPagedResults": -1,
+ "pagedResultsCookie": null,
+ "resultCount": 1257,
+ "result": [
+ {
+ "_id": "7c41496a-9898-4074-a537-bed696b6be92",
+ "distinguishedName": "CN=Administrator,CN=Users,DC=example,DC=com"
+ },
+ {
+ "_id": "f2e08a5c-473f-4798-a2d5-d5cc27c862a9",
+ "distinguishedName": "CN=Guest,CN=Users,DC=example,DC=com"
+ },
+ {
+ "_id": "99de98a3-c125-48dd-a7c2-e21f1488ab06",
+ "distinguishedName": "CN=Ben Travis,CN=Users,DC=example,DC=com"
+ },
+ {
+ "_id": "0f7394cc-c66a-404f-ad6d-38dbb4b6526d",
+ "distinguishedName": "CN=Barbara Jensen,CN=Users,DC=example,DC=com"
+ },
+ {
+ "_id": "3e6fa858-ed3a-4b58-9325-1fca144eb7c7",
+ "distinguishedName": "CN=John Doe,CN=Users,DC=example,DC=com"
+ },
+ {
+ "_id": "6feef4a0-b121-43dc-be68-a96703a49aba",
+ "distinguishedName": "CN=Steven Carter,CN=Users,DC=example,DC=com"
+ },
+...
+----
+
+. To return the complete record of a specific user, include the ID of the user in the URL. The following request returns the record for Steven Carter.
++
+
+[source, console]
+----
+PS C:\ curl `
+ --header "X-OpenIDM-Username: openidm-admin" `
+ --header "X-OpenIDM-Password: openidm-admin" `
+ --request GET `
+ "http://localhost:8080/openidm/system/adpowershell/account/6feef4a0-b121-43dc-be68-a96703a49aba"
+{
+ "_id": "6feef4a0-b121-43dc-be68-a96703a49aba",
+ "postalCode": null,
+ "passwordNotRequired": false,
+ "cn": "Steven Carter",
+ "name": "Steven Carter",
+ "trustedForDelegation": false,
+ "uSNChanged": "47219",
+ "manager": null,
+ "objectGUID": "6feef4a0-b121-43dc-be68-a96703a49aba",
+ "modifyTimeStamp": "11/27/2014 3:37:16 PM",
+ "employeeNumber": null,
+ "sn": "Carter",
+ "userAccountControl": 512,
+ "passwordNeverExpires": false,
+ "displayName": "Steven Carter",
+ "initials": null,
+ "pwdLastSet": "130615726366949784",
+ "scriptPath": null,
+ "badPasswordTime": "0",
+ "employeeID": null,
+ "badPwdCount": "0",
+ "accountExpirationDate": null,
+ "userPrincipalName": "steve.carter@ad0.example.com",
+ "sAMAccountName": "steve.carter",
+ "mail": "steven.carter@example.com",
+ "logonCount": "0",
+ "cannotChangePassword": false,
+ "division": null,
+ "streetAddress": null,
+ "allowReversiblePasswordEncryption": false,
+ "description": null,
+ "whenChanged": "11/27/2014 3:37:16 PM",
+ "title": null,
+ "lastLogon": "0",
+ "company": null,
+ "homeDirectory": null,
+ "whenCreated": "6/23/2014 2:50:48 PM",
+ "givenName": "Steven",
+ "telephoneNumber": "555-2518",
+ "homeDrive": null,
+ "uSNCreated": "20912",
+ "smartcardLogonRequired": false,
+ "distinguishedName": "CN=Steven Carter,CN=Users,DC=example,DC=com",
+ "createTimeStamp": "6/23/2014 2:50:48 PM",
+ "department": null,
+ "memberOf": [
+ "CN=employees,DC=example,DC=com"
+ ],
+ "homePhone": null
+}
+----
+
+. Test whether you can authenticate as one of the users in your Active Directory. The username that you specify here can be either an ObjectGUID, UPN, sAMAccountname or CN.
++
+
+[source, console]
+----
+$ PS C:\ curl `
+ --header "X-OpenIDM-Username: openidm-admin" `
+ --header "X-OpenIDM-Password: openidm-admin" `
+ --request POST `
+ "http://localhost:8080/openidm/system/adpowershell/account?_action=authenticate&username=Steven+Carter&password=Passw0rd"
+{
+ "_id": "6feef4a0-b121-43dc-be68-a96703a49aba"
+}
+----
++
+The request returns the ObjectGUID if the authentication is successful.
+
+. You can return the complete record for a specific user, using the query filter syntax described in xref:../integrators-guide/chap-data.adoc#constructing-queries["Constructing Queries"] in the __Integrator's Guide__.
++
+The following query returns the record for the guest user.
++
+
+[source, console]
+----
+PS C:\ curl `
+ --header "X-OpenIDM-Username: openidm-admin" `
+ --header "X-OpenIDM-Password: openidm-admin" `
+ --request GET `
+ "http://localhost:8080/openidm/system/adpowershell/account?_queryFilter=cn+eq+guest"
+{
+ "remainingPagedResults": -1,
+ "pagedResultsCookie": null,
+ "resultCount": 1,
+ "result": [
+ {
+ "_id": "f2e08a5c-473f-4798-a2d5-d5cc27c862a9",
+ "postalCode": null,
+ "passwordNotRequired": true,
+ "cn": "Guest",
+ "name": "Guest",
+ "trustedForDelegation": false,
+ "uSNChanged": "8197",
+ "manager": null,
+ "objectGUID": "f2e08a5c-473f-4798-a2d5-d5cc27c862a9",
+ "modifyTimeStamp": "6/9/2014 12:35:16 PM",
+ "employeeNumber": null,
+ "userAccountControl": 66082,
+ "whenChanged": "6/9/2014 12:35:16 PM",
+ "initials": null,
+ "pwdLastSet": "0",
+ "scriptPath": null,
+ "badPasswordTime": "0",
+ "employeeID": null,
+ "badPwdCount": "0",
+ "accountExpirationDate": null,
+ "sAMAccountName": "Guest",
+ "logonCount": "0",
+ "cannotChangePassword": true,
+ "division": null,
+ "streetAddress": null,
+ "allowReversiblePasswordEncryption": false,
+ "description": "Built-in account for guest access to the computer/domain",
+ "userPrincipalName": null,
+ "title": null,
+ "lastLogon": "0",
+ "company": null,
+ "homeDirectory": null,
+ "whenCreated": "6/9/2014 12:35:16 PM",
+ "givenName": null,
+ "homeDrive": null,
+ "uSNCreated": "8197",
+ "smartcardLogonRequired": false,
+ "distinguishedName": "CN=Guest,CN=Users,DC=example,DC=com",
+ "createTimeStamp": "6/9/2014 12:35:16 PM",
+ "department": null,
+ "memberOf": [
+ "CN=Guests,CN=Builtin,DC=example,DC=com"
+ ],
+ "homePhone": null,
+ "displayName": null,
+ "passwordNeverExpires": true
+ }
+ ]
+}
+----
+
+. Test whether you are able to create a user on the Active Directory server by sending a POST request with the `create` action.
++
+The following request creates the user `Jane Doe` on the Active Directory server.
++
+
+[source, console]
+----
+PS C:\ curl `
+ --header "X-OpenIDM-Username: openidm-admin" `
+ --header "X-OpenIDM-Password: openidm-admin" `
+ --header "Content-Type: application/json" `
+ --request POST `
+ --data "{
+ \"distinguishedName\" : \"CN=Jane Doe,CN=Users,DC=example,DC=com\",
+ \"sn\" : \"Doe\",
+ \"cn\" : \"Jane Doe\",
+ \"sAMAccountName\" : \"sample\",
+ \"userPrincipalName\" : \"janedoe@example.com\",
+ \"__ENABLE__\" : true,
+ \"__PASSWORD__\" : \"Passw0rd\",
+ \"telephoneNumber\" : \"0052-611-091\"
+ }" `
+ "http://localhost:8080/openidm/system/adpowershell/account?_action=create"
+{
+ "_id": "42725210-8dce-4fdf-b0e0-393cf0377fdf",
+ "title": null,
+ "uSNCreated": "47244",
+ "pwdLastSet": "130615892934093041",
+ "cannotChangePassword": false,
+ "telephoneNumber": "0052-611-091",
+ "smartcardLogonRequired": false,
+ "badPwdCount": "0",
+ "department": null,
+ "distinguishedName": "CN=Jane Doe,CN=Users,DC=example,DC=com",
+ "badPasswordTime": "0",
+ "employeeID": null,
+ "cn": "Jane Doe",
+ "division": null,
+ "description": null,
+ "userPrincipalName": "janedoe@example.com",
+ "passwordNeverExpires": false,
+ "company": null,
+ "memberOf": [],
+ "givenName": null,
+ "streetAddress": null,
+ "sn": "Doe",
+ "initials": null,
+ "logonCount": "0",
+ "homeDirectory": null,
+ "employeeNumber": null,
+ "objectGUID": "42725210-8dce-4fdf-b0e0-393cf0377fdf",
+ "manager": null,
+ "lastLogon": "0",
+ "trustedForDelegation": false,
+ "scriptPath": null,
+ "allowReversiblePasswordEncryption": false,
+ "modifyTimeStamp": "11/27/2014 8:14:53 PM",
+ "whenCreated": "11/27/2014 8:14:52 PM",
+ "whenChanged": "11/27/2014 8:14:53 PM",
+ "accountExpirationDate": null,
+ "name": "Jane Doe",
+ "displayName": null,
+ "homeDrive": null,
+ "passwordNotRequired": false,
+ "createTimeStamp": "11/27/2014 8:14:52 PM",
+ "uSNChanged": "47248",
+ "sAMAccountName": "sample",
+ "userAccountControl": 512,
+ "homePhone": null,
+ "postalCode": null
+}
+----
+
+. Test whether you are able to update a user object on the Active Directory server by sending a PUT request with the complete object, and including the user ID in the URL.
++
+The following request updates user `Jane Doe`'s entry, including her ID in the request. The update sends the same information that was sent in the `create` request, but adds an `employeeNumber`.
++
+
+[source, console]
+----
+PS C:\ curl `
+ --header "X-OpenIDM-Username: openidm-admin" `
+ --header "X-OpenIDM-Password: openidm-admin" `
+ --header "Content-Type: application/json" `
+ --header "If-Match: *" `
+ --request PUT `
+ --data "{
+ \"distinguishedName\" : \"CN=Jane Doe,CN=Users,DC=example,DC=com\",
+ \"sn\" : \"Doe\",
+ \"cn\" : \"Jane Doe\",
+ \"sAMAccountName\" : \"sample\",
+ \"userPrincipalName\" : \"janedoe@example.com\",
+ \"__ENABLE__\" : true,
+ \"__PASSWORD__\" : \"Passw0rd\",
+ \"telephoneNumber\" : \"0052-611-091\",
+ \"employeeNumber\": \"567893\"
+ }" `
+ "http://localhost:8080/openidm/system/adpowershell/account/42725210-8dce-4fdf-b0e0-393cf0377fdf"
+{
+ "_id": "42725210-8dce-4fdf-b0e0-393cf0377fdf",
+ "title": null,
+ "uSNCreated": "47244",
+ "pwdLastSet": "130615906375709689",
+ "cannotChangePassword": false,
+ "telephoneNumber": "0052-611-091",
+ "smartcardLogonRequired": false,
+ "badPwdCount": "0",
+ "department": null,
+ "distinguishedName": "CN=Jane Doe,CN=Users,DC=example,DC=com",
+ "badPasswordTime": "0",
+ "employeeID": null,
+ "cn": "Jane Doe",
+ "division": null,
+ "description": null,
+ "userPrincipalName": "janedoe@example.com",
+ "passwordNeverExpires": false,
+ "company": null,
+ "memberOf": [],
+ "givenName": null,
+ "streetAddress": null,
+ "sn": "Doe",
+ "initials": null,
+ "logonCount": "0",
+ "homeDirectory": null,
+ "employeeNumber": "567893",
+ "objectGUID": "42725210-8dce-4fdf-b0e0-393cf0377fdf",
+ "manager": null,
+ "lastLogon": "0",
+ "trustedForDelegation": false,
+ "scriptPath": null,
+ "allowReversiblePasswordEncryption": false,
+ "modifyTimeStamp": "11/27/2014 8:37:17 PM",
+ "whenCreated": "11/27/2014 8:14:52 PM",
+ "whenChanged": "11/27/2014 8:37:17 PM",
+ "accountExpirationDate": null,
+ "name": "Jane Doe",
+ "displayName": null,
+ "homeDrive": null,
+ "passwordNotRequired": false,
+ "createTimeStamp": "11/27/2014 8:14:52 PM",
+ "uSNChanged": "47253",
+ "sAMAccountName": "sample",
+ "userAccountControl": 512,
+ "homePhone": null,
+ "postalCode": null
+}
+----
+
+. Test whether you are able to delete a user object on the Active Directory server by sending a DELETE request with the user ID in the URL.
++
+The following request deletes user `Jane Doe`'s entry.
++
+
+[source, console]
+----
+PS C:\ curl `
+ --header "X-OpenIDM-Username: openidm-admin" `
+ --header "X-OpenIDM-Password: openidm-admin" `
+ --request DELETE `
+ "http://localhost:8080/openidm/system/adpowershell/account/42725210-8dce-4fdf-b0e0-393cf0377fdf"
+----
++
+The response includes the complete user object that was deleted.
++
+You can you attempt to query the user object to confirm that it has been deleted.
++
+
+[source, console]
+----
+PS C:\ curl `
+ --header "X-OpenIDM-Username: openidm-admin" `
+ --header "X-OpenIDM-Password: openidm-admin" `
+ --request GET `
+ "http://localhost:8080/openidm/system/adpowershell/account/42725210-8dce-4fdf-b0e0-393cf0377fdf"
+{
+ "message": "",
+ "reason": "Not Found",
+ "code": 404
+}
+----
+
+====
+
+
+
+[#powershell-azure]
+=== Connect to Azure AD
+
+This sample uses the Microsoft Azure Active Directory (Azure AD) PowerShell module. For more information about this module, see link:https://msdn.microsoft.com/en-us/library/jj151815.aspx[https://msdn.microsoft.com/en-us/library/jj151815.aspx, window=\_blank].
+
+The sample assumes that OpenIDM runs on a local UNIX/Linux machine and that the PowerShell Connector Toolkit (and the OpenICF .NET connector server) run on a remote Windows host with access to an instance of AzureAD. Adjust the command-line examples if your OpenIDM instance runs on Windows.
+
+This sample demonstrates how you can synchronize managed object data such as users and groups with a Microsoft AzureAD deployment.
+
+[NOTE]
+====
+This sample utilizes a connection between three systems: OpenIDM on UNIX/Linux, an OpenICF .NET connector server on Windows, and Azure AD "in the cloud". Internet connection times vary widely and performance is based on responses to external calls, including potential timeouts, if a command doesn't perform the first time, try again. OpenIDM’s synchronization and reconciliation performance will be fully dependent on the performance of the managed resource.
+====
+
+[#powershell-azure-before-you-start]
+==== Before You Start
+
+Before you can run this sample, you need to meet several prerequisites. This section describes each of the prerequisites, and how to install or test them.
+
+* You must have a Microsoft account, which gives you access to Microsoft Azure.
++
+You can set up a Microsoft account at link:https://signup.live.com/[https://signup.live.com/, window=\_blank].
++
+With a Microsoft account, you can access the Azure portal at link:http://azure.microsoft.com[http://azure.microsoft.com, window=\_blank].
+
+* You must have an Azure AD cloud directory.
++
+If you do not have an existing Azure AD cloud, set one up as follows:
++
+
+. Navigate to link:https://account.windowsazure.com/signup[https://account.windowsazure.com/signup, window=\_blank]. Once you log in with your Microsoft credentials, fill in the prompts and Microsoft will create an Azure subscription.
+
+. Navigate to link:http://portal.azure.com[http://portal.azure.com, window=\_blank], log in with your Microsoft account.
+
+. In the Microsoft Azure screen, select New on the left hand menu.
+
+. From the New list, select Security + Identity > Active Directory.
+
+. Complete the Add Directory form with the details of your directory, and select the check mark at the bottom of the form to submit.
++
+
+image::images/ps-azure-add-directory.png[]
++
+Your directory should now be created and listed.
+
+
+* Apart from your default Microsoft Azure account, you must have an __administrative user account__ for your Azure AD.
++
+By default your directory will have a single identity, your Microsoft Azure account. You cannot use this account to run the PowerShell Connector scripts that administer the Azure AD.
++
+If your Azure AD does not already include other administrative accounts, create a local administrative identity that is native to your directory as follows:
++
+
+. Log in to https://portal.azure.com/ with your Microsoft Azure credentials.
+
+. From the left-hand menu, select Browse > Active Directory.
+
+. Select your cloud directory from the left-hand menu and select USERS in the top navigation bar.
+
+. At the bottom of the page select Add User and enter the details of the new administrative user.
++
+
+image::images/ps-azure-add-user.png[]
++
+Select the arrow to continue.
+
+. On the User Profile screen, enter the details of this administrative user. Make sure that the user's Role is at least User Admin.
++
+Select the arrow to continue.
+
+. On the final screen, select Create and note the temporary password that is assigned to the user.
++
+
+image::images/ps-azure-user-pwd.png[]
++
+Because new administrative users are forced to change their password on first login, you should log in as this user to change the password.
++
+Select the check mark to complete the new user creation process.
+
+. Select the username at the top right of the screen and select Sign-out to sign out of your Microsoft Azure account, then select SIGN IN > Use Another Account to sign in as your new administrative user.
+
+. Enter the email address of the new administrative user and select Continue.
+
+. Enter the temporary password that you received and select Sign In.
+
+. On the Update Your Password screen, enter a new password, then select Update password and sign in.
++
+You now have a new administrative user account that the PowerShell scripts will use to access your Azure AD.
+
+
+* The Windows Azure AD Module for Windows PowerShell must be installed on the Windows host that connects to Azure.
++
+If needed, install the Azure AD Module as described in the following link:https://msdn.microsoft.com/library/azure/jj151815.aspx[Microsoft article, window=\_blank].
+
+* Your Windows host must be able to contact your Azure AD deployment.
++
+Verify the connection as follows:
++
+
+. Open a PowerShell window and type `Connect-Msolservice` at the command prompt.
+
+. On the Enter Credentials screen, enter the credentials of the administrative account that you created for the Azure directory.
++
+
+image::images/ps-azure-credentials.png[]
++
+If the PowerShell command returns with no error, you have successfully connected to your remote Azure AD deployment.
+
+
+* The OpenICF .NET connector server must be installed on your Windows host.
++
+If you have not yet installed the .NET connector server, follow the instructions in xref:../integrators-guide/index.adoc["Installing and Configuring a .NET Connector Server"] in the __Integrator's Guide__. The connector server must be running in legacy mode (see xref:../integrators-guide/index.adoc["Running the .NET Connector Server in Legacy Mode"] in the __Integrator's Guide__.
+
+* The PowerShell Connector Toolkit must be installed on your Windows host.
++
+If you have not yet installed the PowerShell Connector Toolkit, follow the instructions in xref:../connectors-guide/chap-powershell.adoc#chap-powershell["PowerShell Connector Toolkit"] in the __Connectors Guide__. In these instructions, you will use a command with a `/setkey` option to create a password key for your .NET connector server. You will use that key in xref:#powershell-azure-setup-sample["Setting Up the PowerShell Azure AD Sample on OpenIDM"].
++
+
+[IMPORTANT]
+====
+Before you continue, check that the OpenICF .NET connector server is still running. If it is not running, restart the connector server and check the logs. In some cases, Windows blocks the PowerShell connector dll. If the connector server fails to start, right-click on `MsPowerShell.Connector.dll` and select Properties > Security. If you see the following text on that tab:
+
+[source, console]
+----
+This file came from another computer and might be blocked to help protect
+ this computer.
+----
+Select the Unblock button to unblock the connector dll. Then restart the connector server.
+====
+
+When all of the above elements are in place, you can proceed with running the sample, as described in xref:#powershell-azure-setup-sample["Setting Up the PowerShell Azure AD Sample on OpenIDM"].
+
+
+[#powershell-azure-setup-sample]
+==== Setting Up the PowerShell Azure AD Sample on OpenIDM
+
+This section assumes that OpenIDM is installed on the local UNIX/Linux machine.
+
+====
+
+. On the Windows host, create a directory for the PowerShell scripts.
++
+The sample connector configuration expects the scripts in the directory `C:/openidm/samples/powershell2AzureAD/tools/`. If you put them in a different location, adjust your connector configuration accordingly.
++
+
+[source, console]
+----
+PS C:\> mkdir -Path openidm\samples\powershell2AzureAD\azureADScripts
+
+ Directory: C:\openidm\samples\powershell2AzureAD
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+d---- 5/4/2016 11:26 AM azureADScripts
+
+PS C:\>
+----
+
+. Copy the PowerShell sample scripts from the OpenIDM instance on your UNIX/Linux host to the new directory on the remote Windows server.
++
+One way to do this is to run an `scp` client, such as `pscp` in your Windows terminal. The following command copies the PowerShell scripts from the OpenIDM installation to the Windows machine:
++
+
+[source, console]
+----
+PS C:\> cd openidm\samples\powershell2AzureAD\tools
+PS C:\> pscp -r username@openidm-host:path/to/openidm/samples/powershell2AzureAD/azureADScripts/*.ps .
+----
++
+The following scripts should now be in the `azureADScripts` directory on your Windows system:
++
+
+[source, console]
+----
+PS C:\openidm\samples\powershell2AzureAD\azureADScripts> ls
+
+ Directory: C:\openidm\samples\powershell2AzureAD\azureADScripts
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+-a--- 5/4/2016 11:26 AM 7258 AzureADCreate.ps1
+-a--- 5/4/2016 11:26 AM 3208 AzureADDelete.ps1
+-a--- 5/4/2016 11:26 AM 6952 AzureADSchema.ps1
+-a--- 5/4/2016 11:26 AM 8149 AzureADSearch.ps1
+-a--- 5/4/2016 11:26 AM 2465 AzureADTest.ps1
+-a--- 5/4/2016 11:26 AM 10840 AzureADUpdate.ps1
+----
++
+
+[NOTE]
+======
+You need to set the execution policy, as Windows by default does not trust downloaded scripts. For more information, see the following article: link:https://technet.microsoft.com/en-us/library/ee176961.aspx[Using the Set-ExecutionPolicy Cmdlet, window=\_blank]
+You can then run the `Unblock-File` cmdlet to allow OpenIDM to run the scripts on your Windows system. For more information, see the following article: link:https://technet.microsoft.com/en-us/library/hh849924.aspx[Unblock-File, window=\_blank].
+======
+
+. On the Linux/UNIX machine on which OpenIDM is installed, navigate to the `path/to/openidm/samples/powershell2AzureAD` directory, and open the `provisioner.openicf.connectorinfoprovider.json conf` file.
+
+. Edit the remote connector server configuration file to match the settings of the remote .NET connector server.
++
+Change the port to `8760`, and the password (`key`) that you configured for the .NET connector server.
++
+The following example assumes that the .NET connector server is running on the host `198.51.100.1`, listening on the default port, and configured with a secret key of `Passw0rd`:
++
+
+[source]
+----
+{
+ "remoteConnectorServers" :
+ [
+ {
+ "name" : "dotnet",
+ "host" : "198.51.100.1",
+ "port" : 8760,
+ "useSSL" : false,
+ "timeout" : 0,
+ "key" : "Passw0rd"
+ }
+ ]
+}
+----
+
+. Open the sample Azure AD PowerShell connector configuration file, `provisioner.openicf-azureadpowershell.json`, and edit it to match your deployment. In particular, set the following properties in that file:
++
+
+[source]
+----
+"Host" : "198.51.100.1",
+"Port" : 8760,
+"Login" : "admin@example.onmicrosoft.com",
+"Password" : "Passw0rd",
+----
++
+--
+
+`Host`::
+The hostname or IP address on which the .NET connector server is running.
+
+`Port`::
+The port on which the .NET connector server is listening (`8760` by default in legacy mode).
+
+`Login`::
+The username of the administrative account you created for the Azure directory in the previous section.
+
+`Password`::
+The password of the administrative account you created for the Azure directory in the previous section.
+
+--
++
+If you have placed the PowerShell scripts in a directory other than the default (`C:\openidm\samples\powershell2AzureAD\azureADScripts`) you must also update those paths in the PowerShell connector configuration file.
+
+. Start OpenIDM with the PowerShell AzureAD sample configuration:
++
+
+[source, console]
+----
+$ cd path/to/openidm
+$ ./startup.sh -p samples/powershell2AzureAD
+----
+
+====
+
+
+[#powershell-azure-run-sample]
+==== Managing Users and Groups with the PowerShell Azure AD Sample
+
+This section walks you through several REST commands that enable you to test the connector configuration, and perform basic CRUD operations in your Azure AD, through the PowerShell connector.
+
+====
+
+. Test that the connector has been configured correctly and that the Azure AD resource can be reached:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/system/azureadpowershell?_action=test"
+{
+ "name": "azureadpowershell",
+ "enabled": true,
+ "config": "config/provisioner.openicf/azureadpowershell",
+ "objectTypes": [
+ "__ALL__",
+ "account",
+ "group"
+ ],
+ "connectorRef": {
+ "bundleName": "MsPowerShell.Connector",
+ "connectorName": "Org.ForgeRock.OpenICF.Connectors.MsPowerShell.MsPowerShellConnector",
+ "bundleVersion": "1.4.2.0"
+ },
+ "displayName": "PowerShell Connector ",
+ "ok": true
+}
+----
++
+If you see no response from this connector test, review any changes that you made to the `provisioner-openicf*` files in your project's `conf/` subdirectory. If you've made changes appropriate for your deployment, wait a couple of minutes and try again.
+
+. Query the IDs of the existing users in your Azure AD deployment:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/azureadpowershell/account?_queryId=query-all-ids"
+{
+ "result": [ {
+ "_id": "51560d42-e60e-49a8-855b-42b6eca35ca6",
+ "UserPrincipalName": "admin@example.onmicrosoft.com"
+ },
+ {
+ "_id": "5e63b42f-c93a-466f-af86-f0a8d00f2491",
+ "UserPrincipalName": "scarter@example.onmicrosoft.com"
+ } ],
+...
+}
+----
+
+. Use a query filter to return all details of all existing users in your Azure AD:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/azureadpowershell/account?_queryFilter=true"
+{
+ "result": [
+ {
+ "_id": "51560d42-e60e-49a8-855b-42b6eca35ca6",
+ "LiveId": "10033FFF96C5186D",
+ "FirstName": "Barbara",
+ "LastName": "Jensen",
+ "UserPrincipalName": "admin@example.onmicrosoft.com",
+ "AlternateEmailAddress" : [ "bjensen@example.com" ],
+ "LastPasswordChangeTimestamp": "3/15/2016 11:02:19 AM",
+ "DisplayName": "Barbara Jensen",
+ "PasswordNeverExpires": false,
+ "MobilePhone" : "+1 3602297105"
+ },
+ {
+ "_id": "5e63b42f-c93a-466f-af86-f0a8d00f2491",
+ "LiveId": "1003BFFD96A4CFBA",
+ "FirstName": "Sam",
+ "LastName": "Carter",
+ "UserPrincipalName": "scarter@example.onmicrosoft.com"
+ "AlternateEmailAddresses": [ "scarter@example.com" ],
+ "LastPasswordChangeTimestamp": "3/7/2016 1:09:31 PM",
+ "DisplayName": "Sam Carter",
+ "PasswordNeverExpires": false,
+ "MobilePhone" : "+1 3602297105"
+ }
+ ],
+...}
+----
+
+. Return details for a specific user account, by its `_id`
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/azureadpowershell/account/51560d42-e60e-49a8-855b-42b6eca35ca6"
+----
+
+. Create a new user in Azure AD. Substitute the domain for your Azure AD deployment for `example.onmicrosoft.com`:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --header "content-type: application/json" \
+ --data '{
+ "PasswordNeverExpires": false,
+ "AlternateEmailAddresses": ["John.Bull@example.com"],
+ "LastName": "Bull",
+ "PreferredLanguage": "en-GB",
+ "FirstName": "John",
+ "UserPrincipalName": "Dev_John.Bull@example.onmicrosoft.com",
+ "DisplayName": "John Bull"
+ }' \
+ "http://localhost:8080/openidm/system/azureadpowershell/account?_action=create"
+{
+ "_id" : "d4aac947-2037-4f29-b0f5-d404fd99938c",
+ "LiveId" : "10037FFE979FB2C1",
+ "FirstName" : "John",
+ "LastName" : "Bull",
+ "UserPrincipalName" : "Dev_John.Bull@example.onmicrosoft.com",
+ "AlternateEmailAddresses" : [ "John.Bull@example.com" ],
+ "LastPasswordChangeTimestamp" : "5/5/2016 3:52:43 PM",
+ "DisplayName" : "John Bull",
+ "PasswordNeverExpires" : false,
+ "PreferredLanguage" : "en-GB"
+}
+----
++
+Rerun the same command. You should see the following error:
++
+
+[source, console]
+----
+{
+ "code" : 500,
+ "reason" : "Internal Server Error",
+ "message" : "Operation CREATE failed with ConnectorException on system object:
+ Dev_John.Bull@example.onmicrosoft.com"
+}
+----
+
+. Update the user entry that you have just created with a patch request. Include the `_id` of the new user in the URL. Save that `_id` value for a later step.
++
+The following example updates the user's display name:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "if-match: *" \
+ --header "content-type: application/json" \
+ --request PATCH \
+ --data '[
+ {
+ "operation": "replace",
+ "field": "DisplayName",
+ "value": "John P. Bull"
+ }
+ ]' \
+ "http://localhost:8080/openidm/system/azureadpowershell/account/d4aac947-2037-4f29-b0f5-d404fd99938c"
+{
+ "_id" : "d4aac947-2037-4f29-b0f5-d404fd99938c",
+ "LiveId" : "10037FFE979FB2C1",
+ "FirstName" : "John",
+ "LastName" : "Bull",
+ "UserPrincipalName" : "Dev_John.Bull@mikejangfr.onmicrosoft.com",
+ "AlternateEmailAddresses" : [ "John.Bull@example.com" ],
+ "LastPasswordChangeTimestamp" : "5/5/2016 3:52:43 PM",
+ "DisplayName" : "John P. Bull",
+ "PasswordNeverExpires" : false,
+ "PreferredLanguage" : "en-GB"
+}
+----
+
+. Now create a group:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header 'content-type: application/json' \
+--request POST \
+--data '{
+ "DisplayName" : "Dev Testers group",
+ "Description" : "Description of a Dev Group"
+}' \
+'http://localhost:8080/openidm/system/azureadpowershell/group?_action=create'
+ {
+ "_id" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd",
+ "Members" : [ ],
+ "DisplayName" : "Dev Testers Group",
+ "GroupType" : "Security",
+ "Description" : "Description of a Dev Group",
+ "objectId" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd"
+}
+----
+
+. Add your recently created user to this new group. Use the `_id` of that user, as the `ObjectId`. Use the `_id` of the newly created group in the endpoint:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header "Content-Type: application/json" \
+--header "If-Match: *" \
+--request PUT \
+--data '{
+ "Members" : [
+ {
+ "ObjectId" : "d4aac947-2037-4f29-b0f5-d404fd99938c"
+ }
+ ]
+}' \
+"http://localhost:8080/openidm/system/azureadpowershell/group/9091be74-f37e-408d-9198-2d2b5f4b4cdd"
+ {
+ "_id" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd",
+ "Members" : [ {
+ "ObjectId" : "d4aac947-2037-4f29-b0f5-d404fd99938c",
+ "DisplayName" : "John P. Bull",
+ "GroupMemberType" : "User",
+ "EmailAddress" : "Dev_John.Bull@example.onmicrosoft.com"
+ } ],
+ "DisplayName" : "Testing Devs Group",
+ "GroupType" : "Security",
+ "Description" : "Description of a Dev Group",
+ "objectId" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd"
+}
+----
+
+. Confirm the result, by the `_id` of the group:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request GET \
+"http://localhost:8080/openidm/system/azureadpowershell/group/9091be74-f37e-408d-9198-2d2b5f4b4cdd"
+----
+
+. Update a label for the group. Use the same group `_id`:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header "Content-Type: application/json" \
+--header "If-Match: *" \
+--request PUT \
+--data '{
+ "_id" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd",
+ "Description" : "Dev Masters Group",
+ "Members" : [
+ {
+ "ObjectId" : "d4aac947-2037-4f29-b0f5-d404fd99938c",
+ "DisplayName" : "John P. Bull",
+ "GroupMemberType" : "User",
+ "EmailAddress" : "Dev_John.Bull@example.onmicrosoft.com"
+ }
+ ],
+ "DisplayName" : "Testing Devs Group",
+ "GroupType" : "Security",
+ "objectId" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd"
+}' \
+"http://localhost:8080/openidm/system/azureadpowershell/group/9091be74-f37e-408d-9198-2d2b5f4b4cdd"
+----
++
+You should see the new `Description` in the output.
+
+. Remove the user from the new group. Use the same group `_id` Note how the `Members` in the `--data` block, and the output, are blank:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--header "Content-Type: application/json" \
+--header "If-Match: *" \
+--request PUT \
+--data '{
+ "_id" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd",
+ "Description" : "Dev Masters Group",
+ "Members" : [ ],
+ "DisplayName" : "Testing Devs Group",
+ "GroupType" : "Security",
+ "objectId" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd"
+}' \
+"http://localhost:8080/openidm/system/azureadpowershell/group/9091be74-f37e-408d-9198-2d2b5f4b4cdd"
+ {
+ "_id" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd",
+ "Members" : [ ],
+ "DisplayName" : "Testing Devs Group",
+ "GroupType" : "Security",
+ "Description" : "Dev Masters Group",
+ "objectId" : "9091be74-f37e-408d-9198-2d2b5f4b4cdd"
+}
+----
+
+. Delete the user that you created earlier:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request DELETE \
+"http://localhost:8080/openidm/system/azureadpowershell/account/d4aac947-2037-4f29-b0f5-d404fd99938c"
+----
++
+To verify that the user was deleted, run the REST call to `query-all-ids` shown earlier in this section. The ID associated with that user should have been removed.
+
+====
+
+
+[#azure-sample-recon]
+==== Reconciling Users Between OpenIDM and Azure AD
+
+In this section, you'll run commands that demonstrate reconciliation mappings between OpenIDM managed users and your remote instance of Azure AD.
+
+In preparation, create a new user on the Azure AD system:
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request POST \
+--header "content-type: application/json" \
+--data '{
+ "UserPrincipalName": "CEO@example.onmicrosoft.com",
+ "LastName": "Officer",
+ "FirstName": "Chief",
+ "DisplayName": "Chief Executive Officer",
+ "PasswordNeverExpires": false
+}' \
+"http://localhost:8080/openidm/system/azureadpowershell/account?_action=create"
+----
+
+====
+In the steps that follow, you'll run reconciliations to see what happens to that user in the OpenIDM data store.
+
+. Review the list of current managed users in the OpenIDM repository, filtered for the `userName` that starts with (`sw`) CEO:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request GET \
+"http://localhost:8080/openidm/managed/user?_queryFilter=userName+sw+'CEO'"
+----
++
+Until you reconcile the Azure AD repository to OpenIDM, the output should be empty:
++
+
+[source, console]
+----
+{
+ "result" : [ ],
+ "resultCount" : 0,
+ "pagedResultsCookie" : null,
+ "totalPagedResultsPolicy" : "NONE",
+ "totalPagedResults" : -1,
+ "remainingPagedResults" : -1
+}
+----
+
+. Run a reconciliation from Azure AD to OpenIDM:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request POST \
+"http://localhost:8080/openidm/recon?_action=recon&mapping=systemAzureadpowershellAccount_managedUser&waitForCompletion=true"
+
+ {
+ "_id" : "71811f1c-2ec0-47ae-ba47-d62c7094201b-1105",
+ "state" : "SUCCESS"
+}
+----
+
+. Now rerun the command to list of current managed users in the OpenIDM repository, filtered for the `userName` that starts with (`sw`) CEO:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request GET \
+"http://localhost:8080/openidm/managed/user?_queryFilter=userName+sw+'CEO'"
+ {
+ "result" : [ {
+ "_id" : "3a012a60-19c2-4fb4-99cc-0bb82dc4588c",
+ "_rev" : "1",
+ "userName" : "CEO@example.onmicrosoft.com",
+ "mail" : "CEO@example.onmicrosoft.com",
+ "sn" : "Officer",
+ "givenName" : "Chief",
+ "accountStatus" : "active",
+ "effectiveRoles" : [ ],
+ "effectiveAssignments" : [ ]
+ } ],
+ "resultCount" : 1,
+ "pagedResultsCookie" : null,
+ "totalPagedResultsPolicy" : "NONE",
+ "totalPagedResults" : -1,
+ "remainingPagedResults" : -1
+}
+----
++
+
+. Delete that CEO user from the Azure AD system, by the `_id` shown earlier when you searched that system:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request DELETE \
+"http://localhost:8080/openidm/system/azureadpowershell/account/3a012a60-19c2-4fb4-99cc-0bb82dc4588c"
+----
++
+If successful, you'll see the data for the CEO user one last time.
+
+. Run a second reconciliation from the remote Azure AD repository to OpenIDM:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request POST \
+"http://localhost:8080/openidm/recon?_action=recon&mapping=systemAzureadpowershellAccount_managedUser&waitForCompletion=true"
+----
+
+. Rerun the command to search the OpenIDM repository for a `userName` that starts with 'CEO' one more time, to confirm that user has been reconciled out of the OpenIDM repository:
++
+
+[source, console]
+----
+$ curl \
+--header "X-OpenIDM-Username: openidm-admin" \
+--header "X-OpenIDM-Password: openidm-admin" \
+--request GET \
+"http://localhost:8080/openidm/managed/user?_queryFilter=userName+sw+'CEO'"
+----
+
+====
+
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-roles-sample.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-roles-sample.adoc
new file mode 100644
index 000000000..34be354e5
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-roles-sample.adoc
@@ -0,0 +1,2099 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-roles-sample]
+== Roles Samples - Demonstrating the OpenIDM Roles Implementation
+
+This chapter illustrates how roles are managed in OpenIDM, and how they can be used to provision to external systems, based on certain criteria.
+OpenIDM supports two types of roles:
+
+* __Provisioning roles__ - used to specify how objects are provisioned to an external system.
+
+* __Authorization roles__ - used to specify the authorization rights of a managed object internally, within OpenIDM.
+
+Both provisioning roles and authorization roles use the relationships mechanism to link the role object, and the managed object to which the role applies. For more information about relationships between objects, see xref:../integrators-guide/chap-users-groups-roles.adoc#managing-relationships["Managing Relationships Between Objects"] in the __Integrator's Guide__.
+There are three samples in this chapter:
+
+* The __provisioning__ sample (in `openidm/samples/roles/provrole`) demonstrates the operations that can be performed over the REST interface, or by using the Admin UI, to manage roles in OpenIDM.
+
+* The __crudops__ sample (in `openidm/samples/roles/crudops`) demonstrates how attributes are provisioned to an external system (an LDAP directory), based on role membership.
+
+* The __temporalConstraints__ sample (in `openidm/samples/roles/temporalConstraints`) builds on the other two roles samples and demonstrates how to apply time-based restrictions to role grants.
+
+The separate samples are described in the sections that follow.
+
+[IMPORTANT]
+====
+Most of the commands in this sample can be run by using the command-line but it is generally much easier to use the Admin UI. In some cases, the command-line version makes it easier to explain what is happening in OpenIDM. Therefore, in all steps, the sample first shows the command-line version, and then provides the equivalent method of running the command in the Admin UI.
+====
+
+[#sample-roles-crudops]
+=== CRUDOPS Role Sample - Working With Managed Roles
+
+This sample demonstrates how to manage roles by using the REST interface, or the Admin UI. The sample covers the tasks discussed in the following sections:
+
+* xref:#crudops-create-role["Creating a Managed Role"]
+
+* xref:#crudops-read-role["Reading and Searching Managed Roles"]
+
+* xref:#crudops-assign-role["Granting a Managed Role to a User"]
+
+* xref:#crudops-remove-assignment["Removing a Managed User's Roles"]
+
+* xref:#crudops-delete-role["Deleting a Managed Role"]
+
+
+[#roles-before-you-start]
+==== Before You Start
+
+This sample does not include its own configuration. Before you work through the sample, start OpenIDM with the default configuration, as follows:
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh
+Executing ./startup.sh...
+Using OPENIDM_HOME: /path/to/openidm
+Using PROJECT_HOME: /path/to/openidm
+Using OPENIDM_OPTS: -Xmx1024m -Xms1024m
+Using LOGGING_CONFIG: -Djava.util.logging.config.file=/path/to/openidm/conf/logging.properties
+Using boot properties at /path/to/openidm/conf/boot/boot.properties
+-> OpenIDM ready
+----
+
+
+[#crudops-create-role]
+==== Creating a Managed Role
+
+In this section, you will create two managed roles - an __Employee__ role and a __Contractor__ role. The sample shows how to create the first role directly over the REST interface and the second by using the Admin UI. Choose whichever method is easiest for you to create both roles.
+
+To create the Employee role by using the REST interface:
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{
+ "name" : "Employee",
+ "description": "Role granted to workers on the payroll."
+ }' \
+ "http://localhost:8080/openidm/managed/role?_action=create"
+{
+ "_id": "ad19979e-adbb-4d35-8320-6db50646b432",
+ "_rev": "1",
+ "name": "Employee",
+ "description": "Role granted to workers on the payroll."
+}
+----
+Take note of the role's identifier (ad19979e-adbb-4d35-8320-6db50646b432 in this example). Although you can create roles with a PUT request and specify your own ID, you should use system-generated IDs in a production environment to avoid conflicts and referential integrity issues.
+
+====
+To create the Contractor role by using the Admin UI:
+
+. Point your browser to the Admin UI URL (for example `\https://localhost:8443/admin/`).
+
+. On the Dashboard, click Manage Role > New Role.
+
+. Enter a name for the role (Contractor) and a description.
+
+. Click Save.
++
+The managed role has been created, but currently has no assignments. These will be added in the second roles sample.
++
+
+[NOTE]
+======
+The Admin UI creates managed objects with an unmutable, system-generated identifier. You will see this in the next section, in which you read or query the role object.
+======
+
+====
+
+
+[#crudops-read-role]
+==== Reading and Searching Managed Roles
+
+This section shows how to read, and search managed role objects.
+The easiest way to see a list of managed role objects is by using the Admin UI:
+
+. Navigate to the Admin UI URL.
+
+. On the Dashboard, click Manage Role.
++
+The list of roles (currently only Contractor and Employee) is displayed.
+
+If you know the identifier, it is easy to read a managed role object over the REST interface. The following command reads the Employee role, that you created in the previous section:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role/ad19979e-adbb-4d35-8320-6db50646b432"
+{
+ "_id": "ad19979e-adbb-4d35-8320-6db50646b432",
+ "_rev": "1",
+ "name": "Employee",
+ "description": "Role granted to workers on the payroll."
+}
+----
+You can also __search__ for the role object, with a filtered query, if you know the role name. For more information about filtered queries, see xref:../integrators-guide/chap-data.adoc#query-filters["Common Filter Expressions"] in the __Integrator's Guide__.
+
+The following query retrieves all roles with a name equal to "Contractor". The `_prettyPrint=true` parameter displays the result in a format that is easy to read:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role?_queryFilter=/name+eq+'Contractor'&_prettyPrint=true"
+
+{
+ "result": [
+ {
+ "_id": "b02d2531-5066-415e-bc90-31fe57e02322",
+ "_rev": "1",
+ "name": "Contractor",
+ "description": "Role granted to contract workers."
+ }
+ ],
+ ...
+}
+----
+To retrieve a list of all the managed roles that have been defined, query the `managed/role` endpoint, as follows:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role?_queryFilter=true&_prettyPrint=true"
+
+{
+ "result" : [ {
+ "_id" : "ad19979e-adbb-4d35-8320-6db50646b432",
+ "_rev" : "1",
+ "name" : "Employee",
+ "description" : "Role granted to workers on the payroll."
+ }, {
+ "_id" : "b02d2531-5066-415e-bc90-31fe57e02322",
+ "_rev" : "1",
+ "name" : "Contractor",
+ "description" : "Role granted to contract workers."
+ } ],
+ ...
+}
+----
+
+
+[#crudops-assign-role]
+==== Granting a Managed Role to a User
+
+For a role to be useful, it must be granted to a managed user. This section creates a new managed user entry, Felicitas Doe, then assigns the Employee role, created in the previous section, to Felicitas's user entry.
+
+====
+
+. Create the user entry over REST, as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{
+ "mail":"fdoe@example.com",
+ "sn":"Doe",
+ "telephoneNumber":"555-1234",
+ "userName":"fdoe",
+ "givenName":"Felicitas",
+ "description":"Felicitas Doe",
+ "displayName":"fdoe"
+ }' \
+ "http://localhost:8080/openidm/managed/user?_action=create"
+{
+ "_id": "837085ae-766e-417c-9b7e-c36eee4352a3",
+ "_rev": "1",
+ "mail": "fdoe@example.com",
+ "sn": "Doe",
+ "telephoneNumber": "555-1234",
+ "userName": "fdoe",
+ "givenName": "Felicitas",
+ "description": "Felicitas Doe",
+ "displayName": "fdoe",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
++
+Note that Felicitas has no `effectiveRoles` or `effectiveAssignments` by default.
++
+
+[TIP]
+======
+Create the user entry in the Admin UI:
+
+* Click Manage User > New User from the Dashboard.
+
+======
+
+. Grant the Employee role to Felicitas's entry by sending a PATCH request to update her entry, and providing a pointer to the role ID:
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/roles/-",
+ "value" : {"_ref": "managed/role/ad19979e-adbb-4d35-8320-6db50646b432"}
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/837085ae-766e-417c-9b7e-c36eee4352a3"
+{
+ "_id": "837085ae-766e-417c-9b7e-c36eee4352a3",
+ "_rev": "2",
+ "mail": "fdoe@example.com",
+ "sn": "Doe",
+ "telephoneNumber": "555-1234",
+ "userName": "fdoe",
+ "givenName": "Felicitas",
+ "description": "Felicitas Doe",
+ "displayName": "fdoe",
+ "accountStatus": "active",
+ "effectiveRoles": [
+ {
+ "_ref": "managed/role/ad19979e-adbb-4d35-8320-6db50646b432"
+ }
+ ],
+ "effectiveAssignments": []
+}
+----
++
+
+[TIP]
+======
+Grant the role in the Admin UI:
+
+.. Select Manage > User and click on fdoe's entry.
+
+.. Click Provisioning Roles, select the Employee role and click Add Role.
+
+OR
+
+.. Select Manage > Role and click on the Employee Role.
+
+.. Select the Role Members tab, click Add Role Members, browse for fdoe's entry, and click Add.
+
+======
+
+. Now, query Felicitas's entry to return her `roles` and __effectiveRoles__.
++
+The `effectiveRoles` property is a virtual property whose value is calculated based on the roles that have been granted to a user, either manually as in this example, or dynamically, through a script or condition. For more information about dynamically granted roles, see xref:../integrators-guide/chap-users-groups-roles.adoc#granting-roles-dynamically["Granting Roles Dynamically"] in the __Integrator's Guide__.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=/givenName+eq+'Felicitas'&_fields=_id,userName,roles,effectiveRoles"
+{
+ "result" : [ {
+ "_id" : "837085ae-766e-417c-9b7e-c36eee4352a3",
+ "_rev" : "2",
+ "userName" : "fdoe",
+ "roles" : [ {
+ "_ref": "managed/role/ad19979e-adbb-4d35-8320-6db50646b432",
+ "_refProperties": {
+ "_id": "4a42cd0b-d5d0-47e9-81e7-513aed74f6bc",
+ "_rev": "1"
+ } ],
+ "effectiveRoles" : [ {
+ "_ref" : "managed/role/ad19979e-adbb-4d35-8320-6db50646b432"
+ } ]
+ } ],
+...
+}
+----
++
+Note that Felicitas's `roles` and `effectiveRoles` attributes both show the reference to the `Employee` role ID.
+
+====
+
+
+[#crudops-remove-assignment]
+==== Removing a Managed User's Roles
+
+Imagine that the Employee role was erroneously granted to Felicitas, and must be removed from her user entry.
+
+To remove a granted role from a managed user, send a DELETE request to the user entry, specifying the ID of the __relationship__ that must be removed. Note that this is not the ID of the role.
+
+The request to remove the Employee role from Felicitas's entry is as follows:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/user/837085ae-766e-417c-9b7e-c36eee4352a3/roles/4a42cd0b-d5d0-47e9-81e7-513aed74f6bc"
+{
+ "_ref": "managed/role/ad19979e-adbb-4d35-8320-6db50646b432",
+ "_refProperties": {
+ "_id": "4a42cd0b-d5d0-47e9-81e7-513aed74f6bc",
+ "_rev": "1"
+ }
+}
+----
+If you query Felicitas's entry again, you will notice that her `roles` and `effectiveRoles` properties are now empty:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=/givenName+eq+'Felicitas'&_fields=_id,userName,roles,effectiveRoles"
+{
+ "result" : [ {
+ "_id" : "837085ae-766e-417c-9b7e-c36eee4352a3",
+ "_rev" : "2",
+ "userName" : "fdoe",
+ "roles": [],
+ "effectiveRoles": []
+ } ],
+...
+}
+----
+There are other methods to remove a granted role from a user over the REST interface. These are described in xref:../integrators-guide/chap-users-groups-roles.adoc#delete-role-user["Deleting a User's Roles"] in the __Integrator's Guide__.
+
+[TIP]
+====
+You can also remove the granted role from fdoe's entry in the Admin UI as follows:
+
+. Select Manage > User and click on fdoe's entry.
+
+. On the Provisioning Roles tab, click the checkbox next to the Employee role and click Remove Selected Provisioning Roles.
+
+====
+
+
+[#crudops-delete-role]
+==== Deleting a Managed Role
+
+The final step in basic role management is to remove an existing role. In this section, we will remove the Contractor role we created previously.
+
+Note that it is not possible to remove a role that is already granted to a user. To demonstrate this, we temporarily grant the Contractor role to Felicitas.
+
+====
+
+. Assign the Contractor role to Felicitas, either by using the Admin UI, as described previously, or over the REST interface. If you use the REST interface, you will need to remember the system-generated identifier that was output when you queried the roles.
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/roles/-",
+ "value" : {"_ref": "managed/role/b02d2531-5066-415e-bc90-31fe57e02322"}
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/837085ae-766e-417c-9b7e-c36eee4352a3"
+{
+ "_id": "837085ae-766e-417c-9b7e-c36eee4352a3",
+ "_rev": "4",
+ "mail": "fdoe@example.com",
+ "sn": "Doe",
+ "telephoneNumber": "555-1234",
+ "userName": "fdoe",
+ "givenName": "Felicitas",
+ "description": "Felicitas Doe",
+ "displayName": "fdoe",
+ "accountStatus": "active",
+ "effectiveRoles": [
+ {
+ "_ref": "managed/role/b02d2531-5066-415e-bc90-31fe57e02322"
+ }
+ ],
+ "effectiveAssignments": []
+}
+----
+
+. Now, try to delete the Contractor role:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/role/b02d2531-5066-415e-bc90-31fe57e02322"
+{
+ "code": 409,
+ "reason": "Conflict",
+ "message": "Cannot delete a role that is currently granted"
+}
+----
++
+As you can see from the previous output, it is not possible to delete a role that is still granted to a user.
+
+. Remove the role from Felicitas's entry, either by using the Admin UI (as described previously), or by using a DELETE request, specifying the ID of the __relationship__ that must be removed. Note that this is not the ID of the role. You will need to read Felicitas's `roles` property to obtain the relationship ID:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=/givenName+eq+'Felicitas'&_fields=_id,userName,roles,effectiveRoles"
+{
+ "result" : [ {
+ "_id" : "837085ae-766e-417c-9b7e-c36eee4352a3",
+ "_rev" : "3",
+ "userName" : "fdoe",
+ "roles" : [ {
+ "_ref": "managed/role/b02d2531-5066-415e-bc90-31fe57e02322",
+ "_refProperties": {
+ "_id": "93703eff-a7ef-4cf3-80b1-f86fa3607978",
+ "_rev": "1"
+ } ],
+ "effectiveRoles" : [ {
+ "_ref" : "managed/role/b02d2531-5066-415e-bc90-31fe57e02322"
+ } ]
+ } ],
+...
+}
+----
++
+In this example, the relationship ID is 93703eff-a7ef-4cf3-80b1-f86fa3607978.
++
+The following request removes the Contractor role from Felicitas's entry:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/user/837085ae-766e-417c-9b7e-c36eee4352a3/roles/93703eff-a7ef-4cf3-80b1-f86fa3607978"
+{
+ "_ref": "managed/role/b02d2531-5066-415e-bc90-31fe57e02322",
+ "_refProperties": {
+ "_id": "93703eff-a7ef-4cf3-80b1-f86fa3607978",
+ "_rev": "1"
+ }
+}
+----
+
+. Now that no users are granted the Contractor role, you can delete the role as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/role/b02d2531-5066-415e-bc90-31fe57e02322"
+{
+ "_id": "b02d2531-5066-415e-bc90-31fe57e02322",
+ "_rev": "1",
+ "name": "Contractor",
+ "description": "Role granted to contract workers."
+}
+----
++
+The DELETE operation returns the complete role entry.
++
+
+[TIP]
+======
+Delete the Contractor role by using the Admin UI:
+
+.. Select Manage > Role.
+
+.. Select the Contractor role and click Delete Selected.
+
+======
+
+. Verify that the Contractor role has been deleted by querying the list of managed role objects:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role?_queryFilter=true&_prettyPrint=true"
+{
+ "result": [
+ {
+ "_id": "ad19979e-adbb-4d35-8320-6db50646b432",
+ "_rev": "1",
+ "name": "Employee",
+ "description": "Role granted to workers on the payroll."
+ }
+ ],
+ ...
+}
+----
++
+Note that only the Employee role remains.
++
+
+[TIP]
+======
+List the remaining role objects in the Admin UI by clicking Manage > Role.
+======
+
+====
+This concludes the basic role management operations. In the next section, you will see how to add assignments to roles, and how those assignments are used to provision users to external systems.
+
+
+
+[#more-sample-roles-prov]
+=== Provisioning Role Sample - Provisioning to an LDAP Server
+
+The main purpose of OpenIDM roles is to provision a set of attributes, based on a managed user's role membership.
+
+This sample builds on what you learnt in the previous sample, and you will create the same Employee and Contractor roles that were described in that sample. This sample also builds on Sample 2b (described in xref:chap-ldap-samples.adoc#more-sample-2b["Sample 2b - LDAP Two Way"]), and provisions users from the managed user repository to an OpenDJ directory.
+
+The sample assumes a company, example.com. As an __Employee__ of example.com, a user should be added to two groups in OpenDJ - the Employees group and the Chat Users group (presumably to access certain internal applications). As a __Contractor__, a user should be added only to the Contractors group in OpenDJ. A user's employee type must also be set correctly in OpenDJ, based on the role that is granted to the user.
+
+[#external-ldap-config-roles-prov]
+==== External LDAP Configuration
+
+Configure OpenDJ as for sample 2 (see xref:chap-ldap-samples.adoc#external-ldap-config-2["LDAP Server Configuration"]). The LDAP user must have write access to create users from OpenIDM on the LDAP server. When you set up the LDAP server, import the LDIF file for this sample (`openidm/samples/roles/provrole/data/Example.ldif).`
+
+
+[#install-sample-roles-prov]
+==== Before You Start
+
+This section sets up the scenario by performing the following tasks:
+
+. Start OpenIDM with the configuration for the provisioning roles sample.
+
+. Create the Employee and Contractor roles that you created in the previous sample.
+
+. Reconcile the managed user repository with the user entries in the LDAP server.
+
+
+====
+
+. Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for the Provisioning sample.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+----
++
+
+[source, console]
+----
+$ startup.sh -p samples/roles/provrole
+----
+
+. Create the Employee and Contractor roles, either by using the Admin UI (as described in the previous sample), or by running the following commands:
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{
+ "name" : "Employee",
+ "description": "Role granted to workers on the payroll."
+ }' \
+ "http://localhost:8080/openidm/managed/role?_action=create"
+{
+ "_id" : "2902afd5-155a-49c0-9dd9-7e6bfcf1708a",
+ "_rev" : "1",
+ "name" : "Employee",
+ "description" : "Role granted to workers on the payroll."
+}
+----
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{
+ "name" : "Contractor",
+ "description": "Role granted to contract workers."
+ }' \
+ "http://localhost:8080/openidm/managed/role?_action=create"
+{
+ "_id": "e7f649ad-8013-4673-a52a-bdcac7483111",
+ "_rev": "1",
+ "name": "Contractor",
+ "description": "Role granted to contract workers."
+}
+----
++
+Note the IDs of these two roles because you will use them in the commands that follow.
+
+. Reconcile the repository.
++
+The `sync.json` configuration file for this sample includes two mappings: `systemLdapAccounts_managedUser`, which synchronizes users from the source LDAP server with the target OpenIDM repository; and `managedUser_systemLdapAccounts`, which synchronizes changes from the OpenIDM repository with the LDAP server.
++
+Run a reconciliation operation for the first mapping, either by using the Admin UI, or over the REST interface:
++
+
+* To use the Admin UI, select Configure > Mapping, click on the first mapping (System/Ldap/Account --> Managed User) and click Reconcile Now.
+
+* To use the REST interface, run the following command:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemLdapAccounts_managedUser&waitForCompletion=true"
+{
+ "_id": "b5c535f8-5c1f-44dc-afa3-40d4f9984925-24",
+ "state": "SUCCESS"
+}
+----
+
+
+====
+The sample is now ready to demonstrate provisioning roles.
+
+
+[#run-sample-roles-prov]
+==== Run the Sample
+
+This section assumes that you have reconciled the managed user repository to populate it with the users from the LDAP server, and that you have created the Employee and Contractor roles.
+This part of the sample demonstrates the following features of the OpenIDM roles implementation:
+
+* xref:#provrole-add-assignments["Adding Assignments to a Role Definition"]
+
+* xref:#provrole-effective-assignments["Granting a Role to a User and Observing that User's Role Assignments"]
+
+* xref:#provrole-propagate-assignments["Propagating Assignments to an External System"]
+
+* xref:#provrole-remove-role["Removing a Role Grant From a User and Observing That User's Role Assignments"]
+
+
+[#provrole-add-assignments]
+===== Adding Assignments to a Role Definition
+
+A role __assignment__ is the logic that provisions a managed user to an external system, based on some criteria. The most common use case of a role assignment is the provisioning of specific attributes to an external system, based on the role or roles that the managed user has been granted. Assignments are sometimes called __entitlements__. For more information about assignments, see xref:../integrators-guide/chap-users-groups-roles.adoc#working-with-role-assignments["Working With Role Assignments"] in the __Integrator's Guide__.
+
+In this section, you will create assignments and add them to the two roles that you created previously. This section assumes the following scenario:
+
+example.com's policy requires that every employee has the correct value for their `employeeType` in their corporate directory (OpenDJ).
+
+====
+
+. Display the roles that you created in the previous section:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role?_queryFilter=true"
+{
+ "result" : [
+ {
+ "_id" : "2902afd5-155a-49c0-9dd9-7e6bfcf1708a",
+ "_rev" : "1",
+ "name" : "Employee",
+ "description" : "Role granted to workers on the payroll."
+ },
+ {
+ "_id" : "e7f649ad-8013-4673-a52a-bdcac7483111",
+ "_rev" : "1",
+ "name" : "Contractor",
+ "description" : "Role granted to contract workers."
+ }
+ ],
+...
+}
+----
++
+
+[TIP]
+======
+Display the roles in the Admin UI by selecting Manage > Role.
+======
+
+. Create a new managed assignment named Employee.
++
+The assignment is specifically for the mapping from the managed user repository to the LDAP server. The assignment sets the value of the `employeeType` attribute on the LDAP server to `Employee`:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-type: application/json" \
+ --request POST \
+ --data '{
+ "name" : "Employee",
+ "description": "Assignment for employees.",
+ "mapping" : "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "employeeType",
+ "value": "Employee",
+ "assignmentOperation" : "mergeWithTarget",
+ "unassignmentOperation" : "removeFromTarget"
+ }
+ ]
+ }' \
+ "http://localhost:8080/openidm/managed/assignment?_action=create"
+{
+ "_id": "f2830b80-6ab8-416b-b219-d3bf2efd0ed3",
+ "_rev": "1",
+ "name": "Employee",
+ "description": "Assignment for employees.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "employeeType",
+ "value": "Employee",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ]
+}
+----
++
+
+[TIP]
+======
+Create the assignment in the Admin UI:
+
+.. Select Manage > Assignment, and click New Assignment.
+
+.. Enter a name and description for the assignment, and select the mapping for which the assignment is applied (managedUser_systemLdapAccounts).
+
+.. Click Add Assignment.
+
+.. Select the Attributes tab, and click Add an Attribute.
+
+.. Select employeeType, and enter the value Employee.
+
+.. Click Save.
+
+======
+
+. Add the assignment to the Employee role that you created previously.
++
+Assignments are implemented as __relationship objects__. This means that you add an assignment to a role by __referencing__ the assignment in the role's `assignments` field:
++
+This command patches the Employee role to update its `assignments` field.
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/assignments/-",
+ "value" : { "_ref": "managed/assignment/f2830b80-6ab8-416b-b219-d3bf2efd0ed3"}
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/role/2902afd5-155a-49c0-9dd9-7e6bfcf1708a"
+{
+ "_id": "2902afd5-155a-49c0-9dd9-7e6bfcf1708a",
+ "_rev": "2",
+ "name": "Employee",
+ "description": "Role granted to workers on the payroll."
+}
+----
++
+
+[TIP]
+======
+Add the assignment to the role in the Admin UI:
+
+.. Select Manage > Role, and select the Employee role.
+
+.. On the Managed Assignments tab, click Add Managed Assignments.
+
+.. Select the Employee assignment and click Add.
+
+======
+
+====
+
+
+[#provrole-effective-assignments]
+===== Granting a Role to a User and Observing that User's Role Assignments
+
+When a role is granted to a user (by updating the users `roles` property), any assignments that are referenced by the role are automatically referenced in the user's `assignments` property.
+
+In this section, we will grant the Employee role we created previously to the user Barbara Jensen, who was created in the managed/user repository during the reconciliation from OpenDJ.
+
+====
+
+. Before you can update Barbara Jensen's entry, determine the identifier of her entry by querying her username, `bjensen`, and requesting only her `_id` field:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=/userName+eq+'bjensen'&_fields=_id"
+
+{
+ "result" : [ {
+ "_id" : "2c7daf46-d3ce-4bc5-9790-b44113bca8e7",
+ "_rev" : "1"
+ } ],
+ ...
+}
+----
++
+From the output, you can see that bjensen's `_id` is `2c7daf46-d3ce-4bc5-9790-b44113bca8e7`. (This unique ID will obviously be different in your command output.)
+
+. Update bjensen's entry by adding a reference to the ID of the Employee role as a value of her `roles` attribute:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-type: application/json" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/roles/-",
+ "value" : { "_ref": "managed/role/2902afd5-155a-49c0-9dd9-7e6bfcf1708a" }
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/2c7daf46-d3ce-4bc5-9790-b44113bca8e7"
+{
+ "_id": "2c7daf46-d3ce-4bc5-9790-b44113bca8e7",
+ "_rev": "4",
+ "displayName": "Barbara Jensen",
+ "description": "Created for OpenIDM",
+ "givenName": "Barbara",
+ "mail": "bjensen@example.com",
+ "telephoneNumber": "1-360-229-7105",
+ "sn": "Jensen",
+ "userName": "bjensen",
+ "accountStatus": "active",
+ "effectiveRoles": [
+ {
+ "_ref": "managed/role/2902afd5-155a-49c0-9dd9-7e6bfcf1708a"
+ }
+ ],
+ "effectiveAssignments": [
+ {
+ "name": "Employee",
+ "description": "Assignment for employees.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "employeeType",
+ "value": "Employee",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ],
+ "_id": "f2830b80-6ab8-416b-b219-d3bf2efd0ed3",
+ "_rev": "1"
+ }
+ ]
+}
+----
++
+
+[TIP]
+======
+Assign the role to bjensen by using the Admin UI:
+
+.. Select Manage > User, and click on bjensen's entry.
+
+.. On the Provisioning Roles tab, click Add Provisioning Roles.
+
+.. Select the Employee role and click Add.
+
+======
+
+. Take a closer look at bjensen's entry, specifically at her roles, effective roles and effective assignments:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=/userName+eq+'bjensen'&_fields=_id,userName,roles,effectiveRoles,effectiveAssignments"
+{
+ "result": [
+ {
+ "_id": "2c7daf46-d3ce-4bc5-9790-b44113bca8e7",
+ "_rev": "4",
+ "userName": "bjensen",
+ "roles": [
+ {
+ "_ref": "managed/role/2902afd5-155a-49c0-9dd9-7e6bfcf1708a",
+ "_refProperties": {
+ "_id": "b1c29213-e726-466a-9051-e9bb4e593331",
+ "temporalConstraints": [],
+ "_grantType": "",
+ "_rev": "2"
+ }
+ }
+ ],
+ "effectiveRoles": [
+ {
+ "_ref": "managed/role/2902afd5-155a-49c0-9dd9-7e6bfcf1708a"
+ }
+ ],
+ "effectiveAssignments": [
+ {
+ "name": "Employee",
+ "description": "Assignment for employees.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "employeeType",
+ "value": "Employee",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ],
+ "_id": "f2830b80-6ab8-416b-b219-d3bf2efd0ed3",
+ "_rev": "1"
+ }
+ ]
+ }
+ ],
+ ...
+}
+----
++
+Note that bjensen now has the calculated property `effectiveAssignments`, which includes the set of assignments that pertains to any user with the Employee role. Currently the assignment lists the `employeeType` attribute.
++
+In the next section, you will see how the assignment is used to set the value of the `employeeType` attribute in the LDAP server.
+
+====
+
+
+[#provrole-propagate-assignments]
+===== Propagating Assignments to an External System
+
+This section provides a number of steps that show how effective assignments are propagated out to the external systems associated with their mappings.
+
+====
+
+. Verify that bjensen's `employeeType` has been set correctly in the OpenDJ.
++
+Because implicit synchronization is enabled by default in OpenIDM, any changes made to a managed user object are pushed out to all the external systems for which mappings are configured.
++
+So, because bjensen has an effective assignment that sets an attribute in her LDAP entry, you should immediately see the resulting change in her LDAP entry.
++
+To verify that her entry has changed, run an `ldapsearch` on her entry and check the value of her `employeeType` attribute.
++
+
+[NOTE]
+======
+This command assumes that you are using the `ldapsearch` provided with OpenDJ (`opendj/bin/ldapsearch`).
+======
++
+
+[source, console]
+----
+$ ldapsearch \
+ --port 1389 \
+ --hostname localhost \
+ --baseDN "dc=example,dc=com" \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --searchScope sub \
+ "(uid=bjensen)" dn uid employeeType
+dn: uid=bjensen,ou=People,dc=example,dc=com
+uid: bjensen
+employeeType: Employee
+----
++
+Note that bjensen's `employeeType` attribute is correctly set to `Employee`.
++
+
+[TIP]
+======
+You can also check bjensen's LDAP entry by using the OpenDJ Control Panel (`opendj/bin/control-panel`):
+
+image::images/dj-control-panel.png[]
+======
+
+. To observe how a managed user's roles can be used to provision group membership in an external directory, we add the groups that an Employee and a Contractor should have in the corporate directory (OpenDJ) as assignment attributes of the respective roles.
++
+First, look at the current `assignments` of the Employee role again:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role/2902afd5-155a-49c0-9dd9-7e6bfcf1708a?_fields=assignments,name"
+{
+ "_id": "2902afd5-155a-49c0-9dd9-7e6bfcf1708a",
+ "_rev": "2",
+ "assignments": [
+ {
+ "_ref": "managed/assignment/f2830b80-6ab8-416b-b219-d3bf2efd0ed3",
+ "_refProperties": {
+ "_id": "c0005ecb-9dda-4db1-8660-a723b8237f16",
+ "temporalConstraints": [],
+ "_grantType": "",
+ "_rev": "1"
+ }
+ }
+ ],
+ "name": "Employee"
+}
+----
++
+To update the `groups` attribute in bjensen's LDAP entry, you do not need to create a __new__ assignment. You simply need to add the attribute for LDAP groups to the assignment with ID `f2830b80-6ab8-416b-b219-d3bf2efd0ed3`:
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/attributes/-",
+ "value" : {
+ "name": "ldapGroups",
+ "value": [
+ "cn=Employees,ou=Groups,dc=example,dc=com",
+ "cn=Chat Users,ou=Groups,dc=example,dc=com"
+ ],
+ "assignmentOperation" : "mergeWithTarget",
+ "unassignmentOperation" : "removeFromTarget"
+ }
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/assignment/f2830b80-6ab8-416b-b219-d3bf2efd0ed3"
+
+{
+ "_id": "f2830b80-6ab8-416b-b219-d3bf2efd0ed3",
+ "_rev": "2",
+ "name": "Employee",
+ "description": "Assignment for employees.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "employeeType",
+ "value": "Employee",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ },
+ {
+ "name": "ldapGroups",
+ "value": [
+ "cn=Employees,ou=Groups,dc=example,dc=com",
+ "cn=Chat Users,ou=Groups,dc=example,dc=com"
+ ],
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ]
+}
+----
++
+So, the Employee assignment now sets two attributes on the LDAP system - the `employeeType` attribute, and the `ldapGroups` attribute.
++
+
+[TIP]
+======
+To add more attributes to the Employee assignment in the Admin UI:
+
+.. Select Manage > Assignment, and click on the Employee assignment.
+
+.. On the Attributes tab, select Add an attribute and select the ldapGroups attribute.
+
+.. Enter the values
++
+
+[source]
+----
+cn=Employees,ou=Groups,dc=example,dc=com
+----
++
+and
++
+
+[source]
+----
+cn=Chat Users,ou=Groups,dc=example,dc=com
+----
++
+and click Save.
+
+======
+
+. With the implicit synchronization between the managed user repository and OpenDJ, bjensen should now be a member of the `cn=Employees` and `cn=Chat Users` groups in LDAP.
++
+You can verify this with the following `ldapsearch` command. This command returns bjensen's group membership, in her `isMemberOf` attribute.
++
+
+[source, console]
+----
+$ ldapsearch \
+ --port 1389 \
+ --hostname localhost \
+ --baseDN "dc=example,dc=com" \
+ --bindDN "cn=Directory Manager" \
+ --bindPassword password \
+ --searchScope sub \
+ "(uid=bjensen)" dn uid employeeType isMemberOf
+
+dn: uid=bjensen,ou=People,dc=example,dc=com
+uid: bjensen
+employeeType: Employee
+isMemberOf: cn=openidm2,ou=Groups,dc=example,dc=com
+isMemberOf: cn=Chat Users,ou=Groups,dc=example,dc=com
+isMemberOf: cn=Employees,ou=Groups,dc=example,dc=com
+----
++
+You can also check bjensen's group membership by using the OpenDJ Control Panel (as shown previously), or by querying her object in the LDAP system, over the REST interface:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryFilter=/uid+sw+'bjensen'&_fields=dn,uid,employeeType,ldapGroups"
+{
+ "result" : [ {
+ "_id" : "uid=bjensen,ou=People,dc=example,dc=com",
+ "dn" : "uid=bjensen,ou=People,dc=example,dc=com",
+ "uid" : "bjensen",
+ "employeeType" : "Employee",
+ "ldapGroups" : [
+ "cn=openidm2,ou=Groups,dc=example,dc=com",
+ "cn=Employees,ou=Groups,dc=example,dc=com",
+ "cn=Chat Users,ou=Groups,dc=example,dc=com"
+ ]
+ } ],
+ ...
+}
+----
++
+In the original LDIF file, bjensen was already a member of the openidm2 group. You can ignore this group for the purposes of this sample.
++
+
+[TIP]
+======
+Use the Admin UI to see bjensen's LDAP groups as follows:
+
+.. Select Manage > User, and select bjensen's entry.
+
+.. On the Linked Systems tab, scroll down to the ldapGroups item.
+
+======
+
+. Now, create a new assignment that will apply to Contract employees, and add that assignment to the Contractor role.
++
+Create the Contractor assignment with the following command. This assignment sets the value of the `employeeType` attribute to `Contractor`, and updates the user's `ldapGroups` attribute to include the `cn=Contractors` group:
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ --data '{
+ "name" : "Contractor",
+ "description": "Contractor assignment for contract workers.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "ldapGroups",
+ "value": [
+ "cn=Contractors,ou=Groups,dc=example,dc=com"
+ ],
+ "assignmentOperation" : "mergeWithTarget",
+ "unassignmentOperation" : "removeFromTarget"
+ },
+ {
+ "name": "employeeType",
+ "value": "Contractor",
+ "assignmentOperation" : "mergeWithTarget",
+ "unassignmentOperation" : "removeFromTarget"
+ }
+ ]
+ }' \
+ "http://localhost:8080/openidm/managed/assignment?_action=create"
+{
+ "_id": "7536e234-1268-482d-8459-24c8ef832def",
+ "_rev": "1",
+ "name": "Contractor",
+ "description": "Contractor assignment for contract workers.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "ldapGroups",
+ "value": [
+ "cn=Contractors,ou=Groups,dc=example,dc=com"
+ ],
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ },
+ {
+ "name": "employeeType",
+ "value": "Contractor",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ]
+}
+----
++
+Note the ID of the Contractor assignment (7536e234-1268-482d-8459-24c8ef832def in this example).
++
+
+[TIP]
+======
+Create the assignment by using the Admin UI, as described in xref:#provrole-add-assignments["Adding Assignments to a Role Definition"]).
+======
+
+. Now, add the Contractor assignment to the Contractor role (ID e7f649ad-8013-4673-a52a-bdcac7483111 in this example):
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/assignments/-",
+ "value" : {
+ "_ref" : "managed/assignment/7536e234-1268-482d-8459-24c8ef832def"
+ }
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/role/e7f649ad-8013-4673-a52a-bdcac7483111"
+{
+ "_id": "e7f649ad-8013-4673-a52a-bdcac7483111",
+ "_rev": "2",
+ "name": "Contractor",
+ "description": "Role granted to contract workers."
+}
+----
++
+
+[TIP]
+======
+Add the Contractor assignment to the Contractor role in the Admin UI, as follows:
+
+.. Select Manage > Role, and select the Contractor role.
+
+.. On the Managed Assignments tab, click Add Managed Assignment.
+
+.. Select the Contractor assignment from the dropdown list, and click Add.
+
+======
+
+. Next, we need to grant the Contractor role to user jdoe. Before we can patch jdoe's entry, we need to know his system-generated ID. To obtain the ID, query jdoe's entry as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=/userName+eq+'jdoe'&_fields=_id"
+
+{
+ "result": [
+ {
+ "_id": "92680be0-82f9-4297-9e00-c35c7cf700d2",
+ "_rev": "2"
+ }
+ ],
+ ...
+}
+----
++
+From the output, you can see that jdoe's `_id` is `92680be0-82f9-4297-9e00-c35c7cf700d2`. (This unique ID will obviously be different in your command output.)
+
+. Update jdoe's entry by adding a reference to the ID of the Contractor role (e7f649ad-8013-4673-a52a-bdcac7483111) as a value of his `roles` attribute:
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/roles/-",
+ "value" : {
+ "_ref": "managed/role/e7f649ad-8013-4673-a52a-bdcac7483111"
+ }
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/92680be0-82f9-4297-9e00-c35c7cf700d2"
+{
+ "_id": "92680be0-82f9-4297-9e00-c35c7cf700d2",
+ "_rev": "4",
+ "displayName": "John Doe",
+ "description": "Created for OpenIDM",
+ "givenName": "John",
+ "mail": "jdoe@example.com",
+ "telephoneNumber": "1-415-599-1100",
+ "sn": "Doe",
+ "userName": "jdoe",
+ "accountStatus": "active",
+ "effectiveRoles": [
+ {
+ "_ref": "managed/role/e7f649ad-8013-4673-a52a-bdcac7483111"
+ }
+ ],
+ "effectiveAssignments": [
+ {
+ "name": "Contractor",
+ "description": "Contractor assignment for contract workers.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "ldapGroups",
+ "value": [
+ "cn=Contractors,ou=Groups,dc=example,dc=com"
+ ],
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ },
+ {
+ "name": "employeeType",
+ "value": "Contractor",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ],
+ "_id": "7536e234-1268-482d-8459-24c8ef832def",
+ "_rev": "1"
+ }
+ ]
+}
+----
++
+
+[TIP]
+======
+Grant the Contractor role to jdoe by using the Admin UI, as follows:
+
+.. Select Manage > User, and click on jdoe's entry.
+
+.. On the Provisioning Roles tab, click Add Provisioning Roles.
+
+.. Select the Contractor role and click Add.
+
+.. Click Save.
+
+======
+
+. Check jdoe's entry on the LDAP system.
++
+With the implicit synchronization between the managed user repository and OpenDJ, jdoe should now be a member of the `cn=Contractors` group in LDAP. In addition, his `employeeType` should have been set to `Contractor`.
++
+You can verify this with the following REST query. This command returns jdoes's group membership, in his `isMemberOf` attribute, and his `employeeType`:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryFilter=/uid+sw+'jdoe'&_fields=dn,uid,employeeType,ldapGroups"
+{
+ "result": [
+ {
+ "_id": "uid=jdoe,ou=People,dc=example,dc=com",
+ "givenName": "John",
+ "ldapGroups": [
+ "cn=openidm,ou=Groups,dc=example,dc=com",
+ "cn=Contractors,ou=Groups,dc=example,dc=com"
+ ],
+ "mail": "jdoe@example.com",
+ "employeeType": "Contractor",
+ "uid": "jdoe",
+ "telephoneNumber": "1-415-599-1100",
+ "sn": "Doe",
+ "disabled": null,
+ "cn": "John Doe",
+ "description": "Created for OpenIDM",
+ "dn": "uid=jdoe,ou=People,dc=example,dc=com"
+ }
+ ],
+ ...
+}
+----
++
+
+[TIP]
+======
+Use the Admin UI to see jdoe's LDAP groups as follows:
+
+.. Select Manage > User, and select jdoe's entry.
+
+.. On the Linked Systems tab, scroll down to the ldapGroups item.
+
+======
+
+====
+
+
+[#provrole-remove-role]
+===== Removing a Role Grant From a User and Observing That User's Role Assignments
+
+In this section, you will remove the Contractor role from jdoe's managed user entry and observe the subsequent change to jdoe's managed assignments, and to the corresponding attributes in OpenDJ.
+
+====
+
+. Before you change jdoe's roles, view his entry again to examine his current roles.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=/userName+eq+'jdoe'&_fields=_id,roles"
+{
+ "result": [
+ {
+ "_id": "92680be0-82f9-4297-9e00-c35c7cf700d2",
+ "_rev": "4",
+ "roles": [
+ {
+ "_ref": "managed/role/e7f649ad-8013-4673-a52a-bdcac7483111",
+ "_refProperties": {
+ "_id": "093fc34b-0694-478e-952e-98d0a828b1ac",
+ "_rev": "2"
+ }
+ }
+ ]
+ }
+ ],
+ ...
+}
+----
++
+Note that jdoe's ID is 92680be0-82f9-4297-9e00-c35c7cf700d2 and the ID of the __relationship__ that expresses the role grant is 093fc34b-0694-478e-952e-98d0a828b1ac. You will need these IDs in the next step.
++
+
+[TIP]
+======
+View jdoe's current roles in the Admin UI:
+
+.. Select Manage > User, and select jdoe's entry.
+
+.. The Provisioning Roles tab lists the current roles.
+
+======
+
+. Remove the Contractor role from jdoe's entry by sending a DELETE request to the user entry, specifying the relationship ID:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/user/92680be0-82f9-4297-9e00-c35c7cf700d2/roles/093fc34b-0694-478e-952e-98d0a828b1ac"
+{
+ "_ref": "managed/role/e7f649ad-8013-4673-a52a-bdcac7483111",
+ "_refProperties": {
+ "_id": "093fc34b-0694-478e-952e-98d0a828b1ac",
+ "_rev": "2"
+ }
+}
+----
++
+
+[TIP]
+======
+Use the Admin UI to remove the Contractor role from jdoe's entry as follows:
+
+.. Select Manage > User, and select jdoe's entry.
+
+.. On the Provisioning Roles tab, check the box next to the Contractor role and click Remove Selected Provisioning Roles.
+
+======
+
+. Verify jdoe's `employeeType` and `ldapGroups`.
++
+The removal of the Contractor role causes a synchronization operation to be run on jdoe's entry. His `employeeType` and `ldapGroups` attributes in OpenDJ should be reset to what they were before he was granted the Contractor role.
++
+You can check jdoe's attributes by querying his object in the LDAP directory, over the REST interface:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryFilter=/uid+sw+'jdoe'&_fields=dn,uid,employeeType,ldapGroups"
+{
+ "result" : [ {
+ "sn" : "Doe",
+ "telephoneNumber" : "1-415-599-1100",
+ "employeeType" : null,
+ "dn" : "uid=jdoe,ou=People,dc=example,dc=com",
+ "cn" : "John Doe",
+ "uid" : "jdoe",
+ "ldapGroups" : [ "cn=openidm,ou=Groups,dc=example,dc=com" ],
+ "givenName" : "John",
+ "mail" : "jdoe@example.com",
+ "description" : "Created for OpenIDM",
+ "_id" : "uid=jdoe,ou=People,dc=example,dc=com"
+ } ],
+ ...
+}
+----
++
+
+[TIP]
+======
+Use the Admin UI to see jdoe's LDAP groups as follows:
+
+.. Select Manage > User, and select jdoe's entry.
+
+.. On the Linked Systems tab, scroll down to the ldapGroups item.
+
+======
+
+====
+This concludes the provisioning with roles sample. For more information about roles, assignments, and how to manipulate them, see xref:../integrators-guide/chap-users-groups-roles.adoc#working-with-managed-roles["Working With Managed Roles"] in the __Integrator's Guide__.
+
+
+
+
+[#sample-roles-temporal]
+=== Temporal Constraints Sample - Applying Time-Based Constraints to Roles
+
+To restrict the period during which a role is effective, you can set a __temporal constraint__ on the role itself, or on the role grant. A temporal constraint that is set on a role definition applies to all grants of that role. A temporal constraint that is set on a role grant enables you to specify the period that the role is valid per user. For more information about temporal constraints, see xref:../integrators-guide/chap-users-groups-roles.adoc#roles-temporal-constraints["Using Temporal Constraints to Restrict Effective Roles"] in the __Integrator's Guide__.
+
+This sample builds on what you learned in the previous two roles samples. The sample demonstrates how to add a temporal constraint to the Contractor role to restrict the period that it is effective. The previous sample ended with jdoe's contract ending because he finished up the work that was required for the company. The company now has additional work that requires jdoe's expertise and has rehired him as a contractor. This time however, the company requires that jdoe's contractor role be terminated as soon as his contract expires.
+
+[#temporal-prerequisites]
+==== Before You Start
+
+This sample assumes that you have already run through the previous two roles samples, and have installed and configured OpenDJ as for Sample 2 (see xref:chap-ldap-samples.adoc#external-ldap-config-2["LDAP Server Configuration"]). The LDAP user must have write access to create users from OpenIDM on the LDAP server. When you set up the LDAP server, import the LDIF file for this sample (`openidm/samples/roles/temporalConstraints/data/Example.ldif).`
+
+====
+
+. Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for the temporal constraints sample.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ startup.sh -p samples/roles/temporalConstraints
+----
+
+. Create the Employee and Contractor roles, and their assignments, either by using the Admin UI, or by using the REST interface, as described in the previous sample
++
+Note the IDs of the roles and assignments because you will use them in the commands that follow.
+
+. Reconcile the repository, as described in the previous sample.
+
+====
+The sample is now ready to demonstrate temporal constraints on roles.
+
+
+[#run-sample-roles-temporal]
+==== Run the Sample
+
+This section assumes that you have reconciled the managed user repository to populate it with the users from the LDAP server, and that you have created the Employee and Contractor roles and their assignments.
+This part of the sample demonstrates the following features of the OpenIDM roles implementation:
+
+* xref:#add-temporal-constraint["Adding a Temporal Constraint to a Role Definition"]
+
+* xref:#grant-temporal-role["Granting a Role With a Temporal Constraint to a User"]
+
+* xref:#observe-temporal-role-effects["Observing the Effects of a Temporal Role"]
+
+* xref:#remove-temporal-role["Removing the Role Grant From the User"]
+
+
+[#add-temporal-constraint]
+===== Adding a Temporal Constraint to a Role Definition
+
+In this section, you will update the definition of the Contractor role to add a temporal constraint.
+
+====
+
+. If you are running the sample directly over the REST interface, obtain the ID of the Contractor role:
++
+You can skip this step if you are using the Admin UI.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/role?_queryFilter=true&_prettyPrint=true"
+{
+ "result" : [ {
+ "_id" : "1a4c9047-1ce3-4f39-8901-e4f60176330e",
+ "_rev" : "1",
+ "name" : "Employee",
+ "description" : "Role granted to workers on the payroll."
+ }, {
+ "_id" : "06128fc1-b89b-4fe8-b9b3-4de30fd17b9e",
+ "_rev" : "1",
+ "name" : "Contractor",
+ "description" : "Role granted to contract workers."
+ } ],
+ ...
+}
+----
++
+In this example, the ID of the Contractor role is 06128fc1-b89b-4fe8-b9b3-4de30fd17b9e.
+
+. Patch the contractor role with a temporal constraint by adding a start and end date to the role definition.
++
+For the start date, use the current time plus 5 minutes. This will demonstrate that you can add a future temporal constraint on a user before their contract even starts. For the end date, use 10 minutes from the current time.
++
+In this example, the current time is 15:30:00. Adding 5 minutes results in a start time of 15:35:00. Adding 10 minutes results in an end time of 15:40:00. Adjust these values for the current time that you run the sample.
++
+To add the temporal constraint over REST:
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/temporalConstraints",
+ "value" : [{"duration": "2016-05-05T15:35:00.000Z/2016-05-05T15:40:00.000Z"}]
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/role/06128fc1-b89b-4fe8-b9b3-4de30fd17b9e"
+{
+ "_id": "06128fc1-b89b-4fe8-b9b3-4de30fd17b9e",
+ "_rev": "2",
+ "name": "Contractor",
+ "description": "Role granted to contract workers.",
+ "temporalConstraints": [
+ {
+ "duration": "2016-05-05T15:35:00.000Z\/2016-05-05T15:40:00.000Z"
+ }
+ ]
+}
+----
++
+To add the temporal constraint by using the Admin UI:
+
+.. Select Manage > Role and click the Contractor role.
+
+.. Select Temporal Constraint, then select a time zone, start date, and end date.
+
+.. Click Save.
+
+
+====
+
+
+[#grant-temporal-role]
+===== Granting a Role With a Temporal Constraint to a User
+
+In this section, you will grant the Contractor role to jdoe.
+
+====
+
+. If you are running this sample over REST, determine the identifier of jdoe's entry querying the existing managed users.
++
+If you are using the Admin UI you can skip this step.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=true"
+{
+ "result" : [ {
+ ...
+ }, {
+ "_id" : "0c470c71-bb4e-4cf1-bc9d-77d7b35b180b",
+ "_rev" : "2",
+ "displayName" : "John Doe",
+ "description" : "Created for OpenIDM",
+ "givenName" : "John",
+ "mail" : "jdoe@example.com",
+ "telephoneNumber" : "1-415-599-1100",
+ "sn" : "Doe",
+ "userName" : "jdoe",
+ "accountStatus" : "active",
+ "effectiveRoles" : [ ],
+ "effectiveAssignments" : [ ]
+ } ],
+ ...
+}
+----
++
+Note that jdoe's ID is 0c470c71-bb4e-4cf1-bc9d-77d7b35b180b.
+
+. Update jdoe's entry to grant him the Contractor role:
++
+
+[source, console]
+----
+$ curl \
+ --header "Content-type: application/json" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request PATCH \
+ --data '[
+ {
+ "operation" : "add",
+ "field" : "/roles/-",
+ "value" : {
+ "_ref": "managed/role/06128fc1-b89b-4fe8-b9b3-4de30fd17b9e"
+ }
+ }
+ ]' \
+ "http://localhost:8080/openidm/managed/user/0c470c71-bb4e-4cf1-bc9d-77d7b35b180b"
+{
+ "_id": "0c470c71-bb4e-4cf1-bc9d-77d7b35b180b",
+ "_rev": "4",
+ "displayName": "John Doe",
+ "description": "Created for OpenIDM",
+ "givenName": "John",
+ "mail": "jdoe@example.com",
+ "telephoneNumber": "1-415-599-1100",
+ "sn": "Doe",
+ "userName": "jdoe",
+ "accountStatus": "active",
+ "effectiveRoles": [
+
+ ],
+ "effectiveAssignments": [
+
+ ]
+}
+----
++
+Note that, at this stage, jdoe does not have any effective roles or assignments because his contract has not yet started. Check jdoe's entry again between the start and end date of the temporal constraint to observe the assignment.
++
+To grant the Contractor role to jdoe by using the Admin UI:
+
+.. Select Manage > User and click jdoe's entry.
+
+.. On the Provisioning Roles tab, click Add Provisioning Roles.
+
+.. Select the Contractor role and click Add.
+
+
+====
+
+
+[#observe-temporal-role-effects]
+===== Observing the Effects of a Temporal Role
+
+During the contract period, query jdoe again to see when the Contractor role and assignments are present in his list of effective roles and effective assignments.
+
+====
+
+. When the start time that you set has been reached, query jdoe's entry as follows:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=/userName+sw+"jdoe"&_fields=roles,effectiveRoles,effectiveAssignments"
+{
+ "result": [
+ {
+ "_id": "0c470c71-bb4e-4cf1-bc9d-77d7b35b180b",
+ "_rev": "6",
+ "roles": [
+ {
+ "_ref": "managed/role/06128fc1-b89b-4fe8-b9b3-4de30fd17b9e",
+ "_refProperties": {
+ "_id": "6774696d-999e-4483-87a9-02f501752b29",
+ "_rev": "4"
+ }
+ }
+ ],
+ "effectiveRoles": [
+ {
+ "_ref": "managed/role/06128fc1-b89b-4fe8-b9b3-4de30fd17b9e"
+ }
+ ],
+ "effectiveAssignments": [
+ {
+ "name": "Contractor",
+ "description": "Contractor assignment for contract workers.",
+ "mapping": "managedUser_systemLdapAccounts",
+ "attributes": [
+ {
+ "name": "ldapGroups",
+ "value": [
+ "cn=Contractors,ou=Groups,dc=example,dc=com"
+ ],
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ },
+ {
+ "name": "employeeType",
+ "value": "Contractor",
+ "assignmentOperation": "mergeWithTarget",
+ "unassignmentOperation": "removeFromTarget"
+ }
+ ],
+ "_id": "3e61a1db-202d-4761-aeb7-b617d3376a79",
+ "_rev": "1"
+ }
+ ]
+ }
+ ],
+ ...
+}
+----
++
+There is not really a comparable way to perform this step in the UI.
+
+. Between the start and end times, look at jdoe's entry in the LDAP server. At this point, the Contractor group should be effective for jdoe's entry, so his LDAP groups should include the Contractors group:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryFilter=/uid+sw+"jdoe"&_fields=dn,uid,employeeType,ldapGroups"
+{
+ "result": [
+ {
+ "_id": "uid=jdoe,ou=People,dc=example,dc=com",
+ "dn": "uid=jdoe,ou=People,dc=example,dc=com",
+ "uid": "jdoe",
+ "employeeType": "Contractor",
+ "ldapGroups": [
+ "cn=openidm,ou=Groups,dc=example,dc=com",
+ "cn=Contractors,ou=Groups,dc=example,dc=com"
+ ]
+ }
+ ]
+}
+----
++
+To look at jdoe's LDAP groups in the Admin UI:
+
+.. Select Manage > User, and select jdoe's entry.
+
+.. On the Linked Systems tab, scroll down to the ldapGroups item.
+
+
+. Jdoe's contract expires when the current date is greater than the end date of the temporal constraint.
++
+Query jdoe's entry at that point to see that he no longer has the Contractor roles and assignments in his effective roles and assignments lists (although the role is still there in his roles list, it is no longer effective).
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=/userName+sw+"jdoe"&_fields=roles,effectiveRoles,effectiveAssignments"
+{
+ "result": [
+ {
+ "_id": "0c470c71-bb4e-4cf1-bc9d-77d7b35b180b",
+ "_rev": "8",
+ "roles": [
+ {
+ "_ref": "managed/role/06128fc1-b89b-4fe8-b9b3-4de30fd17b9e",
+ "_refProperties": {
+ "_id": "6774696d-999e-4483-87a9-02f501752b29",
+ "_rev": "6"
+ }
+ }
+ ],
+ "effectiveRoles": [
+
+ ],
+ "effectiveAssignments": [
+
+ ]
+ }
+ ],
+ ...
+}
+----
++
+There is not really comparable way to view this information in the Admin UI. Take note of the ID of the __relationship__ that expresses the role grant (6774696d-999e-4483-87a9-02f501752b29 in this example). You will need this ID in the next step.
+
+. Now verify that jdoe is no longer part of the Contractors group in OpenDJ:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/system/ldap/account?_queryFilter=/uid+sw+"jdoe"&_fields=dn,uid,employeeType,ldapGroups"
+{
+ "result": [
+ {
+ "_id": "uid=jdoe,ou=People,dc=example,dc=com",
+ "dn": "uid=jdoe,ou=People,dc=example,dc=com",
+ "uid": "jdoe",
+ "employeeType": null,
+ "ldapGroups": [
+ "cn=openidm,ou=Groups,dc=example,dc=com"
+ ]
+ }
+ ],
+ ...
+}
+----
++
+In the Admin UI:
+
+.. Select Manage > User, and select jdoe's entry.
+
+.. On the Linked Systems tab, scroll down to the ldapGroups item.
+
+
+====
+
+
+[#remove-temporal-role]
+===== Removing the Role Grant From the User
+
+To finish up this sample, remove the Contractor role from jdoe by sending a DELETE request to his managed user entry, specifying the relationship ID (6774696d-999e-4483-87a9-02f501752b29 in this example):
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request DELETE \
+ "http://localhost:8080/openidm/managed/user/0c470c71-bb4e-4cf1-bc9d-77d7b35b180b/roles/6774696d-999e-4483-87a9-02f501752b29"
+{
+ "_ref": "managed/role/06128fc1-b89b-4fe8-b9b3-4de30fd17b9e",
+ "_refProperties": {
+ "_id": "6774696d-999e-4483-87a9-02f501752b29",
+ "_rev": "6"
+ }
+}
+----
+Alternatively, use the Admin UI to remove the Contractor role from jdoe's entry as follows:
+
+. Select Manage > User, and select jdoe's entry.
+
+. On the Provisioning Roles tab, check the box next to the Contractor role and click Remove Selected Provisioning Roles.
+
+
+
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-salesforce-sample.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-salesforce-sample.adoc
new file mode 100644
index 000000000..d5a170be1
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-salesforce-sample.adoc
@@ -0,0 +1,508 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-salesforce-sample]
+== Salesforce Sample - Salesforce With the Salesforce Connector
+
+OpenIDM 4.5 provides a Salesforce Connector that enables provisioning, reconciliation, and synchronization with a Salesforce organization. The Salesforce Connector is not an OpenICF connector, but a separate OpenIDM module, based on the ForgeRock Common Resource API.
+
+[NOTE]
+====
+The Salesforce Connector, and this corresponding sample, are provided only with the OpenIDM Enterprise build, available on the link:https://backstage.forgerock.com[ForgeRock Backstage, window=\_blank] site.
+====
+This sample demonstrates the creation and update of users from OpenIDM to Salesforce, and from Salesforce to OpenIDM. You can use either the Admin UI, or the command line to run this sample. Both methods are outlined in the sections that follow.
+
+[#salesforce-setup]
+=== Before you Start
+
+This sample requires that you have a Salesforce account, and a Connected App with OAuth enabled. For more information about Connected Apps, see the link:http://help.salesforce.com/apex/HTViewHelpDoc?id=connected_app_overview.htm[Connected Apps Overview, window=\_top] in the Salesforce documentation.
+
+====
+To set up a Connected App for OpenIDM, follow these steps:
+
+. Log in to link:http://salesforce.com[salesforce.com, window=\_top] with your Salesforce credentials.
+
+. Click __Setup__ in the top right corner.
+
+. In the left hand menu, under __Build__, expand the __Create__ item and click __Apps__.
+
+. On the right hand panel, scroll down to __Connected Apps__ and click __New__.
+
+. In the __New Connected App__ panel, enter the following Basic Information:
++
+
+* __Connected App Name__. Enter a name that you will recognize as the OpenIDM App, for example, `OpenIDM`.
+
+* __API Name__. This field defaults to the Connected App Name, but you can change it. Note that the Application API Name can only contain underscores and alphanumeric characters. The name must be unique, begin with a letter, not include spaces, not end with an underscore, and not contain two consecutive underscores.
+
+* __Contact Email__. Enter the email address of the person responsible for this Connected App within your organization.
+
+
+. Select __Enable OAuth Settings__ and enter the following information:
++
+
+* __Callback URL__. Enter the OpenIDM URL, to which the requested OAuth token will be sent, for example `\https://localhost:8443/admin/oauth.html`.
+
+* __Selected OAuth Scopes__. Click the __Add__ button to add the following __Available Auth Scopes__ to the __Selected OAuth Scopes__ column:
++
+
+** Access and manage your data
+
+** Access your basic information
+
+** Perform requests on your behalf at any time
+
++
+
+[#new-app-data]
+image::images/new-app-data.png[]
++
+You can leave the remaining fields blank.
+
+
+. Click __Save__ to create the new Connected App.
+
+. The next window displays your new Connected App.
++
+Under the __API (Enable OAuth Settings)__ item, the Consumer Key and a link to the Consumer Secret are displayed.
++
+Click the link to reveal the Consumer Secret.
++
+
+[#consumer-key]
+image::images/consumer-key.png[]
++
+OpenIDM requires the Consumer Key and Secret to obtain an access token and a refresh token for access to salesforce.com.
++
+Copy and paste both the key and the secret into a file for use when you set up the sample.
+
+. To demonstrate the reconciliation of users from Salesforce to OpenIDM, your Salesforce organization should contain at least a few users. Add these users now if your Salesforce organization is empty.
+
+====
+
+
+[#install-sample-salesforce]
+=== Install the Sample
+
+Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for the Salesforce sample.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/salesforce-connector
+----
+
+
+[#salesforce-sample-ui]
+=== Running the Sample by Using the Admin UI
+
+The Admin UI is the recommended way to test this sample.
+
+====
+
+. Log in to the Admin UI at the URL `\https://localhost:8443/admin` as the default administrative user (`openidm-admin`) with password `openidm-admin`.
++
+
+[WARNING]
+======
+To protect your deployment in production, change the default administrative password. To do so, navigate to the Self-Service UI at `\https://localhost:8443/` and click Change Password.
+======
++
+The Resources tab shows the Salesforce connector, which is currently disabled.
++
+
+image::images/salesforce-connector.png[]
+
+. Enable the Salesforce connector by completing the authentication details as follows. You will need the Consumer Key and Consumer Secret that you obtained in the previous section.
++
+
+* Click on the Salesforce connector and click Edit to open the Edit Connector dialog.
+
+* Select True from the Enabled list.
+
+* Select Salesforce Connector from the Connector Type list.
+
+* Under Basic Connector Details select the Sandbox URL (for the purposes of testing the sample) and enter the Connector Key and Secret that you obtained in the previous section.
+
+* You can leave the default LiveSync details for now. Click Update to update the connector configuration.
++
+
+image::images/edit-sf-connector.png[]
+
+* On the permission request screen click Allow, to enable OpenIDM to access your Salesforce Connected App.
++
+
+[NOTE]
+======
+In the current OpenIDM release, an issue is occasionally seen where the system appears to time out while retrieving the refresh token from Salesforce, at this stage. If this happens, refresh your browser and attempt the connector setup again.
+======
++
+On the Resources tab, your Salesforce Connector should now be Active.
++
+
+image::images/active-sf-connector.png[]
+
+
+. To test the reconciliation process, select the Mappings tab.
++
+This tab shows two configured mappings, one from Salesforce to the OpenIDM repository (`managed/user`) and one from the OpenIDM repository to Salesforce.
++
+
+image::images/salesforce-mappings.png[]
+
+. Click anywhere on the first mapping and click Reconcile Now.
++
+
+image::images/salesforce-reconcile.png[]
++
+The reconciliation operation creates the users that were present in your Salesforce organization in the OpenIDM repository.
+
+. Retrieve the users in the repository. In the upper-right of the screen, click the `openidm-admin` link. In the pop-up menu that appears, click the Data Management View link.
++
+This link opens the Self-Service UI. If you did not change your password in the first step, you are prompted to change your password again. You can bypass this by clicking X to close the password prompt window.
+
+. Select the Users tab.
++
+
+image::images/salesforce-users.png[]
++
+The users from the Salesforce organization have been reconciled to the OpenIDM repository. If the reconciliation was successful, the list of users displayed here should reflect what was in your Salesforce organization.
+
+. To retrieve the details of a specific user, click that username on the Users tab.
++
+The following image shows the details of user `bjensen`. Scroll down. Note the Linked Systems panel that shows the corresponding user record in Salesforce.
++
+
+image::images/salesforce-bjensen.png[]
+
+. To test the second mapping (from OpenIDM to Salesforce), update any user in the OpenIDM repository. For example, update Babs Jensen's username.
+
+. By default, __implicit synchronization__ is enabled for mappings __from__ the `managed/user` repository __to__ any external resource. This means that when you update a managed object, any mappings defined in the `sync.json` file that have the managed object as the source are automatically executed to update the target system. For more information, see xref:../integrators-guide/chap-synchronization.adoc#synchronization-mappings-file["Mapping Source Objects to Target Objects"] in the __Integrator's Guide__.
++
+To test that the implicit synchronization has been successful, look at Babs Jensen's record in the Self-Service UI. At the bottom of the user profile, the Linked Systems panel indicates Babs Jensen's record in the Salesforce data store. Note the changed Username.
++
+Alternatively, check the updated user record in Salesforce.
+
+====
+
+
+[#salesforce-sample-cli]
+=== Running the Sample by Using the Command Line
+
+Running the sample by using the command line is a little more complex. This section breaks the sample into two tasks - configuring the connector, and then testing the configuration by running reconciliation operations between the two systems.
+
+[#d5830e11404]
+.To Set Up the Salesforce Connector
+====
+Before you start, you will need the Consumer Key and Consumer Secret that you obtained in the previous section.
+
+. Obtain the refresh token from salesforce.com by pointing your browser to the following URL. Substitute your Consumer Key for `CLIENT_ID`. If OpenIDM is not running on the localhost, substitute the appropriate hostname and port number in the value of the `redirect_uri` parameter.
++
+link:https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https://localhost:8443/admin/oauth.html&scope=id+api+refresh_token[https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https://localhost:8443/admin/oauth.html&scope=id+api+refresh_token, window=\_blank]
+
+. You are redirected to Salesforce, and prompted to give this application access to your Salesforce account. When you have given consent, you should receive a response URL that looks similar to the following:
++
+
+[source, console]
+----
+https://localhost:8443/admin/index.html#connectors/edit//&code=aPrxJZTK7Rs03PU634VK8Jn9o_U3ZY1ERxM7IiklF...
+----
++
+The `&code` part of this URL is an authorization code, that you need for the following step.
++
+
+[CAUTION]
+======
+Note that this authorization code expires after 10 minutes. If you do not complete the OAuth flow within that time, you will need to start this process again.
+======
+
+. Copy the authorization code from the response URL and use it as the value of the `"code"` parameter in the following REST call. You will also need to supply your Consumer Key and Consumer Secret in this call.
++
+
+[source, console]
+----
+$ curl \
+ --verbose \
+ --data "grant_type=authorization_code" \
+ --data "client_id=consumer-key" \
+ --data "client_secret=consumer-secret" \
+ --data "redirect_uri=https://localhost:8443/admin/oauth.html" \
+ --data "code=access-token-code" \
+ "https://login.salesforce.com/services/oauth2/token"
+{
+ "access_token": "00DS0000003K4fU!AQMAQOzEU.8tCjg8Wk79yKPKCtrtaszX5jrHtoT4NBpJ8x2NFZGjg3PNuc0TWq0EgiGS_mVkfg5f4pVN5...",
+ "signature": "2uREX1lseXdg3Vng/2+Hrlo/KHOWYoim+poj74wKFtw=",
+ "refresh_token": "5Aep861KIwKdekr90I4iHdtDgWwRoG7O_6uHrgJ.yVtMS0UaGxRqE6WFM77W7wCV4muVMgdqKjuWI2i5S6sjN2X",
+ "token_type": "Bearer",
+ "instance_url": "https://example-com.cs1.my.salesforce.com",
+ "scope": "id api refresh_token",
+ "issued_at": "1417182949781",
+ "id": "https://login.salesforce.com/id/00DS0000003K4fUMAS/00530000009hWLcAAM"
+}
+----
++
+The output includes an `access_token` and a `refresh_token`. You will need the `refresh_token` in the following step.
+
+. Edit the `configurationProperties` in your Salesforce connector configuration file (`openidm/samples/salesforce-connector/conf/provisioner.salesforce-salesforce.json`) to include your Consumer Key (`clientID`), Consumer Secret (`clientSecret`), and refresh token.
++
+In addition, set the `"instanceUrl"` to the value returned in the previous step, and set the `"enabled"` property to `true`.
++
+The relevant excerpts of the `provisioner.salesforce-salesforce.json` file are as follows:
++
+
+[source, javascript]
+----
+{
+ "name" : "salesforce",
+ "enabled" : true,
+ "connectorRef" : {
+...
+ "configurationProperties" : {
+ "connectTimeout" : 120000,
+ "loginUrl" : null,
+ "idleCheckInterval" : 10000,
+ "refreshToken" : "5Aep861KIwKdekr90I4iHdtDgWwRoG7O_6uHrgJ.yVtMS0UaGxRqE6WFM77W7wCV4muVMgdqKjuWI2i5S6sjN2X",
+ "clientSecret" : "4850xxxxxxxxxxxxx425",
+ "clientId" : "3MVG98dostKihXN7Is8Q0g5q1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxPdB5f5ATwmaMuWxl",
+ "instanceUrl" : "https://example-com.cs1.my.salesforce.com",
+ "version" : 29
+ }
+...
+----
+
+. Check that your connector configuration is correct by testing the status of the connector, over REST.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "https://localhost:8443/openidm/system?_action=test"
+[
+ {
+ "ok": true,
+ "connectorRef": {
+ "bundleVersion": "2.0.29.2",
+ "systemType": "provisioner.salesforce",
+ "displayName": "Salesforce Connector",
+ "bundleName": "org.forgerock.openidm.salesforce",
+ "connectorName": "org.forgerock.openidm.salesforce.Salesforce"
+ },
+ "objectTypes": [
+ "User",
+ "PermissionSet",
+ "PermissionSetAssignment",
+ "Profile",
+ "PermissionSetLicenseAssign",
+ "Organization",
+ "PermissionSetLicense",
+ "Group",
+ "GroupMember"
+ ],
+ "config": "config/provisioner.salesforce/salesforce",
+ "enabled": true,
+ "name": "salesforce"
+ }
+]
+----
+
+====
+
+[#d5830e11519]
+.Run Reconciliation by Using the Command Line
+====
+The mapping configuration file (`sync.json`) for this sample includes two mappings, `sourceSalesforceUser_managedUser`, which synchronizes users from the Salesforce with the OpenIDM repository, and `managedUser_sourceSalesforceUser`, which synchronizes changes from the OpenIDM repository to Salesforce.
+
+. Reconcile the repository over the REST interface by running the following command:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "https://localhost:8443/openidm/recon?_action=recon&mapping=sourceSalesforceUser_managedUser&waitForCompletion=true"
+{
+ "state": "SUCCESS",
+ "_id": "8a6281ef-6faf-43dd-af5c-3a842b38c468"
+}
+----
++
+The reconciliation operation returns a reconciliation run ID and the status of the operation. Reconciliation creates user objects from LDAP in the OpenIDM repository, assigning the new objects random unique IDs.
+
+. View the recon entry over REST for an indication of the actions that were taken on the OpenIDM repository.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/recon/8a6281ef-6faf-43dd-af5c-3a842b38c468"
+{
+ "duration": 6447,
+ "ended": "2014-11-28T15:01:38.399Z",
+ "started": "2014-11-28T15:01:31.952Z",
+ "parameters": {
+ "null": false,
+ "boolean": false,
+ "number": false,
+ "list": false,
+ "object": {
+ "targetQuery": {
+ "_queryId": "query-all-ids",
+ "resourceName": "managed/user"
+ },
+ "sourceQuery": {
+ "_queryId": "query-all-ids",
+ "resourceName": "system/salesforce/User"
+ }
+ },
+ "pointer": {
+ "empty": true
+ },
+ "transformers": [],
+ "set": false,
+ "map": true,
+ "string": false,
+ "collection": false,
+ "wrappedObject": {
+ "targetQuery": {
+ "resourceName": "managed/user",
+ "_queryId": "query-all-ids"
+ },
+ "sourceQuery": {
+ "_queryId": "query-all-ids",
+ "resourceName": "system/salesforce/User"
+ }
+ }
+ },
+ "_id": "8a6281ef-6faf-43dd-af5c-3a842b38c468",
+ "mapping": "sourceSalesforceUser_managedUser",
+ "state": "SUCCESS",
+ "stage": "COMPLETED_SUCCESS",
+ "stageDescription": "reconciliation completed.",
+ "progress": {
+ "links": {
+ "created": 8,
+ "existing": {
+ "total": "0",
+ "processed": 0
+ }
+ },
+ "target": {
+ "created": 8,
+ "existing": {
+ "total": "0",
+ "processed": 0
+ }
+ },
+ "source": {
+ "existing": {
+ "total": "9",
+ "processed": 9
+ }
+ }
+ },
+ "situationSummary": {
+ "FOUND_ALREADY_LINKED": 0,
+ "UNASSIGNED": 0,
+ "TARGET_IGNORED": 0,
+ "SOURCE_IGNORED": 0,
+ "MISSING": 0,
+ "FOUND": 0,
+ "AMBIGUOUS": 0,
+ "UNQUALIFIED": 0,
+ "CONFIRMED": 0,
+ "SOURCE_MISSING": 0,
+ "ABSENT": 9
+ },
+ "statusSummary": {
+ "SUCCESS": 8,
+ "FAILURE": 1
+ }
+}
+----
++
+The output shows that eight entries were created on the target (`managed/user`).
+
+. You can display those users by querying the IDs in the managed/user repository.
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/managed/user?_queryId=query-all-ids"
+{
+ "remainingPagedResults": -1,
+ "pagedResultsCookie": null,
+ "resultCount": 8,
+ "result": [
+ {
+ "_rev": "0",
+ "_id": "f15322f2-5873-4e5f-a4e5-2d4bc03dd190"
+ },
+ {
+ "_rev": "0",
+ "_id": "85879c60-afa1-4425-8c7a-5cccbbaff587"
+ },
+ {
+ "_rev": "0",
+ "_id": "ed3fe655-29a6-4016-b6bc-4b2356911fd1"
+ },
+ {
+ "_rev": "0",
+ "_id": "34678464-c080-41b1-8da6-d5fde9d35aeb"
+ },
+ {
+ "_rev": "0",
+ "_id": "02d5da29-8349-4f35-affc-5f6c331307ef"
+ },
+ {
+ "_rev": "0",
+ "_id": "f91d6fce-bf27-4379-9411-fd626f8a9528"
+ },
+ {
+ "_rev": "0",
+ "_id": "6ace9220-59e7-4d97-8683-e03362a9150c"
+ },
+ {
+ "_rev": "0",
+ "_id": "56863eea-35d7-4aeb-a017-74ef28fd3116"
+ }
+ ]
+----
+
+====
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-trustedfilter-sample.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-trustedfilter-sample.adoc
new file mode 100644
index 000000000..7ffc88dcf
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-trustedfilter-sample.adoc
@@ -0,0 +1,204 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-trustedfilter-sample]
+== The Trusted Servlet Filter Sample
+
+This sample demonstrates how to use a custom servlet filter and the "Trusted Request Attribute Authentication Module" in OpenIDM. Once configured, OpenIDM can use the servlet filter to authenticate through another service.
+
+If you want to set up authentication through OpenAM, refer to xref:chap-fullstack-sample.adoc#chap-fullstack-sample["Full Stack Sample - Using OpenIDM in the ForgeRock Identity Platform"].
+
+[#trustedfilter-before-you-start]
+=== Before You Start
+
+Before you start this sample, complete the following steps:
+
+* Prepare a fresh installation of OpenIDM. (See xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"]).
+
+* Download and install the link:https://maven.apache.org/install.html[Apache Maven, window=\_blank] build tool.
+
+* Build the custom servlet filter bundle file:
++
+
+[source, console]
+----
+$ cd /path/to/openidm/samples/trustedservletfilter/filter
+$ mvn clean install
+----
+
+* Copy the newly built servlet bundle file to the `openidm/bundle` directory:
++
+
+[source, console]
+----
+$ cp target/sample-trusted-servletfilter-1.0.jar /path/to/openidm/bundle
+----
+
+
+
+[#trustedservlet-bundle]
+=== The Sample Servlet Filter
+
+You just built a bundle file from a Java file in the following `trustedservletfilter/filter` subdirectory: `src/main/java/org/forgerock/openidm/sample/trustedservletfilter`. The file is named `SampleTrustedServletFilter.java`.
+
+The following line looks for the `X-Special-Trusted-User` header, to identify a specific User ID as a "trusted" user.
+
+[source, java]
+----
+final String specialHeader = ((HttpServletRequest) servletRequest).getHeader("X-Special-Trusted-User");
+----
+The next line sets the special Servlet attribute `X-ForgeRock-AuthenticationId` to this trusted User ID.
+
+[source, java]
+----
+servletRequest.setAttribute("X-ForgeRock-AuthenticationId", specialHeader);
+----
+The rest of the servlet filter chain continues request processing:
+
+[source, java]
+----
+filterChain.doFilter(servletRequest, servletResponse);
+----
+This sample includes a `servletfilter-trust.json` file that calls the compiled OpenIDM trusted servlet `filterClass`:
+
+[source, javascript]
+----
+{
+ "classPathURLs" : [ ],
+ "systemProperties" : { },
+ "requestAttributes" : { },
+ "scriptExtensions" : { },
+ "initParams" : { },
+ "urlPatterns" : [
+ "/*"
+ ],
+ "filterClass" : "org.forgerock.openidm.sample.trustedservletfilter.SampleTrustedServletFilter"
+}
+----
+
+
+[#run-trustedfilter]
+=== Run the Sample
+
+Start OpenIDM with the configuration for the trusted filter sample.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/trustedservletfilter
+Executing ./startup.sh...
+Using OPENIDM_HOME: /path/to/openidm
+Using PROJECT_HOME: /path/to/openidm
+Using OPENIDM_OPTS: -Xmx1024m -Xms1024m
+Using LOGGING_CONFIG: -Djava.util.logging.config.file=/path/to/openidm/samples/trustedservletfilter/conf/logging.properties
+Using boot properties at /path/to/openidm/samples/trustedservletfilter/conf/boot/boot.properties
+-> OpenIDM ready
+----
+
+
+[#trusted-create-user]
+=== Create a Trusted User
+
+In this section, you will create a user, and then apply the special request header `X-Special-Trusted-User` to authenticate that user.
+
+Create user Barbara Jensen in OpenIDM, with `userName` `bjensen`:
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request PUT \
+ --data '
+ {
+ "userName": "bjensen",
+ "telephoneNumber": "6669876987",
+ "givenName": "Barbara",
+ "sn": "Jensen",
+ "description": "Example User",
+ "mail": "bjensen@example.com",
+ "authzRoles" : [
+ {
+ "_ref" : "repo/internal/role/openidm-authorized"
+ }
+ ]
+ }' \
+ "https://localhost:8443/openidm/managed/user/bjensen"
+----
+Now you can demonstrate the servlet filter by configuring it with the noted special header. Normally, a servlet filter used for authentication does not allow a client to masquerade as any user. This sample demonstrates a basic use of a servlet filter by establishing the authentication ID.
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-Special-Trusted-User: bjensen" \
+ --request GET \
+ "https://localhost:8443/openidm/info/login?_fields=authenticationId,authorization"
+----
+The output should include a JSON structure with the user's authentication and authorization details. In this case, user `bjensen` is authenticated with the "openidm-authorized" role.
+
+[source, console]
+----
+{
+ "_id" : "",
+ "authenticationId" : "bjensen",
+ "authorization" : {
+ "id" : "bjensen",
+ "component" : "managed/user",
+ "roles" : [ "openidm-authorized" ]
+ }
+}
+----
+
+
+[#external-trustedfilter-servlet]
+=== Customizing the Sample for an External System
+
+To customize this sample for an external authentication/authorization system, you need a servlet filter which authenticates against that external system. You may use a third-party supplied filter, or develop your own filter, using the one in this sample as a model.
+
+The filter you use should have at least the following capabilities:
+
+* Perform REST calls to another system.
+
+* Search through databases.
+
+* Inspect headers related to authentication and authorization requests.
+
+This servlet filter must set the username of the authenticated user in a special request attribute. You need to configure that same attribute name in the `TRUSTED_ATTRIBUTE` authentication module, specifically the value of `authenticationIdAttribute`.
+
+It is helpful if you have a filter that returns an object with the `userRoles` property. If your filter does not support queries using the following parameter:
+
+[source, console]
+----
+queryOnResource + "/" + authenticationId
+----
+You will need to provide a security context augmentation script that populates the following authorization properties in the "security" object:
+
+* `security.authorization.component`
+
+* `security.authorization.roles`
+
+The value for the `security.authorization.component` is automatically set to the value specified in any exisitng `queryOnResource` property.
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-workflow-samples.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-workflow-samples.adoc
new file mode 100644
index 000000000..4ebd36754
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-workflow-samples.adoc
@@ -0,0 +1,954 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-workflow-samples]
+== Workflow Samples
+
+This chapter walks you through the workflow provisioning sample (in the `openidm/samples/workflow` directory) and the workflow use cases (in the `openidm/samples/usecase` directories). For a complete list of the samples provided with OpenIDM, and an overview of each sample, see xref:chap-overview.adoc#chap-overview["Overview of the OpenIDM Samples"].
+
+[#example-provisioning-workflow]
+=== Sample Workflow - Provisioning User Accounts
+
+This sample, provided in `openidm/samples/workflow`, demonstrates a typical use case of a workflow — provisioning new users.
+
+The sample demonstrates the use of the Admin UI, to configure user self-service and the Self-Service UI that enables users to complete their registration process.
+This sample simulates the following scenario:
+
+* An existing employee requests that an outside contractor be granted access to an organization's system.
+
+* The __system__ in this case, is OpenIDM's managed user repository and a remote datasource, represented by an XML file.
+
+* User roles are stored separately, in a CSV file.
+
+The sample has three mappings — two for the bidirectional synchronization of the managed user repository and the XML data store, and one for the synchronization of the roles data (stored in the CSV file) to the managed repository.
+
+[#provisioning-sample-prepare]
+==== Prepare OpenIDM For the Provisioning Sample
+
+In this section, you start OpenIDM, configure the outbound email service, and reconcile user and role data. The reconciliation operations create two managed users, `user1` and `manager1`, and two managed roles, `employee` (assigned to `user1`) and `manager` (assigned to `manager1`).
+
+====
+
+. Start OpenIDM with the configuration for the provisioning sample.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/workflow
+----
+
+. Log in to the Admin UI (`\https://localhost:8443/admin`) with the default username (`openidm-admin` and password (`openidm-admin`).
+
+. Configure the outbound email service.
++
+An email configuration is required to send a password-reset email to the user, at the end of the sample.
++
+Select Configure > System Preferences > Email and provide connection information for your preferred email host.
++
+Gmail is configured by default but you must still supply credentials to use Gmail.
+
+. Reconcile the role data, and the user data.
++
+
+.. Select Configure > Mappings.
+
+.. Click the first mapping (`systemRolesFileRole_managedRole`) and click Reconcile Now.
++
+This reconciliation operation creates two roles in the managed repository (`employee` and `manager`). You can check the result of the reconciliation by selecting Manage > Role.
+
+.. Go back to the Mappings page (Configure > Mappings), select the second mapping (`systemXmlfileAccounts_managedUser`) and click Reconcile Now.
++
+This reconciliation operation creates the top-level managers (users who do not have their own `manager` property) in the managed user repository. In this sample, there is only one top-level manager (`manager1`).
+
+.. Click Reconcile Now a second time.
++
+This reconciliation operation creates the employees of the managers that were created by the previous reconciliation. In this sample, there is only one employee (`employee1`).
++
+Check that the manager and employee entries were created correctly by selecting Manage > User. You should have two users — `manager1` and `user1`.
+
+
+. Verify the relationships between your new user and role objects:
++
+
+.. Click on `user1`. Note that the `Manager` field shows `manager1` for this user.
+
+.. On the Authorization Roles tab, note that user1 has two roles — `employee` and `openidm-authorized`.
+
+.. Click Back to User List then click on `manager1`. Note that the `Manager` field is empty for this user.
+
+.. On the Authorization Roles tab, note that manager1 has two roles — `manager` and `openidm-authorized`.
+
+
+. Verify the available workflows:
++
+
+.. Select Manage > Processes > Definitions.
+
+.. Click the Contractor onboarding process and look at the sequence diagram for this workflow.
+
+
+. Log out of the Admin UI by selecting Log Out from the top right dropdown menu.
+
+====
+OpenIDM is now prepared for the Provisioning sample. In the next section you will walk through the workflow whose sequence diagram you saw in the previous step.
+
+
+[#provisioning-sample-running]
+==== Running the Provisioning Sample
+
+As part of provisioning, employees are required to initiate a __Contractor Onboarding__ process. This process is a request to add a contractor to the managed user repository, with an option to include the contractor in the original data source (the XML file).
+
+When the employee has completed the required form, the request is sent to the manager for approval. Any user with the role `manager` can claim the approval task. If the request is approved, an email is sent to the address provided in the initial form, with a request for the contractor to reset their password. When the password reset has been completed, the contractor is created in the managed user repository. If a request was made to add the contractor to the original data source (the XML file) this is done when the manager approves the request.
+
+====
+
+. Log in to the Self-Service UI (`\https://localhost:8443/`) as the user you created in the previous section (`user1`), with password `Welcome1`.
++
+The user Dashboard is displayed:
++
+
+image::images/provisioning-dashboard.png[]
+
+. Initiate the provisioning workflow as user1:
++
+
+.. Under Processes, click the Details link next to the Contractor onboarding process and complete the form for the sample user you will be creating.
++
+Use an email address that you have access to because you will need the email that is sent to this address to complete the workflow.
++
+In the Provision to XML field, select Yes.
++
+This selection enables implicit synchronization from the managed user repository to the XML file.
++
+
+image::images/provisioning-add-user.png[]
++
+Note that user1 does not provide the password for this user. This is to ensure that only the actual contractor can log in with this account
+
+.. Click Start to initiate the process.
+
+.. Log out of the Self-Service UI.
+
+
+. Approve the workflow task as manager1:
++
+
+.. Log in to the Self-Service UI as `manager1`, with password `Welcome1`.
+
+.. Under My Group's Tasks, locate the Approve Contractor task and select Assign to Me.
+
+.. Approve Contractor is now listed under My Tasks.
++
+Click the Details link.
+
+.. Check the form content. (It is the same content that you provided as `user1`, along with a Decision field.)
++
+Select Accept and click Complete to finish the task.
+
+.. Log out of the Self-Service UI.
+
+
+. Verify that the contractor has been created in the XML file:
++
+Open `openidm/samples/workflow/data/xmlConnectorData.xml` and note the addition of the new contractor entry. Note that there is no value for ``. Note that `user1` is the contractor's manager.
++
+The following excerpt of the `xmlConnectorData.xml` shows a new user entry for bjensen, after the implicit synchronization to the XML file:
++
+
+[source, xml]
+----
+
+...
+ openidm-authorized
+ Barbara
+ user1
+ a02536cf-84b2-4d5c-a3ae-480f5e0899e9
+ bjensen
+ bjensen@example.com
+
+ Jensen
+
+----
+
+. Complete the password reset process:
++
+
+.. Check the inbox for the email address that you provided when you completed the initial form.
++
+You should have received an email with the subject "Reset your password".
+
+.. Open the password reset email and click Password reset link.
++
+The link takes you to the Self-Service UI, with the option to Reset Your Password.
+
+.. Enter a new password and confirmation password, submit the form.
++
+The password that you enter here must comply with the default password policy for managed users, described in xref:../integrators-guide/chap-passwords.adoc#enforce-password-policy["Enforcing Password Policy"] in the __Integrator's Guide__.
+
+.. Click Return to Login Page and log in with the username that you provided when you completed the initial form, and the new password you have just set.
++
+Notice the Welcome message under Notifications.
+
+
+. Verify that the password reset has been propagated to the XML file:
++
+Open `openidm/samples/workflow/data/xmlConnectorData.xml` and note that the password for the contractor has been added to their entry.
+
+====
+If you declined the approval request, the user is not created in either data source.
+
+
+
+[#workflow-use-cases]
+=== Workflow Use Cases
+
+This section describes a number of sample workflows, that demonstrate typical use cases for OpenIDM. The use cases, provided in `/path/to/openidm/samples/usecase`, work together to describe a complete business story, with the same set of sample data. Each of the use cases is integrated with the Self-Service UI.
+
+These use cases use OrientDB as a repository by default. Alternative repository configuration files are provided in `/path/to/openidm/samples/usecase/db`. If you want to use one of these alternative repositories, remove the `repo.orientdb.json` file from the `conf/` directory of the use case you are testing (for example, `samples/usecase/usecase1/conf/repo.orientdb.json`) and copy the appropriate JDBC repository configuration files (`datasource.jdbc-default.json` and `repo.jdbc.json`) into that `conf/` directory. For more information on using an alternative repository, see xref:../install-guide/chap-repository.adoc#chap-repository["Installing a Repository For Production"] in the __Installation Guide__.
+
+Each use case builds on the previous one. You __must__ run the use cases in order, from use case 1 through 3, before you try the remaining use cases. Use cases 2 onwards depend on the `hr_data.ldif` file that you import and reconcile when you run use case 1.
+
+The use cases assume an initial data set of twenty __ordinary__ managed users in OpenIDM (user.0 - user.19). The users are divided as follows:
+
+[cols="25%,25%,12%,25%,13%"]
+|===
+|Users |Department |Manager |Employees |Contractors
+|===
+In addition, the following __special__ users are defined:
+
+* `hradmin` - represents the human interaction of the HR department
+
+* `systemadmin` - represents the human interaction of the populated systems (Business and Project)
+
+* `superadmin` - represents the manager of the managers
+
+
+[NOTE]
+====
+Note that the `curl` commands in this section use the secure port for OpenIDM (8443) and assume a self-signed certificate named `self-signed.crt`, located in the directory from which the command is launched. For instructions on using the self-signed certificate that is generated when OpenIDM first starts up, see xref:../integrators-guide/chap-security.adoc#rest-over-https["Restrict REST Access to the HTTPS Port"] in the __Integrator's Guide__.
+====
+
+[#use-case-1]
+==== Use Case 1 - Initial Reconciliation
+
+This use case assumes an OpenDJ server and populates the managed user repository with users from OpenDJ.
+To set up the sample, install and configure OpenDJ, as follows:
+
+. Download and install OpenDJ, as described in link:../../../opendj/3.5/install-guide/#gui-install[To Install OpenDJ Directory Server With the GUI, window=\_blank].
++
+This sample assumes that OpenDJ is listening on port 1389, the standard LDAP port for users who cannot use privileged ports.
+
+. The use case assumes a user with DN `cn=Directory Manager` and password `password` who will bind to the directory server.
+
+. During the install, import the user data from the LDIF file `/path/to/openidm/samples/usecase/data/hr_data.ldif`.
++
+The OpenDJ server now contains the users required for all the workflow use cases.
+
+
+[#running-use-case1]
+.Running Use Case 1
+====
+
+. Start OpenIDM with the configuration for use case 1.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/usecase/usecase1
+----
+
+. Run reconciliation to populate the managed user repository with the users from the OpenDJ server.
++
+The validation rules in this workflow require a managed user's __manager__ entry to exist before that user can be created. You must therefore run three consecutive reconciliation operations:
++
+
+* The first reconciliation creates the `superadmin` user. This user has no manager entry, so is unaffected by the validation rules. Creation fails for the remaining users, because their manager does not yet exist.
+
+* The second reconciliation creates the 12 users who have the `superadmin` user as their manager. Creation fails for the remaining users, because they require the 12 manager users to exist before they can be created.
+
+* The third reconciliation creates the remaining 10 users, bringing the total number of users to 23.
+
++
+The easiest way to run reconciliation is from the Admin UI:
++
+
+.. Log in to the Admin UI (`\https://localhost:8443/admin`) as the administrative user (`openidm-admin`) with password `openidm-admin`.
+
+.. Select Configure > Mappings.
+
+.. Click on the only configured mapping (`systemHRAccounts_managedUser`) and click Reconcile Now.
+
++
+To run reconciliation from the command line, use the following command:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "https://localhost:8443/openidm/recon?_action=recon&mapping=systemHRAccounts_managedUser"
+{
+ "_id": "376b3290-24f0-47a9-8a9e-bba025536c39",
+ "state": "ACTIVE"
+ }
+----
+
+. Run reconciliation twice more to create all the users in the repository.
+
+. Query the managed users that were created by the three reconciliation operations.
++
+In the Admin UI, select Manage > User and note the new entries in the User List.
++
+Alternatively, run the following command to view the managed user entries over REST:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "https://localhost:8443/openidm/managed/user?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "user.7",
+ "_rev": "1"
+ },
+ {
+ "_id": "user.3",
+ "_rev": "1"
+ },
+ {
+ "_id": "user.4",
+ "_rev": "1"
+ },
+ ...
+ {
+ "_id": "systemadmin",
+ "_rev": "1"
+ },
+ {
+ "_id": "hradmin",
+ "_rev": "1"
+ },
+ {
+ "_id": "superadmin",
+ "_rev": "1"
+ }
+ ],
+ "resultCount": 23,
+ ...
+}
+----
++
+Your managed user repository should now contain 23 users. The default password of all the newly created users is `Passw0rd`.
+
+. Shut down OpenIDM before you proceed with the next use case.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./shutdown.sh
+----
+
+====
+
+
+[#use-case-2]
+==== Use Case 2 - New User Onboarding
+
+This use case demonstrates a new user onboarding process. The process can be initiated by any of the users created in the previous reconciliation process. In this example, we use `user.1` to initiate the process. `user.1` captures the details of a new user, and then submits the new user entry for approval by the prospective manager of that new user.
+
+The use case includes three separate workflows - onboarding (creation of the new user), sunrise (new user start date) and sunset (user end date).
+
+The use case also demonstrates email notification with the configuration of an external email service. You must configure the external email service, as described in xref:#configure-email-notification["Configuring Email Notification"], __before you start the workflow__.
+
+The use case demonstrates the OpenIDM Self-Service UI. If you deploy OpenIDM on the local system, you can access the UI at the following URL: `\https://localhost:8443`.
+
+[#configure-email-notification]
+.Configuring Email Notification
+====
+
+. Start OpenIDM with the configuration for use case 2.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/usecase/usecase2
+----
+
+. Configure the outbound email service.
++
+Log in to the Admin UI (`\https://localhost:8443/admin/`) as the default administrative user (`openidm-admin` with password `openidm-admin`).
++
+Select Configure > System Preferences > Email and provide connection information for your preferred email host.
++
+Gmail is configured by default but you must still supply credentials to use Gmail.
+
+. Log out of the Admin UI.
+
+. Change the notification email parameters in the workflow definition file. To edit the workflow definition file:
++
+
+.. Copy the workflow archive (`.bar`) file (`newUserCreate.bar`) to a temporary location, such as a `/tmp` directory:
++
+
+[source, console]
+----
+$ cd /path/to/openidm/samples/usecase/usecase2/workflow
+$ cp newUserCreate.bar /tmp/
+----
+
+.. Unzip the temporary workflow `.bar` file.
++
+This step extracts the workflow definition file (`newUserCreate.bpmn20.xml`) and two xhtml templates required by the workflow:
++
+
+[source, console]
+----
+$ unzip /tmp/newUserCreate.bar
+Archive: newUserCreate.bar
+ inflating: nUCDecideApprovalForm.xhtml
+ inflating: nUCStartForm.xhtml
+ inflating: newUserCreate.bpmn20.xml
+----
+
+.. Edit the extracted workflow definition file (`newUserCreate.bpmn20.xml`). The email parameters are towards the end of this file:
++
+
+[source, console]
+----
+$ cd /tmp
+$ grep emailParams newUserCreate.bpmn20.xml
+emailParams = [from : 'usecasetest@forgerock.com', to : 'notification@example.com',
+...
+----
++
+Change the `from` and `to` parameters to reflect valid email addresses.
+
+.. Zip up the amended workflow definition file, and the xhtml templates into a workflow `.bar` file.
++
+
+[source, console]
+----
+$ zip newUserCreate.bar newUserCreate.bpmn20.xml nUCDecideApprovalForm.xhtml nUCStartForm.xhtml
+updating: nUCDecideApprovalForm.xhtml (deflated 82%)
+updating: nUCStartForm.xhtml (deflated 82%)
+updating: newUserCreate.bpmn20.xml (deflated 85%)
+----
+
+.. Copy the new `.bar` file to the workflow directory, overwriting the existing `.bar` file.
++
+
+[source, console]
+----
+$ cp /tmp/newUserCreate.bar /path/to/openidm/samples/usecase/usecase2/workflow
+----
+
+
+====
+
+[#initiate-onboarding]
+.Initiating the Onboarding Workflow
+====
+
+. Start OpenIDM with the configuration for use case 2 (if you have not already done so when you configured the email service).
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/usecase/usecase2
+----
+
+. Log in to the Self-Service UI (`\https://localhost:8443`) as `user.1` with password `Passw0rd`.
++
+In this sample, any user who logs into the Self-Service UI can view the new User Onboarding Process workflow.
+
+. Click Details next to User Onboarding Process link and complete the fields for a sample new user.
++
+__Department__. Specifies one of four departments that the new user will belong to (Human Resources, Production Planning, Sales & Distribution, or Treasury & Payments). The value you select here determines the __manager__ of the new user, to which the request will be sent for approval. (See the previous table of users for a list of the managers of each department.)
++
+__User Type__. Governs user access to specific accounts. If the User Type is Employee, the new user will have access to an account named Business. This access is represented as an attribute of the managed user entry in the OpenIDM repository, as follows: `accounts : ["Business"]`. If the User Type is Contractor, the new user will have no accounts associated with its managed user entry in OpenIDM.
++
+__Send Email Notification__. Indicates whether an email should be sent to alert the manager of the new required approval. The email details used here are defined when you configure email notification, as described in xref:#configure-email-notification["Configuring Email Notification"]. If you select not to send an email notification, the notification is simply added to the OpenIDM repository, and appears when the manager logs into the UI.
+
+. Click Start to initiate the onboarding workflow.
++
+This action sends the new user request to the corresponding __management__ users (the department manager, as well as the `superadmin` user, who is an overall manager).
+
+. Log out of the UI, and log back in as the management user of the department that you selected when you completed the new user form. For example, if you selected Human Resources, log in as `user.0`, which simulates the management user for the HR department. All users have the password `Passw0rd`.
++
+Notice that the management user now has an Onboarding Approval task in the queue of tasks assigned to that user's group.
++
+
+image::images/approval-task.png[]
+
+. Select Assign to Me from list next to the Onboarding Approval task.
++
+This action __claims__ the task for `user.0`, removes it from the group queue, and places it in the list of pending tasks for `user.0`.
+
+. Select Details next to the Onboarding Approval task under My Tasks.
++
+The complete new user request is displayed for the manager's approval. As the manager, you can add any information that was missing from the original request.
++
+In addition, you can specify the following information for the new user.
++
+
+* __Start Date__. Completing this field results in the user being created, with a `startDate` added to that user's managed user entry. The status of the user is `inactive`. This field is optional, and is used by the task scanner to trigger the Sunrise workflow.
+
+* __End Date__. Completing this field results in the user being created, with an `endDate` added to that user's managed user entry. The field is optional, and is used by the task scanner to trigger the Sunset workflow.
+
+* __Decision__. Selecting Reject here terminates the workflow and sends a notification to the user who initiated the workflow. Selecting Accept creates the managed user entry in OpenIDM. The password of the new user is `Passw0rd`.
++
+Two notifications are created when the request is accepted - one for the user who initiated the workflow, and one for the newly created user. The notifications are visible in the UI after login. If you selected email notification, one email is sent to the user that you defined when you configured email notification, as described in xref:#configure-email-notification["Configuring Email Notification"].
+
+
+. At the bottom of the form, there is an option either to Requeue the request or to Complete it. Click Complete.
++
+If you click Requeue here, the task is removed from the list of that user's tasks, and returned to the list of tasks pending for that group. The task can then be claimed by any member of that group.
++
+When the new user request has been approved, the user is created in the OpenIDM repository. If you did not include a Start Date in the manager approval, you should now be able to log into the UI with the details of the new user. If you included a Start Date, you need to complete the sunrise workflow before the user account is active (which will enable you to log in as this user).
+
+====
+
+[#initiate-sunrise]
+.Initiating the Sunrise Workflow
+====
+If a sunrise date is specified for the new user, the user is created in the repository, with an `inactive` account status.
+
+* To trigger the sunrise workflow (which activates the account), enable the sunrise task scanning schedule. The schedule is disabled by default.
++
+Modify the schedule configuration file (`schedule-taskscan_sunrise.json`), setting the `enabled` property to `true`.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ grep "enabled" samples/usecase/usecase2/conf/schedule-taskscan_sunrise.json
+"enabled" : true,
+----
++
+The scan runs every minute, and checks the repository for users that have a sunrise date that is anything up to one day after the current date. When the scan is triggered, it locates the newly created user and starts the sunrise workflow on this user. The workflow takes the following actions:
++
+
+** Changes the account status of the user to `active`.
++
+You can check that this part of the workflow has completed by looking at the user's account in the Admin UI:
++
+
+. Log in to the Admin UI as `openidm-admin` with password `openidm-admin`, then select Manage > User.
+
+. Click on the account of the new user that was created in the previous section and note their Status.
++
+The following image shows the status for the new user jdoe, who was created by the previous workflow:
++
+
+image::images/new-user.png[]
+
++
+
+** Generates a notification for the new user, which is visible when the user logs into the Self-Service UI.
+
++
+
+image::images/workflow-notifications.png[]
+
+====
+
+[#initiate-sunset]
+.Initiating the Sunset Workflow
+====
+If a sunset date is set for the new user, you can trigger the sunset workflow to deactivate the user account when the end of his work period is reached.
+
+. To trigger the sunset workflow, enable the sunset task scanning schedule. The schedule is disabled by default.
++
+Modify the schedule configuration file (`schedule-taskscan_sunset.json`), setting the `enabled` property to `true`.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ grep "enabled" samples/usecase/usecase2/conf/schedule-taskscan_sunset.json
+
+"enabled" : true,
+----
++
+The scan runs every minute, and checks the repository for users that have a sunset date that is anything up to one day after the current date. When the scan is triggered, it locates users whose contracts are about to end, and starts the sunset workflow on these users. When the workflow is initiated, it assigns a task to the manager of the affected user. In our example, the task is assigned to `user.0`.
+
+. When the sunset schedule has been enabled, log in to the Self-Service UI as `user.0` (with password `Passw0rd`). If the user's sunset date is within one day of the current date, a Contract Termination task becomes available under the manager's My Group's Tasks section.
++
+Select the contract termination task and click Details.
+
+. In the Decision field, select either Accept termination or Modify date, then click Complete.
++
+When you accept the termination, the user's account status is set to `inactive` and the HR administrative user receives a notification to that effect, the next time that user logs into the UI. The deactivated user is no longer able to log into the UI.
++
+If you select to modify the date, the sunset date of that user is changed to the value that you specify in the End Date field on that form. The management user receives a UI notification that the employee's contract has been extended.
+
+. Shut down OpenIDM before you proceed with the next use case.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./shutdown.sh
+----
+
+====
+
+
+[#use-case-3]
+==== Use Case 3 - User Access Request
+
+This use case simulates a user access request, with two levels of approval for the request.
+
+If you want to use email notification with this workflow, start OpenIDM with the configuration for Use Case 3, then follow the instructions in xref:#configure-email-notification["Configuring Email Notification"], substituting and `usecase3/workflow/accessRequest.bpmn20.xml` for the file described in that procedure.
+
+====
+
+. Start OpenIDM with the configuration for use case 3, if you have not already started the server to configure email notification.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/usecase/usecase3
+----
+
+. Log into the Self-Service UI as `user.1` with password `Passw0rd`.
++
+`user.1` belongs to the HR department and, in this workflow, is requesting access to a Project system.
+
+. Click Details next to the Access request process and click Start to start the workflow.
++
+A User Access Request appears in the list of tasks for `user.1`.
+
+. Click Details next to the User Access Request task.
++
+The resulting form indicates the various systems to which the user may request access.
++
+__Access to Business system__. This field reflects the current value of the `accounts` property for that user in the repository. If the value includes `Business` this field is True.
++
+__Access to Project system__. Set this field to True to request Project access for `user.1`.
++
+__Send Email Notification__. Set to True to send an email to alert the manager of the new access request. The email details used here are defined when you configure email notification, as described in xref:#configure-email-notification["Configuring Email Notification"]. If you select not to send an email notification, the notification appears when the manager logs into the UI.
++
+Select either Cancel, to terminate the process, or Request, to start a user task, assigned to the manager of the user who is requesting access (`user.0` in this example), and select Complete.
+
+. Log out of the Self-Service UI and log back in as the manager (`user.0` with password `Passw0rd`).
+
+. Under My Group's Tasks, locate the User Access Request Approval task and select Assign to me.
++
+Note that the User Access Request Approval task has moved under My Tasks.
+
+. Click Details next to the User Access Request Approval task.
+
+. The details of the access request are displayed. The manager is able to modify the access rights. Select Accept or Reject to approve or deny the request.
++
+Rejecting the request results in a notification being sent to the user who made the request. If you have enabled email notification, a single email is sent to the account that you specified when you configured email notification.
++
+Accepting the request initiates a second approval task, assigned to the `systemadmin` user.
++
+
+image::images/ui-access-request.png[]
++
+Click Complete to complete the task.
+
+. Log out of the UI and log in as the `systemadmin` user (with password `Passw0rd`).
++
+This user now has one User Access Request Approval task in his queue.
+
+. Select the task and click Details.
++
+This task interface is similar to that of the task that was assigned to the manager.
++
+Rejecting the request results in a notification being sent to the user who made the request.
++
+Accepting the request updates the managed/user record in OpenIDM, to reflect the approved access changes.
++
+If you have enabled email notification, a single email is sent to the account defined when you configured the external email service (xref:#configure-email-notification["Configuring Email Notification"]), indicating whether the request has been accepted or rejected.
+
+====
+Note that this sample includes an __escalation__ step that is attached to the manager approval task. If the manager does not complete assessment of the user task within ten minutes of its initiation, a new user task is created and assigned to the `superadmin` user. This task has the same interface and functionality as the task assigned to the manager. Accordingly, when the `superadmin` user completes the task, the execution is passed to the `systemadmin` user for approval.
+
+Shut down OpenIDM before you proceed with the next use case.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./shutdown.sh
+----
+
+
+[#use-case-4]
+==== Use Case 4 - Orphan Account Detection
+
+This use case demonstrates two asynchronous tasks, started from a reconciliation process:
+
+* Detecting orphan accounts on a target object set
+
+* Handling ambiguous results during correlation
+
+This use case relies on a customized synchronization configuration (mapping) file, named `syncManagedBusiness.json`, in the `/path/to/openidm/samples/usecase/usecase4/conf` directory.
+
+This file defines a mapping (`recon_managedUser_systemBusiness`) between a source (managed users) and a target object set. The target object set is defined in the file `samples/usecase/usecase4/data/business.csv`. The `business.csv` file includes all users from the initial reconciliation (described in xref:#use-case-1["Use Case 1 - Initial Reconciliation"]). These users are categorized as `employees`, and therefore include the property `"accounts" : ["Business"]` in their managed user entry (see xref:#use-case-2["Use Case 2 - New User Onboarding"] for an explanation of the User Type).
+
+The mapping includes the following `"validSource"` field:
+
+[source]
+----
+"validSource" : {
+ "type" : "text/javascript",
+ "file" : "script/isSourceValidBusiness.js"
+},
+----
+This field references a script which specifies that only those users who are employees are taken into account during the reconciliation.
+
+In addition, the `business.csv` file includes the following users:
+
+* `user.50`. This user is defined __only__ in the .csv file, and not in the managed/user repository. When a reconciliation operation is run, this user is detected as an __orphan account__. The orphan account workflow is triggered when an "UNQUALIFIED" or "UNASSIGNED" situation is encountered, as indicated in this section of the mapping:
++
+
+[source]
+----
+{
+ "situation" : "UNQUALIFIED",
+ "action" : {
+ "workflowName" : "orphanAccountReport",
+ "type" : "text/javascript",
+ "file" : "workflow/triggerWorkflowFromSync.js"
+ }
+},
+{
+ "situation" : "UNASSIGNED",
+ "action" : {
+ "workflowName" : "orphanAccountReport",
+ "type" : "text/javascript",
+ "file" : "workflow/triggerWorkflowFromSync.js"
+ }
+}
+----
+
+* `user.33`. This user has a `"userName"` attribute of `"user.3"` (which is the same as the `"userName"` attribute of the user, `user.3`). The correlation query of the reconciliation operation is based on the `"userName"` attribute. During the correlation query, two candidate users are therefore correlated with the same managed user (`user.3`), and the result is ambiguous. The manual match workflow is triggered when an "AMBIGUOUS" situation is encountered, as indicated in this section of the mapping:
++
+
+[source]
+----
+{
+ "situation" : "AMBIGUOUS",
+ "action" : {
+ "workflowName" : "manualMatch",
+ "type" : "text/javascript",
+ "file" : "workflow/triggerWorkflowFromSync.js"
+ }
+}
+----
+
+
+====
+
+. Before you start with this use case, rename the mapping file to `sync.json`.
++
+
+[source, console]
+----
+$ cd /path/to/openidm/samples/usecase/usecase4/conf
+$ mv syncManagedBusiness.json sync.json
+----
+
+. Start OpenIDM with the configuration for use case 4.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/usecase/usecase4
+----
+
+. Reconcile the managed user repository with the CSV file, either by using the Admin UI or over the command line.
++
+To use the Admin UI, log in to the Admin UI (`\https://localhost:8443/admin/`) as `openidm-admin` with password `openidm-admin`. Select Configure > Mappings, click on the mapping `recon_managedUser_systemBusiness` and click Reconcile Now.
++
+To use the command line, run the following command:
++
+
+[source, console]
+----
+$ curl \
+ --cacert self-signed.crt \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "https://localhost:8443/openidm/recon?_action=recon&mapping=recon_managedUser_systemBusiness"
+----
++
+When the reconciliation operation finds the ambiguous entry (`user.3`) and the orphan entry (`user.50`) in the CSV file, two asynchronous workflows are launched (`manualMatch` and `orphanAccountReport`), as indicated in the mapping file, described previously.
+
+. Log in to the Self-Service UI (`\https://localhost:8443`) as the `systemadmin` user, with password `Passw0rd`.
+
+. Next to the Manual Linking Task in the My Tasks list, click Details.
++
+The __Possible targets__ field presents a list of target entries to which the ambiguous record can be linked. In this example, `user.3 - Atrc, Aaron` and `user.33 - Atrc, Aaron` are the two candidate users found in the target object set by the correlation query. When you select one of these values, the workflow manually links the managed user (`user.3`) to the selected user.
++
+Click complete to finish the manual account linking task.
++
+If you select Ignore, here, no action is taken (no link is created), and the workflow terminates.
+
+. Next to the Orphan Account Task in the My Tasks list, click Details.
++
+The __Link to__ field enables you to enter an existing managed user ID to which this orphan account should be linked. For the purposes of this example, enter `user.5`.
++
+In the Decision field, select Link to link the orphan account to the ID that you entered in the previous step. Click Complete to complete the task.
++
+Selecting Delete here deletes the user from the target object set (the CSV file in this case) and terminates the workflow.
+
+. Shut down OpenIDM before you proceed with the next use case.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./shutdown.sh
+----
+
+====
+
+[NOTE]
+====
+Use Case 5 has been removed from the sample use cases.
+====
+
+
+[#use-case-6]
+==== Use Case 6 - Password Change Reminder
+
+This use case demonstrates using the task scanner to trigger a password change reminder workflow for managed users.
+
+In this example, each managed user entry in OpenIDM has a dedicated attribute, `lastPasswordSet`, that stores the date on which the password was last changed. The value of this attribute is updated by an `onStore` script, defined in the managed user configuration file (`conf/managed.json`), as follows:
+
+[source]
+----
+"onStore" : {
+ "type" : "text/javascript",
+ "file" : "script/onStoreManagedUser.js"
+},
+----
+When a new password is stored for a user, the script sets the date on which this change was made. The task scanner periodically scans the `lastPasswordSet` attribute, and starts the workflow if the password was changed more than an hour ago. This condition is configured in the schedule configuration file (`schedule-taskscan_passwordchange.json`):
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ more samples/usecase/usecase6/conf/schedule-taskscan_passwordchange.json
+
+...
+"condition" : {
+ "before" : "${Time.now - 1h}"
+},
+....
+----
+Obviously, in a real deployment, the period between required password changes would be longer, and this value would need to be set accordingly. For the purposes of testing this use case, you might want to set the value to a shorter period, such as `"${Time.now - 1m}"`, which will send the notification one minute after a password change.
+
+By default, the workflow sends notifications to the user entry, visible when the user logs into the UI. If you want notifications sent by email, configure the external email service, as follows:
+
+====
+
+. Set up outbound email, as described in xref:#configure-email-notification["Configuring Email Notification"].
+
+. Enable email notification in the script file that starts the workflow (`samples/usecase/usecase6/script/passwordchange.js`). For example:
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ more samples/usecase/usecase6/script/passwordchange.js
+
+/*global objectID*/
+
+(function () {
+ var params = {
+ "userId" : objectID,
+ "emailEnabled" : "true",
+ "_key": "passwordChangeReminder"
+};
+----
+
+. Make sure that all managed users have a valid email address as the value of their `mail` attribute.
+
+====
+The task scanning schedule is disabled by default. To test this use case, follow these steps:
+
+====
+
+. Enable the task scanning schedule by setting `enabled` to `true` in the schedule configuration file (`schedule-taskscan_passwordchange.json`).
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ more samples/usecase/usecase6/conf/schedule-taskscan_passwordchange.json
+{
+ "enabled" : true,
+...
+----
+
+. Start OpenIDM with the configuration for use case 6.
++
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/usecase/usecase6
+----
++
+
+. Log in to the Self-Service UI as any of the users listed in the introduction to this section (for example, `user.4`, with password `Passw0rd`).
++
+You will see a password expiration notice.
++
+If you ignore the change for five minutes, you will see a second warning, that the password is about to expire.
++
+If you ignore that second warning for another two minutes, the user's account is deactivated.
+
+. (Optional) To avoid the second notification, or the account deactivation, you can change the user password through the UI, as follows:
++
+
+.. Log in to the UI as the user whose password you want to change and click Change Password at the top right of the page.
+
+.. Enter a new password that conforms to the requirements of the password policy.
+
+.. Enter the old password (in this case `Passw0rd`).
+
+
+====
+
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/chap-xml-samples.adoc b/openidm-doc/src/main/asciidoc/samples-guide/chap-xml-samples.adoc
new file mode 100644
index 000000000..e0a62d83e
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/chap-xml-samples.adoc
@@ -0,0 +1,823 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[#chap-xml-samples]
+== XML Samples - Reconciling Data Between OpenIDM and an XML File
+
+This chapter walks you through the XML samples (those samples labeled Sample 1, Sample 8, and Sample 9 in the `openidm/samples` directory. For a complete list of the samples provided with OpenIDM, and an overview of each sample, see xref:chap-overview.adoc#chap-overview["Overview of the OpenIDM Samples"].
+
+[#more-sample-1]
+=== First OpenIDM Sample - Reconciling an XML File Resource
+
+This chapter provides an overview of the first sample and how it is configured. For a complete list of the samples provided with OpenIDM, and an overview of each sample, see xref:chap-overview.adoc#chap-overview["Overview of the OpenIDM Samples"] or the README in the `openidm/samples` directory.
+
+[#about-the-sample]
+==== About the XML Sample
+
+OpenIDM connects data objects held between resources by mapping one object to another. To connect to external resources, OpenIDM uses link:http://openicf.forgerock.org[OpenICF, window=\_top] connectors, configured for use with each external resource.
+
+When objects in one external resource change, OpenIDM determines how the changes affect other objects, and can make the changes as necessary. This sample demonstrates how OpenIDM does this by using __reconciliation__. OpenIDM reconciliation compares the objects in one object set to mapped objects in another object set. For a complete explanation of reconciliation and synchronization, see xref:../integrators-guide/chap-synchronization.adoc#sync-types["Types of Synchronization"] in the __Integrator's Guide__.
+
+This sample connects to an XML file that holds sample user data. The XML file is configured as the authoritative source. In this sample, users are created in the local repository to show you how you can manage local users through the REST APIs as well as through the OpenIDM UI.
+
+You can also use OpenIDM without storing managed objects for users in the local repository, instead reconciling and synchronizing objects directly through connectors to external resources.
+
+This sample involves only one external resource. In practice, you can connect as many resources as needed for your deployment.
+[#about-the-sample-configuration]
+.Sample Configuration Files
+--
+You can find configuration files for the sample under the `openidm/samples/sample1/conf` directory. As you review the sample, keep the following in mind:
+
+* Start OpenIDM with the configuration associated with Sample 1:
++
+
+[source, console]
+----
+$ ./startup.sh -p samples/sample1
+----
++
+For more information, see xref:#install-sample1["Install the Sample"].
+
+* OpenIDM regularly scans for any scheduler configuration files in the `conf` directory.
+
+* OpenIDM's reconciliation service reads the mappings and actions for the source and target users from `conf/sync.json`.
+
+* When you initiate a reconciliation, OpenIDM queries all users in the source, and then creates, deletes, or modifies users in the local OpenIDM repository as mapped in `conf/sync.json`.
+
+* OpenIDM writes all operations to the audit logs in both the internal database and also the flat files in the `openidm/audit` directory.
+
+* The default Sample 1 version of the `conf/authentication.json` file includes several authentication modules: `STATIC_USER`, `MANAGED_USER`, `INTERNAL_USER`, and `CLIENT_CERT`. For more information, see xref:../integrators-guide/chap-auth.adoc#supported-auth-modules["Supported Authentication Modules"] in the __Integrator's Guide__.
+
+When you start OpenIDM with the `-p` project variable (`./startup.sh -p samples/sample1`), the `&{launcher.project.location}` is set to a value of `samples/sample1`. The configuration files use this location, as shown in the following sections.
+
+The following configuration files play important roles in this sample:
+
+`samples/sample1/conf/provisioner.openicf-xml.json`::
+This connector configuration file serves as the XML file resource. It is a copy of the file of the same name found in the `samples/provisioners` directory.
+
++
+In this sample, the connector instance acts as the authoritative source for users. In the configuration file you can see that the `xmlFilePath` is set to `&{launcher.project.location}/data/xmlConnectorData.xml`.
+
++
+The `&{launcher.project.location}`, in this case, is `sample/sample1`.
+
++
+For details on the OpenICF connector configuration files, see xref:../integrators-guide/chap-resource-conf.adoc#chap-resource-conf["Connecting to External Resources"] in the __Integrator's Guide__.
+
+`samples/sample1/conf/schedule-reconcile_systemXmlAccounts_managedUser.json`::
+The sample schedule configuration file defines a reconciliation job that, if enabled by setting `"enabled" : true`, starts a reconciliation each minute for the mapping named `systemXmlAccounts_managedUser`. The mapping is defined in the configuration file, `conf/sync.json`:
++
+
+[source, javascript]
+----
+{
+ "enabled" : false,
+ "type": "cron",
+ "schedule": "0 0/1 * * * ?",
+ "persisted" : true,
+ "misfirePolicy" : "fireAndProceed",
+ "invokeService": "sync",
+ "invokeContext": {
+ "action": "reconcile",
+ "mapping": "systemXmlfileAccounts_managedUser"
+ }
+}
+----
++
+For information about the schedule configuration, see xref:../integrators-guide/chap-scheduler-conf.adoc#chap-scheduler-conf["Scheduling Tasks and Events"] in the __Integrator's Guide__.
+
++
+Apart from the scheduled reconciliation run, you can also start the reconciliation run through the REST interface. The call to the REST interface is an HTTP POST such as the following:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemXmlfileAccounts_managedUser&waitForCompletion=true"
+----
++
+The `waitForCompletion=true` parameter specifies that the operation should return only when it has completed.
+
+`samples/sample1/conf/sync.json`::
+This sample configuration file defines the configuration for reconciliation and synchronization. The `systemXmlAccounts_managedUser` is the mapping for the reconciliation. This entry in `conf/sync.json` defines the synchronization mappings between the XML file connector (source) and the local repository (target):
++
+
+[source, javascript]
+----
+{
+ "mappings": [
+ {
+ "name": "systemXmlfileAccounts_managedUser",
+ "source": "system/xmlfile/account",
+ "target": "managed/user",
+ "correlationQuery": {
+ "type": "text/javascript",
+ "source": "var query = {'_queryId' : 'for-userName',
+ 'uid' : source.name};query;"
+ },
+ "properties": [
+ {
+ "source": "email",
+ "target": "mail"
+ },
+ {
+ "source": "firstname",
+ "target": "givenName"
+ },
+ {
+ "source": "lastname",
+ "target": "sn"
+ },
+ }
+ "source": "description",
+ "target": "description"
+ },
+ {
+ "source": "_id",
+ "target": "_id"
+ },
+ {
+ "source": "name",
+ "target": "userName"
+ },
+ {
+ "source": "password",
+ "target": "password"
+ },
+ {
+ "source" : "mobileTelephoneNumber",
+ "target" : "telephoneNumber"
+ },
+ {
+ "source" : "roles",
+ "transform" : {
+ "type" : "text/javascript",
+ "source" : "var _ = require('lib/lodash'); _.map(source.split(','),
+ function(role) { return {'_ref': 'repo/internal/role/' + role} });"
+ },
+ "target" : "authzRoles"
+ }
+ ],
+ "policies": [
+ {
+ "situation": "CONFIRMED",
+ "action": "UPDATE"
+ },
+ {
+ "situation": "FOUND",
+ "action": "IGNORE"
+ },
+ {
+ "situation": "ABSENT",
+ "action": "CREATE"
+ },
+ {
+ "situation": "AMBIGUOUS",
+ "action": "IGNORE"
+ },
+ {
+ "situation": "MISSING",
+ "action": "IGNORE"
+ },
+ {
+ "situation": "SOURCE_MISSING",
+ "action": "IGNORE"
+ },
+ {
+ "situation": "UNQUALIFIED",
+ "action": "IGNORE"
+ },
+ {
+ "situation": "UNASSIGNED",
+ "action": "IGNORE"
+ }
+ ]
+ }
+ ]
+}
+----
++
+Source and target paths that start with `managed`, such as `managed/user`, always refer to objects in the local OpenIDM repository. Paths that start with `system`, such as `system/xmlfile/account`, refer to external objects, in this case, objects in the XML file.
+
++
+For more information about synchronization, reconciliation, and `sync.json`, see xref:../integrators-guide/chap-synchronization.adoc#chap-synchronization["Synchronizing Data Between Resources"] in the __Integrator's Guide__.
+
++
+For additional examples related to scripting, see the xref:../integrators-guide/appendix-scripting.adoc#appendix-scripting["Scripting Reference"] in the __Integrator's Guide__.
+
+--
+
+
+[#install-sample1]
+==== Install the Sample
+
+Start OpenIDM with the configuration for Sample 1:
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample1
+----
+
+
+[#sample1-adminui]
+==== Review the Sample in the Administrative User Interface
+
+OpenIDM includes a web-based Administrative User Interface, known as the Admin UI. For details, see xref:../integrators-guide/chap-ui.adoc#ui-admin["Configuring OpenIDM from the Admin UI"] in the __Integrator's Guide__.
+
+After starting OpenIDM, you can access the Admin UI by navigating to `\https://localhost:8443/admin`. The first time you log in, use the default administrative credentials, (Login: openidm-admin, Password: openidm-admin).
+
+[WARNING]
+====
+To protect your deployment in production, change the default administrative password. To do so, navigate to the Self-Service UI at `\https://localhost:8443/` and click Change Password.
+====
+You should now see the Dashboard screen, with quick start cards for common administrative tasks. with the connectors and managed objects associated with that configuration.
+
+
+[#sample-running-reconciliation]
+==== Running Reconciliation
+
+Reconcile the objects in the resources, either by setting `"enabled" : true` in the schedule configuration file (`conf/schedule-reconcile_systemXmlAccounts_managedUser.json`) and then waiting until the scheduled reconciliation happens, or by using the REST interface, as shown in the following example:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemXmlfileAccounts_managedUser&waitForCompletion=true"
+----
+Successful reconciliation returns a reconciliation run ID, and the status of the reconciliation operation, as follows:
+
+[source, console]
+----
+{
+ "_id":"2d87c817-3d00-4776-a705-7de2c65937d8",
+ "state":"SUCCESS"
+}
+----
+Alternatively, you can run the same reconciliation in the Admin UI:
+
+. Click Configure > Mappings.
++
+For Sample 1, you should see one mapping, `systemXmlfileAccounts_managedUser`.
+
+. Select Edit to access the configuration options associated with reconciliation.
+
+. To run the reconciliation, click Reconcile Now.
+
+
+[#d5830e860]
+image::images/admin-ui-mappings-sample1.png[]
+
+
+[#sample-viewing-users-logs]
+==== Viewing Users and Logs
+
+After reconciliation, you can use the Admin UI to display user records in both the source and target resources:
+
+. Navigate to the URL where OpenIDM is installed.
++
+If it is local, navigate to `\https://localhost:8443/admin`.
+
+. Click Configure > Mappings, then select the only available mapping (`systemXmlfileAccounts_managedUser`)
+
+. On the Association tab, you should see the result of the reconciliation, from source to target, at the bottom of the screen.
+
+You can also use the REST interface to display all users in the local repository. Use a REST client to perform an HTTP GET on the following URL: `\http://localhost:8080/openidm/managed/user?_queryId=query-all-ids` with the headers `"X-OpenIDM-Username: openidm-admin"` and `"X-OpenIDM-Password: openidm-admin"`.
+
+OpenIDM returns JSON data. Depending on the browser, you can use a REST client to display the JSON or download it as a file. Alternatively, you can use the following link:http://curl.haxx.se/[curl, window=\_top] command to get the JSON response:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryId=query-all-ids"
+
+ {
+ "result": [
+ {
+ "_id": "scarter",
+ "_rev": "1"
+ },
+ {
+ "_id": "bjensen",
+ "_rev": "1"
+ }
+ ],
+...
+}
+----
+In addition to querying the users by their ID, you can set up arbitrary queries. For more information about using query expressions in a REST call, see xref:../integrators-guide/chap-data.adoc#queries["Defining and Calling Queries"] in the __Integrator's Guide__.
+
+Now try a RESTful GET of user `bjensen` by appending the user ID to the managed user URL (`\http://localhost:8080/openidm/managed/user/`):
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user/bjensen"
+ {
+ "_id": "bjensen",
+ "_rev": "1",
+ "mail": "bjensen@example.com",
+ "givenName": "Barbara",
+ "sn": "Jensen",
+ "description": "Created By XML1",
+ "userName": "bjensen@example.com",
+ "telephoneNumber": "1234567",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
+The complete user record is returned. If you need this level of information for all users, substitute `query-all` for `query-all-ids`.
+
+You can filter the output with the query expressions described in xref:../integrators-guide/chap-data.adoc#queries["Defining and Calling Queries"] in the __Integrator's Guide__.
+
+As defined in the mapping file `conf/sync.json`, the `sn` and `mail` parameters correspond to surname (sn) and email address, respectively.
+
+For example, the following RESTful GET filters output by surname (sn):
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=true&_fields=sn"
+
+ {
+ "result": [
+ {
+ "_id": "scarter",
+ "_rev": "1",
+ "sn": "Carter"
+ },
+ {
+ "_id": "bjensen",
+ "_rev": "1",
+ "sn": "Jensen"
+ }
+ ],
+...
+}
+----
+Now that you have a list of users, you can add more fields to your query:
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryFilter=true&_fields=sn,mail,description"
+
+ {
+ "result": [
+ {
+ "_id": "scarter",
+ "_rev": "1",
+ "sn": "Carter",
+ "mail": "scarter@example.com",
+ "description": "Created By XML1"
+ },
+ {
+ "_id": "bjensen",
+ "_rev": "1",
+ "sn": "Jensen",
+ "mail": "bjensen@example.com",
+ "description": "Created By XML1"
+ }
+ ],
+...
+}
+----
+This information is also available in the CSV format audit logs located in the `openidm/audit` directory:
+
+[source, console]
+----
+$ ls /path/to/openidm/audit/
+access.csv activity.csv recon.csv
+----
+For more information about the contents of each of these files, see xref:../integrators-guide/chap-auditing.adoc#audit-log-topics["Audit Log Event Topics"] in the __Integrator's Guide__.
+
+You can get a similar level of information for each user. For example, after running reconciliation, follow the instructions in xref:#sample-viewing-users-logs["Viewing Users and Logs"], and review information from the reconciled linked resource.
+
+
+[#sample-adding-users-resource]
+==== Adding Users in a Resource
+
+Add a user to the source connector XML data file to see reconciliation in action. During the next reconciliation, OpenIDM finds the new user in the source connector, and creates the user in the local repository.
+
+====
+
+. To add the user copy the following XML into `openidm/samples/sample1/data/xmlConnectorData.xml`:
++
+
+[source, xml]
+----
+
+ tmorris
+ tmorris@example.com
+ Toni
+ Morris
+ tmorris@example.com
+ 1234567
+ openidm-authorized
+ Created By XML1
+
+----
+
+. Run reconciliation again, as described in xref:#sample-running-reconciliation["Running Reconciliation"].
+
+. After reconciliation has run, query the local repository to see the new user appear in the list of all managed users:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryId=query-all-ids"
+{
+ "result": [
+ {
+ "_id": "scarter",
+ "_rev": "2"
+ },
+ {
+ "_id": "bjensen",
+ "_rev": "2"
+ },
+ {
+ "_id": "tmorris",
+ "_rev": "1"
+ }
+ ],
+...
+}
+----
+
+====
+To see what happened during the reconciliation operation, look at the reconciliation audit log, `openidm/audit/recon.csv`. This formatted excerpt from the log covers the two reconciliation runs done in this sample:
+
+[source, csv]
+----
+"_id", "action",...,"reconId","situation","sourceObjectId", "targetObjectId","timestamp";
+"7e...","CREATE",...,"486...", "ABSENT", "system/xmlfile/acc.../bjensen","managed/user/bjensen",...;
+"1a...","CREATE",...,"486...", "ABSENT", "system/xmlfile/acc.../scarter","managed/user/scarter",...;
+"33...","UPDATE",...,"aa9...", "CONFIRMED","system/xmlfile/acc.../bjensen","managed/user/bjensen",...;
+"1d...","UPDATE",...,"aa9...", "CONFIRMED","system/xmlfile/acc.../scarter","managed/user/scarter",...;
+"0e...","CREATE",...,"aa9...", "ABSENT", "system/xmlfile/acc.../tmorris","managed/user/tmorris",...;
+----
+The relevant audit log fields in this example are: action, situation, `sourceObjectId`, and `targetObjectId`. For each object in the source, reconciliation leads to an action on the target.
+
+In the first reconciliation run (abbreviated `reconID` is shown as `486...`), the source object does not exist in the target, resulting in an ABSENT situation and an action to CREATE the object in the target. The object created earlier in the target does not exist in the source, and so is IGNORED.
+
+In the second reconciliation run (abbreviated `reconID` is shown as `aa9...`), after you added a user to the source XML, OpenIDM performs an UPDATE on the user objects `bjensen` and `scarter` that already exist in the target. OpenIDM performs a CREATE on the target for the new user (`tmorris`).
+
+You configure the action that OpenIDM takes based on an object's situation in the configuration file, `conf/sync.json`. For the list of all possible situations and actions, see xref:../integrators-guide/chap-synchronization.adoc#chap-synchronization["Synchronizing Data Between Resources"] in the __Integrator's Guide__.
+
+For details about auditing, see xref:../integrators-guide/chap-auditing.adoc#chap-auditing["Using Audit Logs"] in the __Integrator's Guide__.
+
+
+[#sample-adding-users-rest]
+==== Adding Users Over REST
+
+You can add users to the local repository over the REST interface. The following example adds a user named James Berg.
+
+Create `james` (UNIX):
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{
+ "_id":"jberg",
+ "userName":"jberg",
+ "sn":"Berg",
+ "givenName":"James",
+ "mail":"jberg@example.com",
+ "telephoneNumber":"5556787",
+ "description":"Created by OpenIDM REST.",
+ "password":"MyPassw0rd"
+ }' \
+ "http://localhost:8080/openidm/managed/user?_action=create"
+ {
+ "_id": "jberg",
+ "_rev": "1",
+ "userName": "jberg",
+ "sn": "Berg",
+ "givenName": "James",
+ "mail": "jberg@example.com",
+ "telephoneNumber": "5556787",
+ "description": "Created by OpenIDM REST.",
+ "accountStatus": "active",
+ "effectiveRoles": [],
+ "effectiveAssignments": []
+}
+----
+Create `james` (Windows):
+
+[source, console]
+----
+C:\> curl ^
+ --header "X-OpenIDM-Username: openidm-admin" ^
+ --header "X-OpenIDM-Password: openidm-admin" ^
+ --header "Content-Type: application/json" ^
+ --request POST ^
+ --data "{\"_id\":\"jberg\",\"userName\":\"jberg\",\"sn\":\"Berg\",\"givenName\":\"James\",\"email\":\"jberg@example.com\",\"telephoneNumber\":\"5556787\",\"description\":\"Created by OpenIDM REST.\",\"password\":\"MyPassw0rd\"}" ^
+ "http://localhost:8080/openidm/managed/user?_action=create"
+----
+The output is essentially the same as the UNIX command output.
+
+OpenIDM creates the new user in the repository. If you configure a mapping to apply changes from the local repository to the XML file connector as a target, OpenIDM then updates the XML file to add the new user.
+
+You can also add users through the UI, which uses the OpenIDM REST API. When you have logged into the UI as the OpenIDM administrator, click Manage > User > New User. The process is straightforward.
+
+
+
+[#more-sample-8]
+=== Logging Sample - Using Scripts to Generate Log Messages
+
+OpenIDM provides a `logger` object with `debug()`, `error()`, `info()`, `trace()`, and `warn()` functions that you can use to log messages to the OSGi console from your scripts.
+
+[#install-sample8]
+==== Install the Sample
+
+Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for sample 8.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample8
+----
+The `sync.json` file in the `sample8/conf` directory includes brief examples of log messages.
+
+
+[#run-sample8]
+==== Running the Sample
+
+Run reconciliation over the REST interface.
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemXmlfileAccounts_managedUser&waitForCompletion=true"
+----
+The reconciliation operation returns a reconciliation run ID, and the status of the operation.
+
+Note the log messages displayed in the OSGi console. The following example omits timestamps and so forth to show only the message strings.
+
+[source]
+----
+->
+...Case no Source: the source object contains: = null [5235432-...
+...Case emptySource: the source object contains: = {lastname=Carter, mobile...
+...Case sourceDescription: the source object contains: = Created By XML1
+...Case onCreate: the source object contains: = {lastname=Carter, mobile...
+...Case result: the source object contains: = {SOURCE_IGNORED={count=0, ids=[]},...
+----
+
+
+
+[#more-sample-9]
+=== Workflow Sample - Demonstrating Asynchronous Reconciling Using a Workflow
+
+Sample 9 demonstrates asynchronous reconciliation using workflows. Reconciliation generates an approval request for each ABSENT user. The configuration for this action is defined in the `conf/sync.json` file, which specifies that an `ABSENT` condition should launch the `managedUserApproval` workflow:
+
+[source, javascript]
+----
+...
+ {
+ "situation" : "ABSENT",
+ "action" : {
+ "workflowName" : "managedUserApproval",
+ "type" : "text/javascript",
+ "file" : "workflow/triggerWorkflowFromSync.js"
+ }
+ },
+ ...
+----
+When the request is approved by an administrator, the absent users are created by an asynchronous reconciliation process.
+
+Prepare a fresh installation of OpenIDM before trying this sample.
+
+[#install-sample9]
+==== Install the Sample
+
+Prepare OpenIDM as described in xref:chap-overview.adoc#preparing-openidm["Preparing OpenIDM"], then start OpenIDM with the configuration for sample 9.
+
+[source, console]
+----
+$ cd /path/to/openidm
+$ ./startup.sh -p samples/sample9
+----
+
+
+[#run-sample9]
+==== Running the Sample
+
+
+. Run reconciliation over the REST interface.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request POST \
+ "http://localhost:8080/openidm/recon?_action=recon&mapping=systemXmlfileAccounts_managedUser&waitForCompletion=true"
+----
++
+The reconciliation operation returns a reconciliation run ID, and the status of the operation.
++
+Reconciliation starts an approval workflow for each ABSENT user. These approval workflows (named `managedUserApproval`) wait for the request to be approved by an administrator.
+
+. Query the invoked workflow task instances over REST.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/workflow/taskinstance?_queryId=query-all-ids"
+----
++
+In this case, the request returns two workflow results, each with a process ID (`_id`) as well as a process definition ID. You will use the value of the `_id` shortly.
++
+
+[source, console]
+----
+{
+ "result" : [ {
+ "tenantId" : "",
+ "createTime" : "2014-05-01T13:48:42.980-08:00",
+ "executionId" : "101",
+ "delegationStateString" : null,
+ "processVariables" : { },
+ "_id" : "123",
+ "processInstanceId" : "101",
+ "description" : null,
+ "priority" : 50,
+ "name" : "Evaluate request",
+ "dueDate" : null,
+ "parentTaskId" : null,
+ "processDefinitionId" : "managedUserApproval:1:3",
+ "taskLocalVariables" : { },
+ "suspensionState" : 1,
+ "assignee" : "openidm-admin",
+ "cachedElContext" : null,
+ "queryVariables" : null,
+ "activityInstanceVariables" : { },
+ "deleted" : false,
+ "suspended" : false,
+ "_rev" : 1,
+ "revisionNext" : 2,
+ "category" : null,
+ "taskDefinitionKey" : "evaluateRequest",
+ "owner" : null,
+ "eventName" : null,
+ "delegationState" : null
+ }, {
+ "tenantId" : "",
+ "createTime" : "2014-05-01T13:48:42.980-08:00",
+ "executionId" : "102",
+ "delegationStateString" : null,
+ "processVariables" : { },
+ "_id" : "124",
+ "processInstanceId" : "102",
+ "description" : null,
+ "priority" : 50,
+ "name" : "Evaluate request",
+ "dueDate" : null,
+ "parentTaskId" : null,
+ "processDefinitionId" : "managedUserApproval:1:3",
+ "taskLocalVariables" : { },
+ "suspensionState" : 1,
+ "assignee" : "openidm-admin",
+ "cachedElContext" : null,
+ "queryVariables" : null,
+ "activityInstanceVariables" : { },
+ "deleted" : false,
+ "suspended" : false,
+ "_rev" : 1,
+ "revisionNext" : 2,
+ "category" : null,
+ "taskDefinitionKey" : "evaluateRequest",
+ "owner" : null,
+ "eventName" : null,
+ "delegationState" : null
+ } ],
+ "resultCount" : 2,
+ "pagedResultsCookie" : null,
+ "remainingPagedResults" : -1
+}
+----
+
+. Approve the requests over REST, by setting the `"requestApproved"` parameter for the specified task instance to `"true"`. Note the use of one of the values of `_id` in the REST call, in this case, `124`.
++
+On UNIX:
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{"requestApproved": "true"}' \
+ "http://localhost:8080/openidm/workflow/taskinstance/124?_action=complete"
+----
++
+On Windows:
++
+
+[source, console]
+----
+$ curl ^
+ --header "X-OpenIDM-Username: openidm-admin" ^
+ --header "X-OpenIDM-Password: openidm-admin" ^
+ --header "Content-Type: application/json" ^
+ --request POST ^
+ --data "{\"requestApproved\": \"true\"}" ^
+ "http://localhost:8080/openidm/workflow/taskinstance/124?_action=complete"
+----
++
+A successful call returns the following:
++
+
+[source, console]
+----
+{"Task action performed":"complete"}
+----
+
+. Once the request has been approved, an asynchronous reconciliation operation runs, which creates the users whose accounts were approved in the previous step.
++
+List the users that were created by the asynchronous reconciliation.
++
+
+[source, console]
+----
+$ curl \
+ --header "X-OpenIDM-Username: openidm-admin" \
+ --header "X-OpenIDM-Password: openidm-admin" \
+ --request GET \
+ "http://localhost:8080/openidm/managed/user?_queryId=query-all-ids"
+----
++
+One user is returned.
++
+
+[source, console]
+----
+{
+ "result": [ {
+ "_rev": "0",
+ "_id": "1"
+ } ],
+...
+}
+----
+
+
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/index.adoc b/openidm-doc/src/main/asciidoc/samples-guide/index.adoc
new file mode 100644
index 000000000..469a0ddaa
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/index.adoc
@@ -0,0 +1,48 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+= Samples Guide
+:doctype: book
+:toc:
+:authors: Lana Frost, Mike Jang
+:copyright: Copyright 2011-2016 ForgeRock AS.
+:copyright: Portions Copyright 2024 3A Systems LLC.
+
+:imagesdir: ../
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+[abstract]
+This guide provides a number of "sample deployments" that walk you through the essential OpenIDM features, as they would be implemented.
+
+include::./preface.adoc[]
+include::./chap-overview.adoc[]
+include::./chap-xml-samples.adoc[]
+include::./chap-ldap-samples.adoc[]
+include::./chap-groovy-samples.adoc[]
+include::./chap-powershell-samples.adoc[]
+include::./chap-audit-sample.adoc[]
+include::./chap-roles-sample.adoc[]
+include::./chap-multiaccount-sample.adoc[]
+include::./chap-trustedfilter-sample.adoc[]
+include::./chap-fullstack-sample.adoc[]
+include::./chap-workflow-samples.adoc[]
+include::./chap-google-sample.adoc[]
+include::./chap-salesforce-sample.adoc[]
+include::./chap-kerberos-sample.adoc[]
+include::./chap-endpoint-sample.adoc[]
+include::./openidm-glossary.adoc[]
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/openidm-glossary.adoc b/openidm-doc/src/main/asciidoc/samples-guide/openidm-glossary.adoc
new file mode 100644
index 000000000..d8bba1dff
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/openidm-glossary.adoc
@@ -0,0 +1,80 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[glossary]
+[#openidm-glossary]
+== OpenIDM Glossary
+
+
+correlation query::
+A correlation query specifies an expression that matches existing entries in a source repository to one or more entries on a target repository. While a correlation query may be built with a script, it is __not__ a correlation script.
+
++
+As noted in xref:../integrators-guide/chap-synchronization.adoc#correlation["Correlating Source Objects With Existing Target Objects"] in the __Integrator's Guide__, you can set up a query definition, such as`_queryId`,`_queryFilter`, or`_queryExpression`, possibly with the help of a`linkQualifier`.
+
+correlation script::
+A correlation script matches existing entries in a source repository, and returns the IDs of one or more matching entries on a target repository. While it skips the intermediate step associated with a`correlation query`, a correlation script can be relatively complex, based on the operations of the script.
+
+entitlement::
+An entitlement is a collection of attributes that can be added to a user entry via roles. As such, it is a specialized type of `assignment`. A user or device with an entitlement gets access rights to specified resources. An entitlement is a property of a managed object.
+
+JSON::
+JavaScript Object Notation, a lightweight data interchange format based on a subset of JavaScript syntax. For more information, see the link:http://www.json.org[JSON, window=\_blank] site.
+
+JWT::
+JSON Web Token. As noted in the link:http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html[JSON Web Token draft IETF Memo, window=\_blank], "JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties." For OpenIDM, the JWT is associated with the `JWT_SESSION` authentication module.
+
+managed object::
+An object that represents the identity-related data managed by OpenIDM. Managed objects are configurable, JSON-based data structures that OpenIDM stores in its pluggable repository. The default configuration of a managed object is that of a user, but you can define any kind of managed object, for example, groups or roles.
+
+mapping::
+A policy that is defined between a source object and a target object during reconciliation or synchronization. A mapping can also define a trigger for validation, customization, filtering, and transformation of source and target objects.
+
+OSGi::
+A module system and service platform for the Java programming language that implements a complete and dynamic component model. For a good introduction, see the link:https://www.osgi.org//developer/benefits-of-using-osgi[OSGi, window=\_blank] site. While OpenIDM services are designed to run in any OSGi container, currently only link:http://felix.apache.org/[Apache Felix, window=\_blank] is supported.
+
+reconciliation::
+During reconciliation, comparisons are made between managed objects and objects on source or target systems. Reconciliation can result in one or more specified actions, including, but not limited to, synchronization.
+
+resource::
+An external system, database, directory server, or other source of identity data to be managed and audited by the identity management system.
+
+[#gloss-rest]
+REST::
+Representational State Transfer. A software architecture style for exposing resources, using the technologies and protocols of the World Wide Web. REST describes how distributed data objects, or resources, can be defined and addressed.
+
+role::
+OpenIDM includes two different types of provisioning roles and authorization roles. For more information, see xref:../integrators-guide/chap-users-groups-roles.adoc#working-with-managed-roles["Working With Managed Roles"] in the __Integrator's Guide__.
+
+source object::
+In the context of reconciliation, a source object is a data object on the source system, that OpenIDM scans before attempting to find a corresponding object on the target system. Depending on the defined mapping, OpenIDM then adjusts the object on the target system (target object).
+
+synchronization::
+The synchronization process creates, updates, or deletes objects on a target system, based on the defined mappings from the source system. Synchronization can be scheduled or on demand.
+
+system object::
+A pluggable representation of an object on an external system. For example, a user entry that is stored in an external LDAP directory is represented as a system object in OpenIDM for the period during which OpenIDM requires access to that entry.System objects follow the same RESTful resource-based design principles as managed objects.
+
+target object::
+In the context of reconciliation, a target object is a data object on the target system, that OpenIDM scans after locating its corresponding object on the source system. Depending on the defined mapping, OpenIDM then adjusts the target object to match the corresponding source object.
+
+
diff --git a/openidm-doc/src/main/asciidoc/samples-guide/preface.adoc b/openidm-doc/src/main/asciidoc/samples-guide/preface.adoc
new file mode 100644
index 000000000..c534c4115
--- /dev/null
+++ b/openidm-doc/src/main/asciidoc/samples-guide/preface.adoc
@@ -0,0 +1,44 @@
+////
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions copyright [year] [name of copyright owner]".
+
+ Copyright 2017 ForgeRock AS.
+ Portions Copyright 2024 3A Systems LLC.
+////
+
+:figure-caption!:
+:example-caption!:
+:table-caption!:
+
+
+[preface]
+[#preface]
+== Preface
+
+This guide describes a number of sample deployments that demonstrate the core functionality of OpenIDM. The samples correspond to the configurations provided in the `openidm/samples` directory.
+
+[#d5830e168]
+=== Who Should Use This Guide
+
+This guide is written for anyone testing OpenIDM to manage identities, and to ensure compliance with identity management regulations.
+
+The guide covers a number OpenIDM features, often including multiple features in a single sample.
+
+You do not need to be an OpenIDM wizard to learn something from this guide, although a background in identity management and maintaining web application software can help. You do need some background in managing services on your operating systems and in your application servers. You can nevertheless get started with this guide, and then learn more as you go along.
+
+include::../partials/sec-formatting-conventions.adoc[]
+
+include::../partials/sec-accessing-doc-online.adoc[]
+
+include::../partials/sec-joining-the-community.adoc[]
+
+include::../partials/sec-support-contact.adoc[]