User offboarding workflow
In this example, an administrator creates a workflow that triggers a series of offboarding tasks when a user update occurs, such as a status, manager, or department change.
The offboarding tasks include:
-
Setting up a replacement user ID for the inactive user. Depending on the case, the replacement user is the manager or former manager.
-
Setting up a replacement user ID as a delegate for the inactive user.
-
Replacing all instances of other users delegating to the inactive user with replacement user ID.
-
Replacing the inactive user with the replacement user ID as an app owner.
-
Replacing the inactive user with the replacement user ID as an entitlements owner.
-
Replacing the inactive user with the replacement user ID as a role owner.
-
Replacing the inactive user with the replacement user ID as an access request approver.
-
Replacing the inactive user with the replacement user ID as a violations approver.
Assumptions
-
Human Resources confirms the user’s change in status, manager, or department and has activated offboarding tasks to stakeholders.
Example
-
1 The Script node reads the event user information, including manager data from the request object.
Click to display the Get Replacement User ID script
// Insert logic to set ID of user who will be replacing inactive user logger.info("Getting ID of user who will be replacing inactive user."); var content = execution.getVariables(); var requestId = content.get('id'); // Read event user information from request object try { var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {}); var previousUserObj = requestObj.request.common.blob.before; var currentUserObj = requestObj.request.common.blob.after; var userId = currentUserObj.id; var replacementId = null; // Check current value of manager, or previous manager if not present, to find a replacement user ID if (currentUserObj && currentUserObj.manager) { replacementId = currentUserObj.manager.id; } else if (previousUserObj && previousUserObj.manager) { replacementId = previousUserObj.manager.id; } execution.setVariable('userId', userId); execution.setVariable('replacementId', replacementId); } catch(e) { logger.info("Unable to get replacement user ID for inactive user " + userId); }
-
2 The Script node adds the replacement user as a delegate for the inactive user so that they can act on their tasks.
Click to display Set Replacement User as Inactive User script
// Adding the Replacement User as a delegate for the Inactive User so that they will be able to act on their tasks var content = execution.getVariables(); var userId = content.get('userId'); var replacementId = content.get('replacementId'); // Read event user information from request object try { if (replacementId) { logger.info("Adding " + replacementId + " as inactive user " + userId + "'s delegate"); var payload = { proxyIds: [ "managed/user/" + replacementId ]}; var proxyUpdate = openidm.action('iga/governance/user/' + userId + '/set-proxy', 'POST', payload, {}); logger.info("Added " + replacementId + " as a delegate for inactive user " + userId); } } catch(e) { logger.info("Unable to add delegate for inactive user " + userId); }
-
3 The Script node replaces all instances of other users delegating to the inactive user with the replacement user.
Click to display the Replace Inactive User as delegate script
// Replacing all instances of others delegating to the inactive user with replacement user // Before script: User A and User B both delegate to inactive User // After script: User A and User B both delegate to replacement User var content = execution.getVariables(); var userId = content.get('userId'); var replacementId = content.get('replacementId'); try { if (replacementId) { logger.info("Replacing instances of users delegating to inactive user " + userId + " with " + replacementId); // Get the list of users delegating to inactive user var inactiveUser = openidm.query("managed/alpha_user/" + userId + '/taskPrincipals', { _queryFilter: 'true' }, [ '_refResourceId' ]); var usersDelegatingToInactiveUser = inactiveUser.result; // Set the payloads var removePayload = { proxyIds: [ "managed/user/" + userId ]}; var addPayload = { proxyIds: [ "managed/user/" + replacementId ]}; // For each delegate, remove the inactive user and add the replacement user for (var i = 0; i < usersDelegatingToInactiveUser.length; i++) { var delegatingUserId = usersDelegatingToInactiveUser[i]._refResourceId; openidm.action("iga/governance/user/" + delegatingUserId + "/remove-proxy", "POST", removePayload, {}); openidm.action("iga/governance/user/" + delegatingUserId + "/set-proxy", "POST", addPayload, {}); } logger.info("Replaced instances of users delegating to inactive user " + userId + " with " + replacementId); } } catch(e) { logger.info("Unable to replace instances of users delegating to inactive user " + userId + " with " + replacementId); }
-
4 The Script node invokes the APIs and executes business logic.
Click to display the App Owner Replacement script
/* Script nodes are used to invoke APIs or execute business logic. You can invoke governance APIs or IDM APIs. Refer to https://backstage.forgerock.com/docs/idcloud/latest/identity-governance/administration/workflow-configure.html for more details. Script nodes should return a single value and should have the logic enclosed in a try-catch block. Example: try { var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {}); applicationId = requestObj.application.id; } catch (e) { failureReason = 'Validation failed: Error reading request with id ' + requestId; } */ var content = execution.getVariables(); var userId = content.get('userId'); var replacementId = content.get('replacementId'); var queryFilter = {"_queryFilter": "true"} var removeBody = []; var addBody = []; var applications = openidm.query('managed/alpha_user/' + userId + '/ownerOfApp', queryFilter, []); for(var app of applications['result']){ removeBody.push({ "operation": "remove", "field": "/ownerOfApp", "value": { "_ref": app._ref, "_refProperties": { "_id": app._id, "_rev": app._rev }, "_refResourceCollection": "managed/alpha_application", "_refResourceId": app._refResourceId } }) addBody.push({ "operation": "add", "field": "ownerOfApp/-", "value": { "_ref": app._ref, "_refProperties": {} } }) } openidm.patch('managed/alpha_user/'+ userId, null, removeBody) openidm.patch('managed/alpha_user/'+ replacementId, null, addBody)
-
5 The Script node replaces the entitlement owner.
Click to display the Entitlement Owner Replacement script
/* Script nodes are used to invoke APIs or execute business logic. You can invoke governance APIs or IDM APIs. Refer to https://backstage.forgerock.com/docs/idcloud/latest/identity-governance/administration/workflow-configure.html for more details. Script nodes should return a single value and should have the logic enclosed in a try-catch block. Example: try { var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {}); applicationId = requestObj.application.id; } catch (e) { failureReason = 'Validation failed: Error reading request with id ' + requestId; } */ var content = execution.getVariables(); var userId = content.get('userId'); var replacementId = content.get('replacementId'); var targetFilter = {"targetFilter": { "operator": "EQUALS", "operand": { "targetName": "entitlementOwner.id", "targetValue": userId } }} var entitlements = openidm.action('iga/governance/resource/search', 'POST', targetFilter, {}); for(var entitlement of entitlements['result']){ var body = openidm.action('iga/governance/resource/' + entitlement.id + '/glossary', 'GET', {}, {}) body.entitlementOwner = "managed/user/" + replacementId; openidm.action('iga/governance/resource/' + entitlement.id + '/glossary', 'PUT', body, {}) }
-
6 The Script node replaces the role owner.
Click to display the Role Owner Replacement script
/* Script nodes are used to invoke APIs or execute business logic. You can invoke governance APIs or IDM APIs. Refer to https://backstage.forgerock.com/docs/idcloud/latest/identity-governance/administration/workflow-configure.html for more details. Script nodes should return a single value and should have the logic enclosed in a try-catch block. Example: try { var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {}); applicationId = requestObj.application.id; } catch (e) { failureReason = 'Validation failed: Error reading request with id ' + requestId; } */ var content = execution.getVariables(); var userId = content.get('userId'); var replacementId = content.get('replacementId'); var targetFilter = {"targetFilter": { "operator": "EQUALS", "operand": { "targetName": "glossary.idx./role.roleOwner", "targetValue": "managed/user/"+ userId } }} var results = openidm.action('iga/governance/catalog/search', 'POST', targetFilter, {}); for(var result of results['result']){ var body = openidm.action('iga/governance/role/' + result.role.id + '/glossary', 'GET', {}, {}) body.roleOwner = "managed/user/" + replacementId; openidm.action('iga/governance/role/' + result.role.id + '/glossary', 'PUT', body, {}) }
-
7 The Script node reassigns approvals.
Click to display the Reassign Approvals script
/* Script nodes are used to invoke APIs or execute business logic. You can invoke governance APIs or IDM APIs. Refer to https://backstage.forgerock.com/docs/idcloud/latest/identity-governance/administration/workflow-configure.html for more details. Script nodes should return a single value and should have the logic enclosed in a try-catch block. Example: try { var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {}); applicationId = requestObj.application.id; } catch (e) { failureReason = 'Validation failed: Error reading request with id ' + requestId; } */ var content = execution.getVariables(); var userId = content.get('userId'); var replacementId = content.get('replacementId'); var targetFilter = { "targetFilter": { "operator": "AND", "operand": [ { "operator": "EQUALS", "operand": { "targetName": "decision.actors.active.id", "targetValue": "managed/user/" + userId } }, { "operator": "EQUALS", "operand": { "targetName": "decision.status", "targetValue": "in-progress" } } ] } } var results = null var params = { "_pageSize": 100, "_pageNumber": 0 } do{ results= openidm.action('iga/governance/requests/search', 'POST', targetFilter, params); for(var result of results['result']){ var phaseName = null; var actors = []; for(var user of result.decision.actors.active){ if(user.id === "managed/user/"+ userId){ phaseName = user.phase; actors.push({"id": "managed/user/" + replacementId, "permissions": user.permissions}) } } for(var user of result.decision.actors.active){ if(user.phase === phaseName && user.id !== "managed/user/"+ userId){ actors.push({"id": user.id, "permissions": user.permissions}) } } var body = { "updatedActors": actors }; if(phaseName){ openidm.action('/iga/governance/requests/' + result.id + '/phases/' + phaseName + '/reassign', 'POST', body, {}) } } } while (results.result.length > 0)
-
8 The Script node reassigns violations.
Click to display the Reassign Violations script
/* Script nodes are used to invoke APIs or execute business logic. You can invoke governance APIs or IDM APIs. Refer to https://backstage.forgerock.com/docs/idcloud/latest/identity-governance/administration/workflow-configure.html for more details. Script nodes should return a single value and should have the logic enclosed in a try-catch block. Example: try { var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {}); applicationId = requestObj.application.id; } catch (e) { failureReason = 'Validation failed: Error reading request with id ' + requestId; } */ logger.info('Script Task 3'); logger.info('User Task'); var content = execution.getVariables(); var userId = content.get('userId'); var replacementId = content.get('replacementId'); var targetFilter = { "targetFilter": { "operator": "AND", "operand": [ { "operator": "EQUALS", "operand": { "targetName": "decision.actors.active.id", "targetValue": "managed/user/" + userId } }, { "operator": "EQUALS", "operand": { "targetName": "decision.status", "targetValue": "in-progress" } } ] } } var results = null var params = { "_pageSize": 10, "_pageNumber": 0 } do{ results = openidm.action('iga/governance/violation/search', 'POST', targetFilter, params); if(results['result'].length > 0){ for(var result of results['result']){ var phaseName = null; var actors = []; for(var user of result.decision.actors.active){ if(user.id === "managed/user/"+ userId){ phaseName = user.phase; actors.push({"id": "managed/user/" + replacementId, "permissions": user.permissions}) } } for(var user of result.decision.actors.active){ if(user.phase === phaseName && user.id !== "managed/user/"+ userId){ actors.push({"id": user.id, "permissions": user.permissions}) } } var body = { "updatedActors": actors }; if(phaseName){ openidm.action('/iga/governance/violation/' + result.id + '/phases/' + phaseName + '/reassign', 'POST', body, {}) } } } } while(results.result.length > 0)
-
9 The Script node completes the process and sets the ID of the user, replacing the inactive user.
Click to display the Complete Process script
// Insert logic to set ID of user who will be replacing inactive user logger.info("Completing request workflow."); var content = execution.getVariables(); var requestId = content.get('id'); // Read event user information from request object try { var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {}); var decision = {'status': 'complete', 'decision': 'approved', 'outcome': 'fulfilled'}; var queryParams = { '_action': 'update'}; openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams); logger.info("Request " + requestId + " completed."); } catch(e) { logger.info("Error finalizing user inactive workflow") }
Download the JSON file for this workflow here. Learn more about how to import or export workflows in workflow editor canvas. |