PingOne Advanced Identity Cloud

Application grant workflow

In this example, an administrator wants to create an application grant workflow that:

  • Requires the manager to approve the request. If an administrator sends a request, the request is auto-approved.

  • If approved, check what line of business (LOB) the application is in.

  • Based on the LOB, the workflow requires a separate approver to approve the request.

Assumptions

  • Each application has an application owner. You populate this value for each target application.

  • You create an application glossary attribute LOB, and populate the LOB for each application. For this scenario, the LOBs are:

    • Sales

    • Finance

    • Human Resources

  • Your end users have a manager assigned to them. An administrator populates this property and it isn’t modifiable by the end user.

Example

An example of an application grant workflow.
  • 1 Use a Script node to do a context check for the request.

    Click to display the request context check script
    /*
    Script nodes are used to invoke APIs or execute business logic.
    You can invoke governance APIs or IDM APIs.
    You can find details in 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 requestId = content.get('id');
    var context = null;
    var skipApproval = false;
    var lineItemId = false;
    try {
      var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      if (requestObj.request.common.context) {
        context = requestObj.request.common.context.type;
        lineItemId = requestObj.request.common.context.lineItemId;
        if (context == 'admin') {
          skipApproval = true;
        }
      }
    }
    catch (e) {
      logger.info("Request Context Check failed "+e.message);
    }
    
    logger.info("Context: " + context);
    execution.setVariable("context", context);
    execution.setVariable("lineItemId", lineItemId);
    execution.setVariable("skipApproval", skipApproval);
  • 2 Use an IF/ELSE node to set the context gateway for auto approval or standard approval based on a manager review.

  • 3 Use the Script node to run any auto approvals:

    Click to display the auto approval script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var context = content.get('context');
    var lineItemId = content.get('lineItemId');
    var queryParams = {
      "_action": "update"
    }
    try {
      var decision = {
          "decision": "approved",
          "comment": "Request auto-approved due to request context: " + context
      }
      openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams);
    }
    catch (e) {
      var failureReason = "Failure updating decision on request. Error message: " + e.message;
      var update = {'comment': failureReason, 'failure': true};
      openidm.action('iga/governance/requests/' + requestId, 'POST', update, queryParams);
    }
  • 4 Using an Approval node, the manager of the end user must approve the request.

  • 5 If approved, a Script node checks the application glossary attribute lineOfBusiness (LOB) and sets the outcome based on the LOB of the application. Based on the outcome, the Switch node evaluates the LOB.

    Click to display check LOB script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var requestObj = null;
    var appId = null;
    var appGlossary = null;
    var lob = null;
    
    try {
      requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      appId = requestObj.application.id;
      }
      catch (e) {
    	logger.info("Validation failed: Error reading application grant request with id " + requestId);
      }
    
    try {
      appGlossary = openidm.action('iga/governance/application/' + appId + '/glossary', 'GET', {}, {});
      lob = appGlossary.lineOfBusiness || "default";
      execution.setVariable("lob", lob);
    }
    catch (e) {
      logger.info("Could not retrieve glossary with appId " + appId + " from application grant request ID " + requestId);
    }
  • 3 If the LOB is:

    • sales — An Approval node requires members of the role Sales App Approver to approve the request.

    • finance — An Approval node requires members ot the fole Finance App Approver to approve the request.

    • humanResources — An Approval node requires members of the role Human Resources App Approver to approve the request.

    • null — An Approval node requires the application owner to approve the request.

  • 7 If the required approvals are met, a Script node runs a validation check.

    Click to display app grant validation script
    logger.info("Running application grant request validation");
    
    var content = execution.getVariables();
    var requestId = content.get('id');
    var failureReason = null;
    var applicationId = null;
    var app = null;
    
    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;
    }
    
    // Validation 1 - Check application exists
    if (!failureReason) {
      try {
        app = openidm.read('managed/alpha_application/' + applicationId);
        if (!app) {
          failureReason = "Validation failed: Cannot find application with id " + applicationId;
        }
      }
      catch (e) {
        failureReason = "Validation failed: Error reading application with id " + applicationId + ". Error message: " + e.message;
      }
    }
    
    // Validation 2 - Check the user does not already have application granted
    // Note: this is done at request submission time as well, the following is an example of how to check user's accounts
    if (!failureReason) {
      try {
        var user = openidm.read('managed/alpha_user/' + requestObj.user.id, null, [ 'effectiveApplications' ]);
        user.effectiveApplications.forEach(effectiveApp => {
          if (effectiveApp._id === applicationId) {
            failureReason = "Validation failed: User with id " + requestObj.user.id + " already has effective application " + applicationId;
          }
        })
      }
      catch (e) {
        failureReason = "Validation failed: Unable to check effective applications of user with id " + requestObj.user.id + ". Error message: " + e.message;
      }
    }
    
    if (failureReason) {
      logger.info("Validation failed: " + failureReason);
    }
    execution.setVariable("failureReason", failureReason);

    If any Approval node has the Reject outcome, a Script node denies the request.

    Click to display reject request script
    logger.info("Rejecting request");
    
    var content = execution.getVariables();
    var requestId = content.get('id');
    
    logger.info("Execution Content: " + content);
    var requestIndex = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
    var decision = {'outcome': 'denied', 'status': 'complete', 'decision': 'rejected'};
    var queryParams = { '_action': 'update'};
    openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams);
  • 8 If the If/Else node outcome is:

    • validationSuccess — A Script node provisions the application to the end user.

      Click to display auto provisioning script
      logger.info("Auto-Provisioning");
      
      var content = execution.getVariables();
      var requestId = content.get('id');
      var failureReason = null;
      
      try {
        var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
        logger.info("requestObj: " + requestObj);
      }
      catch (e) {
        failureReason = "Provisioning failed: Error reading request with id " + requestId;
      }
      
      if(!failureReason) {
        try {
          var request = requestObj.request;
          var payload = {
            "applicationId": request.common.applicationId,
            "startDate": request.common.startDate,
            "endDate": request.common.endDate,
            "auditContext": {},
            "grantType": "request"
          };
          var queryParams = {
            "_action": "add"
          }
      
          logger.info("Creating account: " + payload);
          var result = openidm.action('iga/governance/user/' + request.common.userId + '/applications' , 'POST', payload,queryParams);
        }
        catch (e) {
          failureReason = "Provisioning failed: Error provisioning account to user " + request.common.userId + " for application " + request.common.applicationId + ". Error message: " + e.message;
        }
      
        var decision = {'status': 'complete', 'decision': 'approved'};
        if (failureReason) {
          decision.outcome = 'not provisioned';
          decision.comment = failureReason;
          decision.failure = true;
        }
        else {
          decision.outcome = 'provisioned';
        }
      
        var queryParams = { '_action': 'update'};
        openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams);
        logger.info("Request " + requestId + " completed.");
      }
    • validationFailure — A Script node doesn’t provision the application to the end user.

      Click to display validation failure script
      var content = execution.getVariables();
      var requestId = content.get('id');
      var failureReason = content.get('failureReason');
      
      var decision = {'outcome': 'not provisioned', 'status': 'complete', 'comment': failureReason, 'failure': true, 'decision': 'approved'};
      var queryParams = { '_action': 'update'};
      openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams);

Download the JSON file for this workflow here.

Learn more about how to import or export workflows in workflow editor canvas.