PingOne Advanced Identity Cloud

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

An example of an inactivated user workflow.
  • 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.