Context and Objectives
When there is a new joiner, we basically need to :
- Check if this it does not already exist
- If not, create a unique ID, using a specific naming convention
- If exists, do not consider it at a new joiner but a mover, keeping the same Unique ID, and triggering a “mover” workflow
This might be sometimes challenging, depending of the number of authoritative sources, complexity of uniqueID naming convention …
This article presents a global approach to manage this problematic in a fully automated process
Solution principle
Let’s assume we have an employee named John Doe in the Authoritative Source A. Then there is a new employee joining from Authoritative Source B. We need to:
- Check if this new identity is a new one or if this is John that moved from Auth Source A to Auth Source B
- Based on the result of this initial check
- If this is a brand new Identity : generate a UniqueID then create an account in AD
- If this is John, “merge” the new account with the existing identity to keep and update the old identity with its existing accounts and access.
Here is the full principle for a user named “John Lee”.
Step 1 : during the import of an authoritative source, a new employee is detected
Step 2 : a new identity is created in IDN. Because the uniqueID has not been generated yet and because the duplicate check has not been performed, the LifeCycleState is set to *pending
Step 3 : A “new identity” event triggers the execution of a workflow
Step 4 : Workflow execution
4.1 : The “New identity” triggers the workflow
4.2 : First, a strict search is executed.
{ "query": { "query": "(attributes.firstname.exact:\"{{$.trigger.attributes.firstname}}\" AND attributes.lastname.exact:\"{{$.trigger.attributes.lastname}}\") AND ( NOT attributes.uid.exact:\"{{$.trigger.identity.name}}\")", "indices": [ "identities" ], "sort": [ "displayName" ], "includeNested": true } }
4.3 : Number of results is analysed:
- 4.3.1 : There is 0 result : it means there is no strict duplicate. We run a “fuzzy search” to check if there is not another identity that is very close to the new one (lastname / firstname inverted or a typo in the name)
- Close search uses the fuzzy feature available in IDN
{ "query": { "query": "((attributes.lastname.exact:/.*{{$.trigger.attributes.lastname}}.*/ AND attributes.firstname.exact:/.*{{$.trigger.attributes.firstname}}.*/ ) OR (attributes.lastname:{{$.trigger.attributes.lastname}}~1 AND attributes.firstname:{{$.trigger.attributes.firstname}}~1 ) OR (attributes.firstname.exact:{{$.trigger.attributes.lastname}} AND attributes.lastname.exact:{{$.trigger.attributes.firstname}} )) AND ( NOT attributes.uid.exact:{{$.trigger.identity.name}})" }, "indices": [ "identities" ], "sort": [ "displayName" ], "includeNested": true }
- If this search returns 0 result, there is no duplicate / homonymous. The new identity is relevant. It can be enabled (LCS is set to init.)
- If not, some potential duplicates exist. An email is sent to Business Unit owner, asking for arbitration. The LCS of the identity is set to arbitration. The process ends here. This is now to the BU owner to inform HQ IT team in case the new Identity has to be enabled (because it is a new identity), or to merge the new account with an existing identity.
- 4.3.2 : If the search returns 1 result, it means we have a strict duplicate. It has to be merged with the existing identity. We use API to merge the identity to an existing one.
- 4.3.3 : If search returns more than 1 results, there is more than 1 duplicate. This is not possible. The LCS is set to duplicated, and a manual action has to be done to remediate
Step 5: End. When the workflow ends, we have 2 cases :
- The new identity is in init state
- The new identity is in a arbitration or duplicated state, waiting for a manual action.
Step 6 : For all the new identity set to init, an Access Profile is automatically assigned that triggers the creation of an entry in the mySQL table. During the creation, a uniqueID is created. We use a stored procedure on mySQL to generate the ID and check for unicity against the idnid table.
Step 7 : The UniqueID is synched back to IDN. Now the IdentityCube has a UniqueID. The LCS can move to active. This is done by a transform rule in the Identity Profile
Step 8 : Now the Active Directory account can be created
The implementation of the Homonymous Detection Workflow
Here is the detailed workflow with (in blue) the steps presented above.
Step | Component | Type | Description | API |
---|---|---|---|---|
4.1 | Trigger | Identity Created | ||
4.1.1 | Action | Wait | We have to wait (1 minute) to be sure that the identity is fully created before querying its attributes. I tried without the wait and the “Get Identity Details” step was not returning the expected values | |
4.1.2 | Action | HTTP Request | We need to get some details about the identity, especially its “Identity Profile Id”. This value is not returned by the out of the box “Get Identity” Action, so I’m using an HTTP Request | /v3/search |
4.2 | Action | HTTP Request | Strict search based on firstname and lastname, excluding the new identity | /v3/search |
4.3 | Operator | Verify Data Type | The idea is to check the number of results. This should be done using the X-Total-Count response header but at the moment, this header is not returned by the HTTP Request action. So I’m testing if the strict search body contains a first element using a jsonPath : $.strictSearch.body[0].attributes.uid
|
|
4.3.1 | If there is no results, we go for the “fuzzy search” | |||
4.3.1.1 | Action | HTTP Request | Fuzzy search based on approximate firstname and lastname and same but inverted, excluding the new identity | /v3/search |
4.3.1.2 | Operator | Verify Data Type | The idea is to check the number of results. This should be done using the X-Total-Count response header but at the moment, this header is not returned by the HTTP Request action. So I’m testing if the fuzzy search body contains a first element using a jsonPath : $.fuzzySearch.body[0].attributes.uid
|
|
4.3.1.3 | Action | HTTP Request | Changing the LCS of an identity requires to call the /beta/identities/<identityid>/set-lifecycle-state with the id of the identity profile. This operation has to be done 3 times in my workflow, so I’ve created a “subworkflow” for this specific step, that has a “External Trigger”. The external trigger takes as input the following information:{"id":"{{$.trigger.identity.id}}","lifecycleState":"<name of the LCS to apply>","profileId":"{{$.getIdentityDetails.body[0].identityProfile.id}}"} . This Subworkflow is detailed in the next chapter. |
/beta/workflows/execute/external/ |
4.3.1.4 | Action | HTTP Request | Changing the LCS of an identity requires to call the /beta/identities/<identityid>/set-lifecycle-state with the id of the identity profile. This operation has to be done 3 times in my workflow, so I’ve created a “subworkflow” for this specific step, that has a “External Trigger”. This Subworkflow is detailed in the next chapter |
/beta/workflows/execute/external/ |
4.3.2.1 | Action | HTTP Request | In order to merge the new account, currently linked to the new Identity, to the old identity, we need the id of the account from the HR Source. For that, since we have the “identity profile” from step 4.1.2, we can get the account id of the source of the identity profile using the accounts endpoint, and a filter as query parameters identityId eq "{{$.trigger.identity.id}}" and sourceId eq "{{$.getIdentityDetails.body[0].source.id}}"
|
/v3/accounts |
4.3.2.2 | Action | HTTP Request | To relink the new account to the old identity, we “patch” the /accounts endpoint, replacing the new identityId with the old identityId. [[{"op":"replace","path":"/identityId","value":"{{$.strictSearch.body[0].id}}"}]]
|
/v3/accounts/ |
Here is the source file of this workflow. You will have to change the clientid, secret, urls … to match your environment.
HomonymousDetection20230301_v1.0.json (12.9 KB)
Set LifecycleState workflow
The purpose of the workflow is to change the LifecycleState of a given identity based on the “name” of the LCS.
For that, we need to list the existing lifecycleStates for a specific identity profile to get the id of the relevant one.
Step Component Type Description API
Step | Component | Type | Description | API |
---|---|---|---|---|
1 | Trigger | External Trigger | This external trigger takes the following information as input{"id":"<id of the identity>","lifecycleState":"<name of the LCS>","profileId":"id of the Identity Profile"}
|
|
2 | Action | HTTP Request | Get the list of the lifecycleStates for the given profileId | /beta/identity-profiles//lifecycle-states/ |
3 | Operator | Loop | We loop over the array of lifecycleStates that is returned from the previous step. But since inside the loop we need to compare the name of the LCS in the loop with the one that is passed in the input, we are using the new $.loop.context feature to pass the input inside the loop | |
3.1 | Operator | Compare String | Here we need to compare the technicalname of the LCS in the loop ($.loop.loopInput.technicalName ) with the name of the LCS from the input ($.loop.context.lifecycleState ) |
|
3.2 | Action | HTTP Request | As soon as we find the relevant LCS, we can change the LifecycleState of the identity, using the /identities API, passing the lifecyclestateid. {"lifecycleStateId":"{{$.loop.loopInput.id}}"}
|
/beta/identities//set-lifecycle-state |
Here is the source file of this workflow. You will have to change the clientid, secret, urls … to match your environment.