chap-multiaccount.xml revision 0af00f473b22eed121f9a9a9c8f2b85f76707d6a
<?xml version="1.0" encoding="UTF-8"?>
<!-- TODO: after conversion (AC) delete all info above, except xml version
! CCPL HEADER START
!
! This work is licensed under the Creative Commons
! Attribution-NonCommercial-NoDerivs 3.0 Unported License.
! To view a copy of this license, visit
! http://creativecommons.org/licenses/by-nc-nd/3.0/
! or send a letter to Creative Commons, 444 Castro Street,
! Suite 900, Mountain View, California, 94041, USA.
!
! You can also obtain a copy of the license at
! legal/CC-BY-NC-ND.txt.
! See the License for the specific language governing permissions
! and limitations under the License.
!
! If applicable, add the following below this CCPL HEADER, with the fields
! enclosed by brackets "[]" replaced with your own identifying information:
! Portions Copyright [yyyy] [name of copyright owner]
!
! CCPL HEADER END
!
! Copyright 2015 ForgeRock AS
!
TODO: AC, delete section xml:id line and section closing about 10 lines below
then in the last line sub chapter > chapter
-->
<chapter xml:id='sample-multiaccount-linking'
xmlns='http://docbook.org/ns/docbook'
version='5.0' xml:lang='en'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xsi:schemaLocation='http://docbook.org/ns/docbook
http://docbook.org/xml/5.0/xsd/docbook.xsd'
xmlns:xlink='http://www.w3.org/1999/xlink'>
<title>The Multi-Account Linking Sample</title>
<simpara>The sample provided in the <literal>samples/multiaccountlinking</literal> directory illustrates
how OpenIDM addresses links from multiple accounts to one identity.</simpara>
<simpara>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 customers.
Most of their agents are also insured customers. These different roles are
sometimes known as the multi-account linking conundrum.</simpara>
<simpara>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.</simpara>
<section xml:id="external-ldap-config-multiaccount">
<title>External LDAP Configuration</title>
<simpara>Configure the LDAP server as for sample 2,
<link xlink:show="new" xlink:href="http://openidm.forgerock.org/doc/bootstrap/install-guide/index.html#external-ldap-config-2">External LDAP Configuration</link></simpara>
<simpara>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, <emphasis>openidm/samples/multiaccountlinking/data/Example.ldif</emphasis>.</simpara>
</section>
<section xml:id="install-sample-multiaccount">
<title>Install the Sample</title>
<simpara>Prepare OpenIDM as described in
<link xlink:show="new" xlink:href="http://openidm.forgerock.org/doc/bootstrap/install-guide/index.html#preparing-openidm">Preparing OpenIDM</link>,
then start OpenIDM with the following configuration for the
Multi-Account Linking sample.</simpara>
<screen>$ <userinput>cd /path/to/openidm</userinput></screen>
<screen>$ <userinput>/startup.sh -p samples/multiaccountlinking</userinput></screen>
</section>
<section xml:id="multiaccount-create-users">
<title>Create New Identities for the Sample</title>
<simpara>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
<literal><link xlink:show="new" xlink:href="https://localhost:8443/admin">https://localhost:8443/admin</link></literal> and click Manage &gt; User &gt; New User.</simpara>
<simpara>Alternatively, use the following REST calls to set up identities for the noted
users:</simpara>
<screen>$ <userinput>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",
"roles" : [
"openidm-authorized"
],
"postalCode" : "",
"stateProvince" : "",
"postalAddress" : "",
"address2" : "",
"country" : "",
"city" : ""
}' \
"https://localhost:8443/openidm/managed/user?_action=create"</userinput></screen>
<screen>$ <userinput>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",
"roles" : [
"openidm-authorized"
],
"postalCode" : "",
"stateProvince" : "",
"postalAddress" : "",
"address2" : "",
"country" : "",
"city" : ""
}' \
"https://localhost:8443/openidm/managed/user?_action=create"</userinput></screen>
<simpara>In the output, you will see an ID number associated with each user, in the
following format:</simpara>
<screen> "_id" : "35d0a49d-2571-401f-b429-96c66b23a1c0",</screen>
<simpara>Record the <literal>_id</literal> number for each user. You will use that number to
assign desired roles for each users.</simpara>
</section>
<section xml:id="multiaccount-create-roles">
<title>Create New Roles for the Sample</title>
<simpara>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 <literal>Agent</literal> and <literal>Customer</literal>. To create
these roles in the Admin UI, navigate to <literal><link xlink:show="new" xlink:href="https://localhost:8443/admin">https://localhost:8443/admin</link></literal> and
click Manage &gt; Role &gt; New Role.</simpara>
<simpara>Alternatively, use the following REST calls to set up the <literal>Agent</literal> and <literal>Customer</literal>
roles:</simpara>
<screen>$ <userinput>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 '{
"properties" : {
"name" : "Agent",
"description" : "Role assigned to insurance agents."
}
}' \
"https://localhost:8443/openidm/managed/role?_action=create"</userinput></screen>
<screen>$ <userinput>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 '{
"properties" : {
"name" : "Customer",
"description" : "Role assigned to insured customers."
}
}' \
"https://localhost:8443/openidm/managed/role?_action=create"</userinput></screen>
<simpara>Do record the <literal>_id</literal> output for the Agent and Customer roles. You will use those
numbers</simpara>
<note>
<simpara>While you could use <literal>PUT</literal> to create roles with descriptive names,
we recommend that you use <literal>POST</literal> to create roles with immutable IDs.</simpara>
</note>
</section>
<section xml:id="multiaccount-assign-roles">
<title>Assign Roles to Appropriate Users</title>
<simpara>Now you can assign roles to appropriate users. To review, user <literal>jdoe</literal> is an
<literal>Agent</literal> and user <literal>bjensen</literal> is a <literal>Customer</literal>.</simpara>
<simpara>You will need the <literal>_id</literal> value for each user. The <literal>_id</literal> values shown in the
following commands are random; substitute the <literal>_id</literal> values that you collected
when creating users.</simpara>
<simpara>The following command adds the <literal>Agent</literal> role to user <literal>jdoe</literal>, by their
<literal>_id</literal> values:</simpara>
<screen>$ <userinput>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" : "managed/role/287dc4b1-4b19-49ec-8b4c-28a6c12ede34"
}
]' \
"https://localhost:8443/openidm/managed/user/8fae84ed-1f30-4542-8087-e7fa6e89541c"</userinput></screen>
<simpara>To confirm, you should see output that includes two roles for user
<literal>jdoe</literal>. The following output includes a unique Agent <literal>_id</literal> number; the number
that you see will be different.</simpara>
<screen>"roles":["openidm-authorized","managed/role/287dc4b1-4b19-49ec-8b4c-28a6c12ede34"],</screen>
<simpara>And this next command adds the <literal>Customer</literal> role to user <literal>bjensen</literal>:</simpara>
<screen>$ <userinput>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" : "managed/role/bb9302c4-5fc1-462c-8be2-b17c87175d1b"
}
]' \
"https://localhost:8443/openidm/managed/user/d0b79f30-946f-413a-b7d1-d813034fa345"</userinput></screen>
<simpara>To confirm, you should see output that includes two roles for user
<literal>bjensen</literal>, in this case:</simpara>
<screen>"roles":["openidm-authorized","managed/role/bb9302c4-5fc1-462c-8be2-b17c87175d1b"],</screen>
<simpara>Now assign the <literal>customer</literal> role to user <literal>jdoe</literal>, as that user is a customer and
an agent:</simpara>
<screen>$ <userinput>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" : "managed/role/bb9302c4-5fc1-462c-8be2-b17c87175d1b"
}
]' \
"https://localhost:8443/openidm/managed/user/8fae84ed-1f30-4542-8087-e7fa6e89541c"</userinput></screen>
<simpara>Now user <literal>jdoe</literal> should have three roles:</simpara>
<screen>"roles":["openidm-authorized", "managed/role/287dc4b1-4b19-49ec-8b4c-28a6c12ede34", "managed/role/bb9302c4-5fc1-462c-8be2-b17c87175d1b" ],</screen>
</section>
<section xml:id="multiaccount-background">
<title>Background: Link Qualifiers, Agents, and Customers</title>
<simpara>This is a good moment to take a step back, to see how this sample works, based
on custom options in the <literal>sync.json</literal> configuration file.</simpara>
<simpara>OpenIDM defines mappings between source and target accounts in the
<literal>sync.json</literal> file. This table 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.</simpara>
<simpara>For more information on resource mappings and link qualifiers, see
the following sections of the Integrator&#8217;s Guide:</simpara>
<simpara><link xlink:show="new" xlink:href="http://openidm.forgerock.org/doc/bootstrap/integrators-guide/#mapping-link-qualifiers">Using Link Qualifiers in a Mapping</link></simpara>
<simpara><link xlink:show="new" xlink:href="http://openidm.forgerock.org/doc/bootstrap/integrators-guide/#admin-ui-resource-mapping">Configuring a Resource Mapping from the UI</link></simpara>
<simpara><link xlink:show="new" xlink:href="http://openidm.forgerock.org/doc/bootstrap/integrators-guide/#link-qualifier">Link Qualifier definition</link></simpara>
<simpara>In this sample, we use two link qualifiers:</simpara>
<itemizedlist>
<listitem>
<simpara><literal>insured</literal> represents the customer accounts associated with Example.com,
created under the following LDAP container: <literal>ou=Customers,dc=example,dc=com</literal></simpara>
</listitem>
<listitem>
<simpara><literal>agent</literal> represents agent accounts, considered independent contractors,
and created under the following LDAP container:
<literal>ou=Contractors,dc=example,dc=com</literal></simpara>
</listitem>
</itemizedlist>
<simpara>Assume that agents and customers connect via two different portals. Each group
gets access to different features, depending on the portal.</simpara>
<simpara>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.</simpara>
<simpara>To that end, this sample sets up link qualifiers for two categories of users:
<literal>insured</literal> and <literal>agent</literal>, under the <literal>managedUser_systemLdapAccounts</literal> mapping:</simpara>
<programlisting language="javascript">{
"name" : "managedUser_systemLdapAccounts",
"source" : "managed/user",
"target" : "system/ldap/account",
"linkQualifiers" : [
"insured",
"agent"
],
.....
}</programlisting>
<simpara>You can verify this in the Admin UI. Click Configure &gt; Mappings &gt;
<literal>managedUser_systemLdapAccounts</literal> &gt; Properties &gt; Link Qualifiers. You
should see <literal>insured</literal> and <literal>agent</literal> in the list of configured Link Qualifiers.</simpara>
<simpara>In addition, this sample also includes a transformation script between an LDAP
Distinguished Name (<literal>dn</literal>) and the two categories of users. The following
excerpt of the <literal>sync.json</literal> file includes that script:</simpara>
<programlisting language="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';
}"
},</programlisting>
<simpara>The following validSource script looks through the effective roles of a user,
with two objectives:
* Determine whether the user has an <literal>Agent</literal> or <literal>Insured</literal> role.
* Ensures that OpenIDM looks through the source <emphasis role="strong">only</emphasis> for the specified role.</simpara>
<programlisting language="javascript"><![CDATA["validSource" : {
"type" : "text/javascript",
"globals" : { },
"source" : "var res = false;\nvar i=0;\n\nwhile
(!res && i < source.effectiveRoles.length) {\n
var roleId = source.effectiveRoles[i];\n
if (roleId != null && roleId.indexOf(\"/\") != -1) {\n
var roleInfo = openidm.read(roleId);\n
res = (((roleInfo.properties.name === 'Agent')\n
&&(linkQualifier ==='agent'))\n
|| ((roleInfo.properties.name === 'Insured')\n
&&(linkQualifier ==='insured')));\n
}\n
i++;\n}\n\nres"
}]]></programlisting>
<simpara>You can see how correlation queries are configured in the <literal>sync.json</literal> file.
Note how it recognizes accounts from each LDAP category in case they already
exist on the target system.</simpara>
<programlisting>"correlationQuery" : [
{
"linkQualifier" : "insured",
"type" : "text/javascript",
"globals" : { },
"source" : "var map = {'_queryFilter': 'dn eq \\\"uid=' + source.userName +
',ou=Customers,dc=example,dc=com\\\"'}; map;"
},
{
"linkQualifier" : "agent",
"type" : "text/javascript",
"globals" : { },
"source" : "var map = {'_queryFilter': 'dn eq \\\"uid=' + source.userName +
',ou=Contractors,dc=example,dc=com\\\"'}; map;"
}
],......</programlisting>
<simpara>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 <literal>dn</literal> belongs in a specific
container. For this sample, the container (<literal>ou</literal>) may be Customers or
Contractors.</simpara>
<simpara>You can can avoid specifying the structure of the <literal>dn</literal> attribute in two places
in the <literal>sync.json</literal> file with the following code, which leverages the expression
builder to reuse the construct defined in the <literal>dn</literal> mapping:</simpara>
<programlisting language="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"
}
],</programlisting>
<simpara>You can also leverage the expression builder in the UI. Review how the UI
illustrates the expression builder. To do so, click Configure &gt; Mapping &gt;
select a mapping &gt; Association &gt; Association Rules. Edit either link qualifier.
You will see how the expression builder is configured for this sample.</simpara>
<simpara>The following code snippet shows how the <literal>validSource</literal> script segregates
accounts based on link qualifiers and roles:</simpara>
<programlisting language="javascript"><![CDATA["validSource" : {
"type" : "text/javascript",
"globals" : { },
"source" : "var res = false;
var i=0;
while (!res && i &lt; source.effectiveRoles.length) {
var roleId = source.effectiveRoles[i];
if (roleId != null &amp;&amp; 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"
}]]></programlisting>
<simpara>The <literal>validSource</literal> script uses the effectiveRoles property to determine whether
a user has the <literal>Agent</literal> or the <literal>Insured</literal> role, based on that user&#8217;s effective
roles.</simpara>
<simpara>OpenIDM needs to associate the assignments to the mapping. So the sample
version of <literal>sync.json</literal> includes the following element as part of the
<literal>managedUser_systemLdapAccounts</literal> mapping :</simpara>
<programlisting language="javascript">"assignmentsToMap" : [
"ldap"
]</programlisting>
</section>
<section xml:id="multiaccount-roles-update">
<title>Update Roles With Desired LDAP Attributes</title>
<simpara>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).</simpara>
<simpara>While an agent may also be an insured customer, you do not want other <literal>customer</literal>
accounts to have the same properties (or memberships) as the <literal>agent</literal>
account. In this sample, we ensure that OpenIDM limits role based
assignments to the correct account.</simpara>
<simpara>With the following commands, you will add a condition to the assignment of
attributes to the <literal>agent</literal> and <literal>customer</literal> roles. Note how these commands
<literal>PATCH</literal> the <literal>agent</literal> and <literal>customer</literal> roles with appropriate LDAP attributes.</simpara>
<screen>$ <userinput>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" : {
"ldap": {
"attributes": [
{
"name": "ldapGroups",
"value": [
"cn=Contractors,ou=Groups,dc=example,dc=com"
],
"assignmentOperation" : "mergeWithTarget",
"unassignmentOperation" : "removeFromTarget"
}
],
"linkQualifiers": ["agent"]
}
}
}]' \
"https://localhost:8443/openidm/managed/role/287dc4b1-4b19-49ec-8b4c-28a6c12ede34"</userinput></screen>
<simpara>Now repeat the process for the Customer role, with the value set to the
<literal>Chat Users</literal> group:</simpara>
<screen>$ <userinput>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" : {
"ldap": {
"attributes": [
{
"name": "ldapGroups",
"value": [
"cn=Chat Users,ou=Groups,dc=example,dc=com"
],
"assignmentOperation" : "mergeWithTarget",
"unassignmentOperation" : "removeFromTarget"
}
],
"linkQualifiers": ["customer"]
}
}
}]' \
"https://localhost:8443/openidm/managed/role/bb9302c4-5fc1-462c-8be2-b17c87175d1b</userinput></screen>
</section>
<section xml:id="multiaccountlinking-recon">
<title>Reconciling Managed Users to the External LDAP Server</title>
<simpara>Now that you have loaded <literal>Example.ldif</literal> into OpenDJ, and have started OpenIDM,
you can perform a reconciliation from the internal Managed Users repository
to the external OpenDJ data store:</simpara>
<screen>$ <userinput>curl \
--cacert self-signed.crt \
--header "Content-Type: application/json" \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--request POST \
"https://localhost:8443/openidm/recon?_action=recon&amp;mapping=managedUser_systemLdapAccounts"</userinput>
</screen>
<simpara>With all of the preparation work that you have done, this reconciliation will
create three new accounts on the external LDAP server:</simpara>
<itemizedlist>
<listitem>
<simpara>Two accounts under the <literal>ou=Customers,dc=example,dc=com</literal> branch <literal>dn</literal> under
the insured customers role, <literal>bjensen</literal> and <literal>jdoe</literal>.</simpara>
</listitem>
<listitem>
<simpara>One account under the <literal>ou=Contractors,dc=example,dc=com</literal> branch <literal>dn</literal> under
the insurance agents role, <literal>jdoe</literal>.</simpara>
</listitem>
</itemizedlist>
<simpara>Congratulations, you have just created accounts in two different areas of the
LDAP Directory Information Tree.</simpara>
</section>
<section xml:id="multilinking-review">
<title>Reviewing the Result</title>
<simpara>You have already confirmed that user <literal>bjensen</literal> has a <literal>customer</literal> role,
and user <literal>jdoe</literal> has both a <literal>customer</literal> and <literal>agent</literal> role. You can confirm the
same result in the Admin UI:</simpara>
<orderedlist numeration="arabic">
<listitem>
<simpara>Click Manage &gt; Role.</simpara>
</listitem>
<listitem>
<simpara>You should see both <literal>Agent</literal> and <literal>Customer</literal> in the Role List window that
appears.</simpara>
</listitem>
<listitem>
<simpara>Click Agent &gt; Users. You should see that user <literal>jdoe</literal> is included
as an Agent.</simpara>
</listitem>
<listitem>
<simpara>Click Back to Roles &gt; Customer &gt; Users. You should see that users
<literal>bjensen</literal> and <literal>jdoe</literal> are included as Customers.</simpara>
</listitem>
</orderedlist>
</section>
</chapter>