PingOne Advanced Identity Cloud

Workflow use cases

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.

Entitlement grant workflow

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

  • An entitlement owner must approve the request. If an administrator sends a request, the request is auto-approved.

  • If approved, check if the entitlement is marked as privileged.

    Companies can define what privileged means in the glossary. However, in most cases, a privileged entitlement gives administrative rights to sensitive data, such as view access to quarterly reports before public release.
  • If the entitlement is privileged or null, the end user’s manager must approve the request.

Assumptions

  • Each entitlement has an entitlement owner.

  • You create a boolean entitlement glossary attribute , isPrivileged. This attribute is populated for each entitlement.

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

Example

An example of an entitlement 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.
    Learn more in https://backstage.forgerock.com/docs/idcloud/latest/identity-governance/administration/workflow-configure.html.
    
    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.

  • 3 Use the Script node to run an auto approval:

    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"
    }
    var lineItemParams = {
      "_action": "updateRemediationStatus"
    }
    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 entitlement owner must approve the request.

  • 5 Use a Script node to check the value of the entitlement glossary attribute isPrivileged and set the outcome.

    Click to display the entitlement privileged script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var requestObj = null;
    var entId = null;
    var entGlossary = null;
    var entPriv = null;
    
    //Check entitlement exists and grab entitlement info
    try {
      requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      entId = requestObj.assignment.id;
    }
    catch (e) {
      logger.info("Validation failed: Error reading entitlement grant request with id " + requestId);
    }
    //Check glossary for entitlement exists and grab glossary info
    try {
      entGlossary = openidm.action('iga/governance/resource/' + entId + '/glossary', 'GET', {}, {});
      // Sets entPriv based on the glossary contents, if present, otherwise defaults to true
      entPriv = (entGlossary.hasOwnProperty("isPrivileged")) ? entGlossary.isPrivileged : true;
      //Sets entPriv based on glossary contents, if present, otherwise defaults to false
      //entPriv = !!entGlossary.isPrivileged;
      execution.setVariable("entPriv", entPriv);
    }
    catch (e) {
      logger.info("Could not retrieve glossary with entId " + entId + " from entitlement grant request ID " + requestId);
    }
  • 6 Use a switch node to route outcomes based on the script. If the outcome is:

    • privileged (entPriv==true) — An additional Approval node requires the end user’s manager to approve the request.

    • notPrivileged(entPriv==false`) — An additional approval isn’t required.

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

    Click to display the entitlement grant validation script
    logger.info("Running entitlement grant request validation");
    
    var content = execution.getVariables();
    var requestId = content.get('id');
    var failureReason = null;
    var applicationId = null;
    var assignmentId = null;
    var app = null;
    var assignment = null;
    var existingAccount = false;
    
    try {
      var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      applicationId = requestObj.application.id;
      assignmentId = requestObj.assignment.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 entitlement exists
    if (!failureReason) {
      try {
        assignment = openidm.read('managed/alpha_assignment/' + assignmentId);
        if (!assignment) {
          failureReason = "Validation failed: Cannot find assignment with id " + assignmentId;
        }
      }
      catch (e) {
        failureReason = "Validation failed: Error reading assignment with id " + assignmentId + ". Error message: " + e.message;
      }
    }
    
    // Validation 3 - Check the user has application granted
    if (!failureReason) {
      try {
        var user = openidm.read('managed/alpha_user/' + requestObj.user.id, null, [ 'effectiveApplications' ]);
        user.effectiveApplications.forEach(effectiveApp => {
          if (effectiveApp._id === applicationId) {
            existingAccount = true;
          }
        })
      }
      catch (e) {
        failureReason = "Validation failed: Unable to check existing applications of user with id " + requestObj.user.id + ". Error message: " + e.message;
      }
    }
    
    // Validation 4 - If account does not exist, provision it
    if (!failureReason) {
      if (!existingAccount) {
        try {
          var request = requestObj.request;
          var payload = {
            "applicationId": 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 = "Validation failed: Error provisioning new account to user " + request.common.userId + " for application " + applicationId + ". 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 the 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:

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

      Click to display the 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 = {
            "entitlementId": request.common.entitlementId,
            "startDate": request.common.startDate,
            "endDate": request.common.endDate,
            "auditContext": {},
            "grantType": "request"
          };
          var queryParams = {
            "_action": "add"
          }
      
          var result = openidm.action('iga/governance/user/' + request.common.userId + '/entitlements' , 'POST', payload,queryParams);
        }
        catch (e) {
          failureReason = "Provisioning failed: Error provisioning entitlement to user " + request.common.userId + " for entitlement " + request.common.entitlementId + ". 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.");
      }
    • validationFlowFailure — A Script node doesn’t provision the application to the end user.

      Click to display the 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.

For information on how to import or export workflows, refer to workflow editor canvas.

Role grant workflow

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

  • Checks the risk level of the role.

  • Separate approvals are required based on the risk level. Specifically, if the risk level is high, send two approvals, in parallel, to the end user’s manager and the role owner.

Assumptions

Example

An example of a role grant workflow.
  • 1 A Script node checks the value of the role glossary attribute riskLevel and sets outcomes.

    Click to display risk level script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var requestObj = null;
    var roleId = null;
    var roleGlossary = null;
    var riskLevel = null;
    
    try {
      requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      riskId = requestObj.risk.id;
    }
    catch (e) {
      logger.info("Validation failed: Error reading role grant request with id " + requestId);
    }
    
    try {
      roleGlossary = openidm.action('iga/governance/role/' + roleId + '/glossary', 'GET', {}, {});
      riskLevel = roleGlossary.riskLevel;
      execution.setVariable("riskLevel", riskLevel);
    }
    catch (e) {
      logger.info("Could not retrieve glossary with roleId " + roleId + " from role grant request ID " + requestId);
    }
  • 2 A Switch node determines the path to take based on the Script node.

  • 3 If the risk level is:

    • low — An Approval node requires either the role owner or the end user’s manager to approve the request.

    • medium — An Approval node requires the role owner to approve the request.

  • 4 If the risk level is high or null then:

    • A Switch node sends two approval tasks in parallel.

    • An Approval node requires the role owner to approve the request.

    • An Approval node requires the end user’s manager to approve the request.

    • A closing Switch node waits for both approvals before proceeding to provision the role.

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

    Click to display the Role Grant Validation script
    logger.info("Running role grant request validation");
    
    var content = execution.getVariables();
    var requestId = content.get('id');
    var failureReason = null;
    var roleId = null;
    var role = null;
    
    try {
      var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      roleId = requestObj.role.id;
    }
    catch (e) {
      failureReason = "Validation failed: Error reading request with id " + requestId;
    }
    
    // Validation 1 - Check role exists
    if (!failureReason) {
      try {
        role = openidm.read('managed/alpha_role/' + roleId);
        if (!role) {
          failureReason = "Validation failed: Cannot find role with id " + roleId;
        }
      }
      catch (e) {
        failureReason = "Validation failed: Error reading role with id " + roleId + ". 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);
  • 6 If the If/else node outcome is:

    • validationFlowSuccess — 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 = {
            "roleId": request.common.roleId,
            "startDate": request.common.startDate,
            "endDate": request.common.endDate,
            "auditContext": {},
            "grantType": "request"
          };
          var queryParams = {
            "_action": "add"
          }
      
          var result = openidm.action('iga/governance/user/' + request.common.userId + '/roles' , 'POST', payload,queryParams);
        }
        catch (e) {
          failureReason = "Provisioning failed: Error provisioning role to user " + request.common.userId + " for role " + request.common.roleId + ". 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.");
      }
    • validationFlowFailure — A Script node doesn’t provision the application to the end user.

      Click to display Validation Failure script
      var content = execution.getVariables();
      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.

Role remove workflow

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

  • Handles a normal role removal access request.

  • Includes a context check for administrator-submitted requests.

  • Skips the the approval task process and runs auto-approval and auto-deprovisioning scripts if the context check passes.

Assumptions

  • Each role has a role owner.

  • Notification settings and email templates exist.

  • Make sure to catch any error/failure conditions.

Example

An example of a role removal workflow.
  • 1 The Script node invokes the APIs and checks the context. If the context is admin or certification, it skips the manual approval process.

    Click to display request context check script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var context = null;
    var skipApproval = false;
    
    try {
      var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      if (requestObj.request.common.context) {
        context = requestObj.request.common.context.type;
        if (context === 'admin' || context === 'certification') {
          skipApproval = true;
        }
      }
    }
    catch (e) {}
    
    logger.info("Context: " + context);
    execution.setVariable("context", context);
    execution.setVariable("skipApproval", skipApproval);
  • 2 The Approval node assigns an approval task to users and roles. The node chains tasks in conjunction with a Switch node to implement serial or parallel flows.

    Click to display the approval task properties
    Item Description

    Name

    Approval Task

    Approvers

    Two options are available:

    • Add users and roles manually, such as Role Owner and define Approver type

      • Approve

      • Reject

      • Reassign

      • Modify

      • Comment

    • Define users using a script:

    Form

    Select a form to present to the reviewer:

    • Dynamic form selection. This selection is typically used for basic out-of-the-box workflows, like BasicApplicationGrant and others.

    • Choose a form. This selection is typically used for custom request type forms.

    Expiration Settings

    Options are:

    • Reject request

    • Reassign request

    • Do nothing

    Notification Settings

    Options are:

    • Assignment notification and email templates, such as requestAssigned.

    • Reassignment notification and email templates, such as requestReassigned.

    • Assignee reminders and email templates, such as requestReminder.

      • Sends every number of time periods, such as 3 day(s).

    • Escalation notifications and email templates, such as requestEscalated.

      • Send every number of day(s), such as 5 day(s).

      • Send to Send escalation to to User, and select User.

    • Expiration notification and email templates, such as requestExpired.

      • Send the notification on the configured number of days before expiration.

  • 3 Invokes the auto-approval script if scriptApproval is true.

    Click to display auto-approval script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var context = content.get('context');
    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 Runs a RejectRequest script when Approval task node returns a reject.

    Click to display RejectRequest 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);
  • 5 Run Auto Deprovisioning script.

    Click to display the auto deprovisioning script
    logger.info("Auto-Deprovisioning");
    
    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 = "Deprovisioning failed: Error reading request with id " + requestId;
    }
    
    if(!failureReason) {
      try {
        var request = requestObj.request;
        var payload = {
          "roleId": request.common.roleId,
          "startDate": request.common.startDate,
          "endDate": request.common.endDate,
          "auditContext": {},
          "grantType": "request"
        };
        var queryParams = {
          "_action": "remove"
        }
    
        var result = openidm.action('iga/governance/user/' + request.common.userId + '/roles' , 'POST', payload,queryParams);
      }
      catch (e) {
        failureReason = "Deprovisioning failed: Error deprovisioning role to user " + request.common.userId + " for role " + request.common.roleId + ". 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.");
    }

Download the JSON file for this workflow here.

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

Violation workflow

In this example, an administrator creates a workflow that:

  • Processes a single violation task.

  • If the violation outcome is Remediate, it remediates the violation, validates the result, and removes the entitlements.

  • If the violation outcome is Allow, it creates an exception.

  • If the violation outcome is expiration, it goes to a manual decision via the Fulfillment node.

  • If the end user tasked with the manual fulfillment approves of the various outcomes, the workflow is complete.

  • If the end user tasked with the manual fulfillment denies the resulting outcomes, the workflow calls a reject requests script, and loops back for another manual confirmation.

Assumptions

  • Each violation has an owner.

  • Make sure to catch any error/failure conditions.

Example

An example of a violation handling workflow.
  • 1 The Violation node routes the violation to the appropriate outcome. Options are: Remediate, Allow, and Expiration.

  • 2 The Remediate Violation Script node gets the context information for the violation and sets the remediationResponse.

    Click to display Remediate Violation script
    logger.info("Remediating violation");
    
    var content = execution.getVariables();
    var violationId = content.get('id');
    var remediation = content.get('remediation');
    logger.info("Remediating violation - violationId: " + violationId + ', remediation payload: ' + remediation);
    
    var remediationContent = null;
    
    var remediationResponse = openidm.action('iga/governance/violation/' + violationId + '/remediate', 'POST', remediation);
    logger.info("Remediating response: " + remediationResponse);
    
    remediationContent = remediationResponse.decision.remediation;
    execution.setVariable("remediation", remediationContent);
  • 3 The Remediate Violation IF/ELSE node routes successful validations to an auto remove script node and validation failures to a failure handling node.

  • 4 The Remove Grants Auto script node removes the entitlement grants that caused the violation.

    Click to display Auto Remove Entitlement Grants script
    logger.info("Removing grants automatically");
    
    var content = execution.getVariables();
    var violationId = content.get('id');
    var failureReason = null;
    var phaseName = content.get('phaseName');
    var violationObj;
    
    logger.info("Removing entitlement grants for violation " + violationId + " with phase name " + phaseName);
    
    try {
      violationObj = openidm.action('iga/governance/violation/lookup/' + violationId, 'GET', {}, {});
    }
    catch (e) {
      failureReason = "Removing entitlement grants failed: Error reading violation with id " + violationId + ". Error message: " + e.message;
    }
    
    if (!failureReason) {
      var remediation = violationObj.decision.remediation;
      var failedDeprovisioning = false;
      var deprovisionedIds = [];
      for(var grant of violationObj.violatingAccess) {
        if (!remediation.grantIds.includes(grant.compositeId)) {
          continue;
        }
    
        var userId = violationObj.user.id;
        logger.info("Removing entitlement grant: " + grant.compositeId + ", user: " + userId + ", violation: " + violationId);
    
        try {
          var payload = {
            entitlementId: grant.assignment.id
          };
          logger.info('Payload to remove grant: ' + JSON.stringify(payload));
    
          var queryParams = {
            "_action": "remove"
          }
    
          var result = openidm.action('iga/governance/user/' + userId + '/entitlements', 'POST', payload,queryParams);
          execution.setVariables(result);
    
          logger.info("Deprovisioned " + grant.assignment.id + " successfully, user " + userId + + ", violation: " + violationId);
          deprovisionedIds.push(grant.compositeId);
        }
        catch (e) {
          failureReason = failureReason + ". Removing grants failed: Error deprovisioning entitlement" + grant.assignment.id + " from user. Error message: " + e.message + ".";
          failedDeprovisioning = true;
        }
      }
    
      if (!failedDeprovisioning) {
        openidm.action('iga/governance/violation/' + violationId + '/remediation/status/complete', 'POST', {});
      } else {
        failureReason = failureReason + ". Grants removed: " + deprovisionedIds;
      }
    }
    
    if (failureReason) {
      var update = { 'comment': failureReason };
      openidm.action('iga/governance/violation/' + violationId + '/comment', 'POST', update, {});
    }
  • 5 The Allow Violation script node logs the information. If a failure arises, Identity Governance posts the failure with the reason.

    Click to display Allow Violation script node
    logger.info("Allowing violation");
    var content = execution.getVariables();
    var violationId = content.get('id');
    var phaseName = content.get('phaseName');
    logger.info("Violation to be allowed: " + violationId + " with phase name " + phaseName);
    
    var failureReason = null;
    try {
        var allowResponse = openidm.action('iga/governance/violation/' + violationId + '/allow', 'POST', {});
    logger.info("Violation " + violationId + " was allowed successfully.");} catch (e) {
        failureReason = "Failed allowing violation with id " + violationId + ". Error message: " + e.message;
    }
    
    if (failureReason) {
        var update = { "comment": failureReason };
        try {
            openidm.action('iga/governance/violation/' + violationId + '/phases/' + phaseName + '/comment', 'POST', update, {});
        } catch (e) {
            openidm.action('iga/governance/violation/' + violationId + '/comment', 'POST', update, {});
        }
    }
  • 6 The Fulfillment node requests a manual completion of the task by an authorized end user, typically during review time. If successful, the task is fulfilled, and the workflow is complete.

  • 7 The Reject Request script node retrieves the requestID, logs the rejection, and sends a reject 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);

Download the JSON file for this workflow here.

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

User create event workflow - send email

In this example, an administrator creates a workflow that:

  • Sends an email notification to the new user when a user create event occurs.

  • Copies their manager, as well, if present.

Assumptions

  • Each user has a manager.

  • Make sure to catch any error/failure conditions.

Example

An example of user create event send email workflow.
  • 1 The Script node sends email to the new user and cc’s to the user’s manager.

    Click to display send email script
    logger.info("Running user create event role workflow - send email");
    
    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 userObj = requestObj.request.common.blob.after;
      var userDisplayName = userObj.givenName + " " + userObj.sn + " (" + userObj.userName + ")";
      var body = {
        subject: "New user created: " + userDisplayName,
        to: userObj.mail,
        body: "New user created: " + userDisplayName + ".",
        object: {}
      };
      if (userObj && userObj.manager && userObj.manager.mail) {
        body.cc = userObj.manager.mail
      };
      openidm.action("external/email", "send", body);
    }
    catch (e) {
      logger.info("Unable to send new user creation email");
    }
    
    // Update event request as final
    var decision = {'status': 'complete', 'outcome': 'fulfilled', 'decision': 'approved'};
    var queryParams = { '_action': 'update'};
    openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams);
    logger.info("Request " + requestId + " completed.");

Download the JSON file for this workflow here.

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

User create event workflow - catalog lookup

In this example, an administrator creates a workflow that:

  • Submits a request to add the Data Analyst and Security roles to a newly created user when a user create event occurs.

  • Looks up the two roles in the catalog.

Assumptions

  • Roles exist in the catalog.

  • Make sure to catch any error/failure conditions.

Example

An example of user create event workflow to request two roles when a user is created.
  • 1 The Script node looks up two roles in the catalog. If the roles are present in the catalog, the script generates a request for roles.

    Click to display Submit Request for Roles script
    logger.info("Running user create event role workflow");
    
    var content = execution.getVariables();
    var requestId = content.get('id');
    var failureReason = null;
    var userObj = null;
    var userId = null;
    
    // Read event user information from request object
    try {
      var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      userObj = requestObj.request.common.blob.after;
      userId = userObj.userId;
    }
    catch (e) {
      failureReason = "Validation failed: Error reading request with id " + requestId;
    }
    
    // Define roles to request
    var roleNames = [ "Data Analyst", "Security" ];
    
    // Look up roles in catalog
    var operand = [];
    for (var index in roleNames) {
      operand.push({operator: "EQUALS", operand: { targetName: "role.name", targetValue: roleNames[index] }})
    }
    var body = { targetFilter: {operator: "OR", operand: operand}};
    var catalog = openidm.action("iga/governance/catalog/search", "POST", body);
    var catalogResults = catalog.result;
    
    // Define request catalogs key
    var catalogBody = [];
    for (var idx in catalogResults) {
      var catalog = catalogResults[idx];
      catalogBody.push({type: "role", id: catalog.id})
    }
    
    // Define request payload
    var requestBody = {
      priority: "low",
      accessModifier: "add",
      justification: "Request submitted on user creation.",
      users: [ userId ],
      catalogs: catalogBody
    };
    
    // Create requests
    try {
      openidm.action("iga/governance/requests", "POST", requestBody, {_action: "create"})
    }
    catch (e) {
      failureReason = "Unable to generate requests for roles";
    }
    
    // Update event request as final
    var decision = failureReason ?
      {'status': 'complete', 'outcome': 'cancelled', 'decision': 'rejected', 'comment': failureReason, 'failure': true} :
      {'status': 'complete', 'outcome': 'fulfilled', 'decision': 'approved'};
    var queryParams = { '_action': 'update'};
    openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams);
    logger.info("Request " + requestId + " completed.");

Download the JSON file for this workflow here.

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

User create event workflow - request two roles

In this example, an administrator creates a workflow that submits a separate request to add two roles to the newly created user. The script is triggered when a user create event occurs.

Assumptions

  • Roles exist in the catalog.

  • Make sure to catch any error/failure conditions.

Example

An example of user create event workflow to request two roles when a user is created.
  • 1 The Script node gets a user ID from the event request and returns the user object.

    Click to display Get User ID from event request script
    logger.info("Get User Id From Event Request: UserCreateEventWithSteps");
    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 userObj = requestObj.request.common.blob.after;
      execution.setVariable("userId", userObj.userId);
    }
    catch (e) {
      execution.setVariable("failureState", "Validation failed: Error reading request with id " + requestId);
    }
  • 2 The Script node makes a call to create the request. The payload contains two catalog IDs for the Data Analyst and Security roles.

    Click to display Submit request for roles script
    logger.info("Submit Role Requests: UserCreateEventWithSteps");
    var content = execution.getVariables();
    var userId = content.get('userId');
    var failureState = content.get('failureState');
    
    // Define request payload
    if (!failureState) {
      var requestBody = {
        priority: "low",
        accessModifier: "add",
        justification: "Request submitted on user creation: UserCreateEventWithSteps.",
        users: [ userId ],
        catalogs: [
          { type: "role", id: "b9224b9ae535c9eab3f493dc206ac689dc9f6733b417d0def37f8969bef3e95dad7c812e4585056f698c7b3eb15c970dfa939eca8217741af187978359af13df"},
          { type: "role", id: "e7ec51656c6f5ca297d82772a681e3069d8a7c24c04f15afaa8060856e17ad6e76f88bdeb635d4dc8c3d8faa462f376189322e85df379ae0721fcb2d28d1a222"}
        ]
      };
    
      // Create requests
      try {
        openidm.action("iga/governance/requests", "POST", requestBody, {_action: "create"})
      }
      catch (e) {
        execution.setVariable("failureState", "Unable to generate requests for roles");
      }
    }
  • 3 The Script node completes the request.

    Click to display Finalize request script
    logger.info("Finalize Request: UserCreateEventWithSteps");
    var content = execution.getVariables();
    var requestId  = content.get('requestId');
    var failureState = content.get('failureState');
    
    if (!failureState) {
      try {
        // Update event request as final
        var decision = {'status': 'complete', 'outcome': 'fulfilled', 'decision': 'approved'}
        var queryParams = { '_action': 'update'};
        openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams);
        logger.info("Request " + requestId + " completed.");
      }
      catch (e) {
        execution.setVariable("failureState", "Unable to finalize request.");
      }
    }
  • 4 The Script node handles any failures.

    Click to display Failure handler script
    logger.info("Failure Handler: UserCreateEventWithSteps");
    var content = execution.getVariables();
    var requestId  = content.get('requestId');
    var failureReason  = content.get('failureReason');
    
    // Update event request as final
    if (failureReason) {
      var decision = {'status': 'complete', 'outcome': 'cancelled', 'decision': 'rejected', 'comment': failureReason, 'failure': true}
      var queryParams = { '_action': 'update'};
      openidm.action('iga/governance/requests/' + requestId, 'POST', decision, queryParams);
      logger.info("Request " + requestId + " completed.");
    }

    The User create event workflow - send email, User create event workflow - catalog lookup, and User create event workflow - request two roles examples present User create event workflows. However, you can also adjust the workflows for User update events. For example, in the User create examples, the user object returns the current or after state of the user:

    var userObj = requestObj.request.common.blob.after

    Update events also have access to the before (or pre-update) state by referencing the object, which you can also use in your scripts.

    var userObj = requestObj.request.common.blob.before

Download the JSON file for this workflow here.

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

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.

Workflow using custom request type and form

In this example, an administrator wants to create a custom request type called Create New User to add new employees or contractors to the system. Administrators or form creators need to carry out the following tasks:

After these tasks, the approver receives the request and can start processing the approval.

Assumptions

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

  • You have designated an end user or a test user who can approve the request.

  • You have configured notifications to the end user or test user properly to receive the emails.

Example

Task 1: Create a custom request type

The initial task is to create a custom request type, Create New User that lets an administrator easily add a new user to the system. The 'Create New User' request type has the following nonmodifiable properties:

  • userName. Username of the new user.

  • givenName. First name of the new user.

  • sn. Last name of the new user.

  • mail. Email address of the new user.

Currently, the only way to create a custom request type is through the API. The UI will support this functionality in a future release.

  • Create a custom request type called createUser using the API. Enter the following command using curl to create your custom request type:

    Details
    curl --location 'http://<hostname>/iga/governance/requestTypes' \
    --header 'Authorization: Bearer token' \
    --header 'Content-Type: application/json' \
    --data '{
        "id": "createNewUser",
        "schemas": {
            "custom": [
                {
                    "_meta": {
                        "type": "system",
                        "displayName": "Create User",
                        "properties": {
                            "userName": {
                                "isRequired": true,
                                "isInternal": false,
                                "isMultiValue": false,
                                "display": {
                                    "name": "User Name",
                                    "isVisible": true,
                                    "order": 1,
                                    "description": "The userName of the new user"
                                }
                            },
                            "givenName": {
                                "isRequired": true,
                                "isInternal": false,
                                "isMultiValue": false,
                                "display": {
                                    "name": "First Name",
                                    "isVisible": true,
                                    "order": 2,
                                    "description": "The first name of the new user"
                                }
                            },
                            "sn": {
                                "isRequired": true,
                                "isInternal": false,
                                "isMultiValue": false,
                                "display": {
                                    "name": "Last Name",
                                    "isVisible": true,
                                    "order": 3,
                                    "description": "The last name of the new user"
                                }
                            },
                            "mail": {
                                "isRequired": true,
                                "isInternal": false,
                                "isMultiValue": false,
                                "display": {
                                    "name": "Email Address",
                                    "isVisible": true,
                                    "order": 4,
                                    "description": "The email address of the new user"
                                }
                            }
                        }
                    },
                    "properties": {
                        "userName": {
                            "type": "text"
                        },
                        "givenName": {
                            "type": "text"
                        },
                        "sn": {
                            "type": "text"
                        },
                        "mail": {
                            "type": "text"
                        }
                    }
                }
            ]
        },
        "workflow": {
            "id": "createNewUser",
            "type": "bpmn"
        },
        "validation": {
            "source": "var validation = {\"errors\" : [], \"comments\" : []}; if (request.custom.userName == undefined || request.custom.givenName == undefined || request.custom.sn == undefined ||  request.custom.mail == undefined) { validation.errors.push(\"Must include all of userName, givenName, sn, and mail fields.\");} validation;"
        },
        "custom": true,
        "displayName": "Create User",
        "uniqueKeys": [
            "custom.userName"
        ],
        "notModifiableProperties": []
    }'

Task 2: Create a form for the custom request type

You have two options to create a form for a custom request type: use the UI or the API.

Using the UI

  1. In the Advanced Identity Cloud admin UI, click Governance > Forms.

  2. On the New Form modal, click Custom request form, and then click Next.

  3. On the Custom request form modal, enter the following:

    Field Description

    Form

    Enter a descriptive name for your form.

    Description (optional)

    Enter a general description for your form.

    Request Type (optional)

    Select a custom request type from the list. In this example, select Create User.

    You can only assign one form to each request type.

    Once you create your form, you can go back and make edits to any of the previous form settings by clicking the ellipsis() in the top right, and then click Settings.

  4. Use the Forms editor to create a form for your custom request type. For example, drag-and-drop four text fields onto the canvas for the fields and label them: User Name, E-mail address, First Name, and Last Name.

    1. On the Forms editor canvas, drag-and-drop the Text node to the canvas, and fill in the properties in the right pane for the User Name field:

      User name text field properties
      Field Description

      Key

      Enter the key for the text string. You can retrieve this key from the curl step under the schemas entry. For example, enter custom.userName as the key.

      Label

      Enter a general label for this text field. For example, enter User Name.

      Description

      Enter help text for the text field. The description appears below your text field.

      Required

      Click if this text field is required. In this example, click Required.

      Read Only

      Click to make the field non-editable.

      Provide Default Value

      Click Provide Default Value to assign a default value for this text field. In this example, skip this step.

      Columns

      Enter the number of columns for this text field. Values are from 1 to 12. For this example, enter 6.

      Offset

      Enter the number of columns to offset from the left for this text field. Values are from 0 to 11. For this example, enter 0.

      Use validation

      Click if you want to validate the text field using a regular expression. In this example, skip this step.

      Regex

      Enter a regular expression to validate the text field.

      Error message

      Enter an error message when the regular expression fails.

    2. On the Forms editor canvas, drag-and-drop the Text node to the canvas, and fill in the properties in the right pane for the E-mail address field:

      E-mail address text field properties
      Field Description

      Key

      Enter the key for the text string. You can retrieve this key from the curl step under the schemas entry. For example, enter custom.mail as the key.

      Label

      Enter a general label for this text field. For example, enter E-mail address.

      Description

      Enter help text for the text field. The description appears below your text field.

      Required

      Click if this text field is required. In this example, click Required.

      Read Only

      Click to make the field non-editable.

      Provide Default Value

      Click Provide Default Value to assign a default value for this text field. In this example, skip this step.

      Columns

      Enter the number of columns for this text field. Values are from 1 to 12. For this example, enter 6.

      Offset

      Enter the number of columns to offset from the left for this text field. Values are from 0 to 11. For this example, enter 0.

      Use Validation

      Click if you want to validate the text field using a regular expression. In this example, skip this step.

      Regex

      Enter a regular expression to validate the text field.

      Error message

      Enter an error message when the regular expression fails.

    3. On the Forms editor canvas, drag-and-drop the Text node to the canvas, and fill in the properties in the right pane for the First Name field:

      First name text field properties
      Field Description

      Key

      Enter the key for the text string. You can retrieve this key from the curl step under the schemas entry. For example, enter custom.givenName as the key.

      Label

      Enter a general label for this text field. For example, enter First Name.

      Description

      Enter help text for the text field. The description appears below your text field.

      Required

      Click if this text field is required. In this example, click Required.

      Read Only

      Click to make the field non-editable.

      Provide Default Value

      Click Provide Default Value to assign a default value for this text field. In this example, skip this step.

      Columns

      Enter the number of columns for this text field. Values are from 1 to 12. For this example, enter 6.

      Offset

      Enter the number of columns to offset from the left for this text field. Values are from 0 to 11. For this example, enter 0.

      Use validation

      Click if you want to validate the text field using a regular expression. In this example, skip this step.

      Regex

      Enter a regular expression to validate the text field.

      Error message

      Enter an error message when the regular expression fails.

    4. On the Forms editor canvas, drag-and-drop the Text node to the canvas, and fill in the properties in the right pane for the Last Name field:

      Last name text field properties
      Field Description

      Key

      Enter the key for the text string. You can retrieve this key from the curl step under the schemas entry. For example, enter custom.sn as the key.

      Label

      Enter a general label for this text field. For example, enter Last Name.

      Description

      Enter help text for the text field. The description appears below your text field.

      Required

      Click if this text field is required. In this example, click Required.

      Read Only

      Click to make the field non-editable.

      Provide Default Value

      Click Provide Default Value to assign a default value for this text field. In this example, skip this step.

      Columns

      Enter the number of columns for this text field. Values are from 1 to 12. For this example, enter 6.

      Offset

      Enter the number of columns to offset from the left for this text field. Values are from 0 to 11. For this example, enter 0.

      Use validation

      Click if you want to validate the text field using a regular expression. In this example, skip this step.

      Regex

      Enter a regular expression to validate the text field.

      Error message

      Enter an error message when the regular expression fails.

  5. Click Save.

    An example of a form for the `Create User` custom request type.

Using the API

  1. Enter the following curl command to create your form for the custom request type:

    Details
    curl --location 'http://<hostname>/iga/governance/requestForms' \
    --header 'Authorization: Bearer token' \
    --header 'Content-Type: application/json' \
    --data '{
        "name": "Create New User",
        "type": "request",
        "description": "Form for creation of a new user",
        "categories": {
            "applicationType": null,
            "objectType": null,
            "operation": "create"
        },
        "form": {
            "fields": [
                {
                    "id": "dd155b12-fb27-44e5-b4d6-476587b31a71",
                    "model": "custom.userName",
                    "type": "string",
                    "label": "User Name",
                    "description": "User name of the new user",
                    "validation": {
                        "required": true
                    },
                    "layout": {
                        "columns": 6,
                        "offset": 0
                    }
                },
                {
                    "id": "88c73e69-86b1-453f-878b-527ceddeccf4",
                    "model": "custom.mail",
                    "type": "string",
                    "label": "E-mail address",
                    "description": "E-mail address of the new user",
                    "validation": {
                        "required": true
                    },
                    "layout": {
                        "columns": 6,
                        "offset": 0
                    }
                },
                {
                    "id": "683892f9-2c13-41c7-a1cc-fcf38d7d0183",
                    "model": "custom.givenName",
                    "type": "string",
                    "label": "First Name",
                    "description": "First name of the new user",
                    "validation": {
                        "required": true
                    },
                    "layout": {
                        "columns": 6,
                        "offset": 0
                    }
                },
                {
                    "id": "76fd5526-2ade-42a9-9b03-b6899e65aa31",
                    "model": "custom.sn",
                    "type": "string",
                    "label": "Last Name",
                    "description": "Last name of the new user",
                    "validation": {
                        "required": true
                    },
                    "layout": {
                        "columns": 6,
                        "offset": 0
                    }
                }
            ]
        }
    }'
  2. Enter the following curl command to assign the form to the custom request type.

    Details
    curl --location 'http://<hostname>/iga/governance/requestFormAssignments?_action=assign' \
    --header 'Authorization: Bearer token' \
    --header 'Content-Type: application/json' \
    --data '{
        "formId": "b309b500-112c-4e6d-b832-a902f91362a3",
        "objectId": "requestType/createNewUser"
    }'

Task 3: Create a workflow to use the custom request type and form

Create a new workflow called Create New User to use the custom request type and form.

An example of a workflow using the organization request type and form.
  • 1 Use a Script node to do a context check for the request.

    Click to display the Request Context Check script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var context = null;
    var skipApproval = false;
    try {
      var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      if (requestObj.request.common.context) {
        context = requestObj.request.common.context.type;
        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("skipApproval", skipApproval);
  • 2 Use an IF/ELSE node and name it Context Gateway. If skipApproval==true, route it to the Auto Approval node. If skipApproval==false, route it to the Approval Task node.

  • 3 Use a Script node for the Auto Approval task.

    Click to display the Auto Approval script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var context = content.get('context');
    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 Use a Script node to create a new user using the custom request type.

    Click to display the Create User script
    logger.info("Creating User");
    
    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 = {
          "userName": request.custom.userName,
          "givenName": request.custom.givenName,
          "sn": request.custom.sn,
          "mail": request.custom.mail,
          "password": 'DemoP@ssword1'
        };
    
        /** Create new user **/
        var result = openidm.create('managed/alpha_user', null, payload, queryParams);
    
        /** Send new user email **/
        var body = {
          subject: "Welcome " + payload.givenName + " " + payload.sn + "!",
          to: payload.mail,
          body: "Your new user has been created in the system.\n\nUsername: " + payload.userName + "\nPassword: " + payload.password + "\n\nLogin to your account here: https://openam-gov-dev-4.forgeblocks.com/am/XUI/?realm=/alpha#/",
          object: {}
        };
        openidm.action("external/email", "send", body);
      }
      catch (e) {
        failureReason = "Creating user failed: Error during creation of user " + request.custom.userName + ". 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.");
    }
  • 5 The Approval node assigns an approval task to users and roles. The node chains tasks in conjunction with a Switch node to implement serial or parallel flows.

    Click to display the Approval Task properties
    Item Description

    Name

    Approval Task

    Approvers

    Two options are available:

    • Add users and roles manually, such as Role Owner, and define the Approver type. For this example, click . In the Approver Type field, select User, and then select a user. Give the approvers all permissions below. Click Add.

      • Approve

      • Reject

      • Reassign

      • Modify

      • Comment

    • Define users using a script:

    Form

    Select a form to present to the reviewer.

    • Dynamic form selection. Skip this step

    • Click Choose a form and select Create New User.

    Expiration Settings

    Options are:

    • Reject request. For this example, you can select this option.

    • Reassign request

    • Do nothing

    Notification Settings

    Options are:

    • Assignment notification and email templates, such as requestAssigned.

    • Reassignment notification and email templates, such as requestReassigned.

    • Assignee reminders and email templates, such as requestReminder.

      • Sends every number of time periods, such as 3 day(s).

    • Escalation notifications and email templates, such as requestEscalated.

      • Send every number of day(s), such as 5 day(s).

      • Send to Send escalation to to User and select User.

    • Expiration notification and email templates, such as requestExpired.

      • Send a configured number of days before expiration.

  • 6 Use the Script node to process any request rejections.

    Click to display the 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);

Download the JSON file for this workflow here.

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

Task 4: Submit the custom request

You can enter a curl command to submit a Create New User request.

curl --location 'https://<hostname>/iga/governance/requests/createNewUser' \
--header 'Authorization: Bearer token' \
--header 'Content-Type: application/json' \
--data-raw '{
    "custom": {
        "userName": "acabby",
        "givenName": "Amy",
        "sn": "Cabby",
        "mail": "amy.cabby@example.com"
    }
}'
Example Response
{
  "id": "d289d1dd-b376-4860-a3d9-db3dd29702b2",
  "requester": {
    "givenName": "Joe",
    "id": "managed/teammember/ce6ef368-c050-4131-bc07-32aa4f58a785",
    "mail": "joe.admin@example.com",
    "sn": "Admin",
    "userName": "jadmin"
  },
  "requestType": "createNewUser",
  "request": {
    "custom": {
      "userName": "acabby",
      "givenName": "Amy",
      "sn": "Cabby",
      "mail": "amy.cabby@example.com"
    },
    "_rev": 1,
    "common": {
      "isDraft": false,
      "context": {
        "type": "request"
      }
    }
  },
  "decision": {
    "status": "in-progress",
    "decision": null,
    "type": "request",
    "outcome": null,
    "startDate": "2024-09-09T15:53:49+00:00",
    "completionDate": null,
    "deadline": null,
    "comments": [],
    "actors": {
      "active": [
        {
          "givenName": "Frank",
          "id": "managed/teammember/ce6ef368-c050-4131-bc07-32aa4f58a785",
          "mail": "frank.york@exampe.com",
          "sn": "York",
          "userName": "fyork",
          "permissions": {
            "approve": false,
            "comment": true,
            "modify": false,
            "reassign": false,
            "reject": false,
            "cancel": false,
            "fulfill": false,
            "deny": false
          }
        }
      ],
      "inactive": [],
      "actorTags": [
        "activeId=managed%2Fteammember%2Fce6ef368-c050-4131-bc07-32aa4f58a785&phase=",
        "phase=&activeId=managed%2Fteammember%2Fce6ef368-c050-4131-bc07-32aa4f58a785"
      ]
    }
  }
}

Approver Task: Process the request

  1. Once the administrator submits the request, the approver (for example, "Frank York") receives a notification email.

    Notification email sent to the approver.
  2. The approver logs in to the Advanced Identity Cloud end-user UI and clicks Pending Approvals.

    Approver’s end-user UI displaying requests

  3. The approver can carry out different tasks: click Approve, Reject, or ellipsis () to Forward, Add Comment, or View Details.

    Approver clicks the specific create user request
  4. The approver clicks View Details.

    The approver review the details of the request.

Workflow using organization custom request type and form

In this example, an administrator wants to create a custom request type called Create Organization to add new organizations to the system. Administrators and form creators need to carry out the following tasks:

After these tasks, the approver receives the request and can start reviewing it.

Assumptions

  • You have designated an end user who can function as an approver and review the request.

  • You have configured notification emails to alert the approver of incoming requests.

Example

Task 1: Create an organization request type

The initial task is to create a custom request type, Create Organization that lets an administrator easily add a new organization to the system.

Currently, the only way to create a custom request type is through the API. The UI will support this functionality in a future release.

  • Create a custom request type called Create Organization using the API. Enter the following command using curl to create your custom request type:

    Details
    curl --location 'http://<hostname>/iga/governance/requestTypes' \
    --header 'Authorization: Bearer token' \
    --header 'Content-Type: application/json' \
    --data '{
        "id": "createOrganization",
        "schemas": {
            "common": [
                {
                    "_meta": {
                        "type": "system",
                        "displayName": "commonRequest",
                        "properties": {
                            "justification": {
                                "isRequired": false,
                                "isInternal": true,
                                "display": {
                                    "name": "Justification",
                                    "isVisible": true,
                                    "order": 3,
                                    "description": "The reason for the request"
                                }
                            },
                            "externalRequestId": {
                                "isRequired": false,
                                "isInternal": true,
                                "isChangable": false,
                                "display": {
                                    "name": "External Request ID",
                                    "isVisible": true,
                                    "order": 4,
                                    "description": "The external ID for the request"
                                }
                            },
                            "requestIdPrefix": {
                                "isRequired": false,
                                "isInternal": true,
                                "display": {
                                    "name": "Request ID prefix",
                                    "isVisible": true,
                                    "order": 5,
                                    "description": "Prefix for the request ID"
                                }
                            },
                            "isDraft": {
                                "isRequired": false,
                                "isInternal": true
                            },
                            "priority": {
                                "isRequired": false,
                                "display": {
                                    "name": "Priority",
                                    "isVisible": true,
                                    "order": 6,
                                    "description": "The priority of the reqeust"
                                },
                                "text": {
                                    "defaultValue": "low"
                                }
                            },
                            "expiryDate": {
                                "isRequired": false,
                                "isInternal": true,
                                "display": {
                                    "name": "Request expiration date",
                                    "isVisible": true,
                                    "order": 7,
                                    "description": "User provided date on which the request will cancel"
                                }
                            },
                            "context": {
                                "isRequired": false,
                                "isInternal": true,
                                "isMultiValue": false,
                                "display": {
                                    "name": "Context",
                                    "isVisible": true,
                                    "order": 1,
                                    "description": "The context of the request"
                                }
                            },
                            "workflowId": {
                                "isRequired": false,
                                "isInternal": true,
                                "isChangable": false,
                                "display": {
                                    "name": "BPMN workflow ID",
                                    "isVisible": true,
                                    "order": 7,
                                    "description": "The ID key of the BPMN workflow"
                                }
                            },
                            "blob": {
                                "isRequired": false,
                                "isInternal": true
                            }
                        }
                    },
                    "properties": {
                        "justification": {
                            "type": "text"
                        },
                        "externalRequestId": {
                            "type": "text"
                        },
                        "requestIdPrefix": {
                            "type": "text"
                        },
                        "isDraft": {
                            "type": "boolean"
                        },
                        "priority": {
                            "type": "text"
                        },
                        "expiryDate": {
                            "type": "text"
                        },
                        "context": {
                            "type": "object"
                        },
                        "workflowId": {
                            "type": "text"
                        },
                        "blob": {
                            "type": "object"
                        }
                    }
                }
            ],
            "custom": [
                {
                    "_meta": {
                        "type": "system",
                        "displayName": "Create Organization",
                        "properties": {
                            "name": {
                                "isRequired": true,
                                "isInternal": false,
                                "isMultiValue": false,
                                "display": {
                                    "name": "Name",
                                    "isVisible": true,
                                    "order": 1,
                                    "description": "The name of the organization"
                                }
                            },
                            "description": {
                                "isRequired": false,
                                "isInternal": false,
                                "isMultiValue": false,
                                "display": {
                                    "name": "Description",
                                    "isVisible": true,
                                    "order": 2,
                                    "description": "The description of the organization being created"
                                }
                            },
                            "parent": {
                                "isRequired": false,
                                "isInternal": false,
                                "isMultiValue": false,
                                "display": {
                                    "name": "Parent Organization",
                                    "isVisible": true,
                                    "order": 3,
                                    "description": "The ID of the parent organization for this organization"
                                }
                            },
                            "admins": {
                                "isRequired": false,
                                "isInternal": false,
                                "isMultiValue": true,
                                "display": {
                                    "name": "Organization Administrators",
                                    "isVisible": true,
                                    "order": 4,
                                    "description": "The IDs of the users who will be this organization's admins"
                                }
                            },
                            "includeSelfAdmin": {
                                "isRequired": false,
                                "isInternal": false,
                                "isMultiValue": false,
                                "display": {
                                    "name": "Include Self as Admin",
                                    "isVisible": true,
                                    "order": 4,
                                    "description": "If selected, include requester as an organization admin"
                                }
                            }
                        }
                    },
                    "properties": {
                        "name": {
                            "type": "text"
                        },
                        "description": {
                            "type": "text"
                        },
                        "parent": {
                            "type": "text"
                        },
                        "admins": {
                            "type": "text"
                        },
                        "includeSelfAdmin": {
                            "type": "boolean"
                        }
                    }
                }
            ]
        },
        "workflow": {
            "id": "createOrganization",
            "type": "bpmn"
        },
        "validation": {
            "source": "var validation = {\"errors\" : [], \"comments\" : []}; if (request.custom.name == undefined) { validation.errors.push(\"Must include all name field.\");} validation;"
        },
        "custom": true,
        "displayName": "Create Organization",
        "uniqueKeys": [
            "custom.name"
        ],
        "notModifiableProperties": []
    }'

Task 2: Create a form for the organization request type

  1. In the Advanced Identity Cloud admin UI, click Governance > Forms.

  2. On the New Form modal, click Custom request form, and then click Next.

  3. On the Custom request form modal, enter the following:

    Field Description

    Form

    Enter a descriptive name for your form. In this example, enter: Create Organization.

    Description (optional)

    Enter a general description for your form. In this example, enter: Form used to create a new organization.

    Request Type (optional)

    Click and select a request type for the form. In this example, enter: Create Organization.

    You can only assign one form to each request type.

    Once you create your form, you can go back and make edits to any of the previous form settings by clicking the ellipsis() in the top right, and then click Settings.

  4. Use the Forms editor to create a form for your custom request type. For example, drag-and-drop five form fields onto the canvas for the fields and label them:

    1. On the Forms editor canvas, drag-and-drop the Text node to the canvas, and fill in the properties in the right pane for the Organization Name field:

      Organization name text field properties
      Field Description

      Key

      Enter the key for the text string. You can retrieve this key from the example in Task 1 under the schemas entry. For example, enter custom.name as the key.

      Label

      Enter a general label for this text field. For example, enter User Name.

      Description

      Enter help text for the text field. The description appears below your text field. Enter Name of the organization.

      Required

      Click if this text field is required. In this example, click Required.

      Read Only

      Click to make the field non-editable. In this example, skip this step.

      Provide Default Value

      Click Provide Default Value to assign a default value for this text field. In this example, skip this step.

      Columns

      Enter the number of columns for this text field. Values are from 1 to 12. For this example, use the default (12 columns).

      Offset

      Enter the number of columns to offset from the left for this text field. Values are from 0 to 11. For this example, use the default (0).

      Use validation

      Click if you want to validate the text field using a regular expression. In this example, skip this step.

    2. On the Forms editor canvas, drag-and-drop the Textarea node to the canvas, and fill in the properties in the right pane for the Description field:

      Description textarea field properties
      Field Description

      Key

      Enter the key for the text string. You can retrieve this key from the example in Task 1 under the schemas entry. For example, enter custom.description as the key.

      Label

      Enter a general label for this text field. For example, enter Description.

      Description

      Enter help text for the text field. The description appears below your text field.

      Required

      Click if this text field is required. In this example, skip this step.

      Read Only

      Click to make the field non-editable. In this example, skip this step.

      Provide Default Value

      Click Provide Default Value to assign a default value for this text field. In this example, skip this step.

      Columns

      Enter the number of columns for this text field. Values are from 1 to 12. For this example, use the default (12 columns).

      Offset

      Enter the number of columns to offset from the left for this text field. Values are from 0 to 11. For this example, use the default (0).

      Use Validation

      Click if you want to validate the text field using a regular expression. In this example, skip this step.

    3. On the Forms editor canvas, drag-and-drop the Select node to the canvas, and fill in the properties in the right pane for the Parent Organization field:

      Parent Organization Select field properties
      Field Description

      Key

      Enter the key for the text string. You can retrieve this key from the example in Task 1 under the schemas entry. For example, enter custom.parent as the key.

      Label

      Enter a general label for this text field. For example, enter Parent Organization (optional).

      Description

      Enter help text for the text field. The description appears below your text field.

      Required

      Click if this text field is required. In this example, skip this step.

      Read Only

      Click to make the field non-editable. In this example, skip this step.

      Options

      Click to manually add enumerated values for your Select menu options:

      1. Click .

      2. On the Add Option modal, do the following:

        1. Value: enter a value for the option field. For example, in the Advanced Identity Cloud end-user UI, click Identities > Manage > <realm> realm - Organizations and search for an organization, such as the Engineering organization, you can view the ID as a27172ad-8adf-425e-a93f-ebd7c7aaa776. Enter this managed organization ID in the Value field.

        2. Label: enter a label for the option field. In this example, enter: Engineering.

        3. Click Selected by default to make the option a default enumerated value for the field.

      3. Click Add.

        1. Repeat the steps 1–3 again to add more Select menu options.

      Provide Default Value

      Click Provide Default Value to assign a default value for this text field. In this example, skip this step.

      Columns

      Enter the number of columns for this text field. Values are from 1 to 12. In this example, use the default (12).

      Offset

      Enter the number of columns to offset from the left for this text field. Values are from 0 to 11. For this example, enter 0.

      Use validation

      Click if you want to validate the text field using a regular expression. In this example, skip this step.

      Regex

      Enter a regular expression to validate the text field.

      Error message

      Enter an error message when the regular expression fails.

    4. On the Forms editor canvas, drag-and-drop the Select node to the canvas, and fill in the properties in the right pane for the Organization Admins field:

      Organization Admins Select field properties
      Field Description

      Key

      Enter the key for the text string. You can retrieve this key from the example in Task 1 under the schemas entry. For example, enter custom.admins as the key.

      Label

      Enter a general label for this text field. For example, enter Organization Admins (optional).

      Description

      Enter help text for the text field. The description appears below your text field.

      Required

      Click if this text field is required. In this example, skip this step.

      Read Only

      Click to make the field non-editable. In this example, skip this step.

      Options

      Click to manually add enumerated values for your Select menu options:

      1. Click .

      2. On the Add Option modal, do the following:

        1. Value: enter a value for the option field. For example, in the Advanced Identity Cloud end-user UI, click Identities > Manage > <realm> realm - Users and search for a user, such as Frank York. You can view the ID as c51d9ee1-43b3-49d1-8742-cbb33842a5cc. Enter this managed user ID in the Value field.

        2. Label: enter a label for the option field. In this example, enter an admin user, such as Frank York.

        3. Click Selected by default to make the option a default enumerated value for the field. In this example, skip this step.

      3. Click Add.

        1. Repeat the steps 1–3 again to add more options.

      Provide Default Value

      Click Provide Default Value to assign a default value for this text field. In this example, skip this step.

      Columns

      Enter the number of columns for this text field. Values are from 1 to 12. In this example, use the default (12).

      Offset

      Enter the number of columns to offset from the left for this text field. Values are from 0 to 11. In this example, use the default (0).

      Use validation

      Click if you want to validate the text field using a regular expression. In this example, skip this step.

    5. On the Forms editor canvas, drag-and-drop the Checkbox node to the canvas, and fill in the properties in the right pane for the Include yourself as administrator field:

      Include yourself as administrator Checkbox field properties
      Field Description

      Key

      Enter the key for the text string. You can retrieve this key from the example in Task 1 under the schemas entry. For example, enter custom.includeSelfAdmin as the key.

      Label

      Enter a general label for this text field. For example, enter Include yourself as administrator.

      Description

      Enter help text for the text field. The description appears below your text field.

      Read Only

      Click to make the field non-editable. In this example, skip this step.

      Provide Default Value

      Click Provide Default Value to assign a default value for this text field. In this example, skip this step.

      Columns

      Enter the number of columns for this text field. Values are from 1 to 12. In this example, use the default (12).

      Offset

      Enter the number of columns to offset from the left for this text field. Values are from 0 to 11. In this example, use the default (0).

      Use validation

      Click if you want to validate the text field using a regular expression. In this example, skip this step.

  5. Click Preview to review your form.

  6. Click Save.

    An example of a form for the `Create Organization` custom request type.

Task 3: Create a workflow to use the organization request type and form

An example of a workflow using the organization request type and form.
  • 1 Use a Script node to do a context check for the request.

    Click to display the Request Context Check script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var context = null;
    var skipApproval = false;
    try {
      var requestObj = openidm.action('iga/governance/requests/' + requestId, 'GET', {}, {});
      if (requestObj.request.common.context) {
        context = requestObj.request.common.context.type;
        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("skipApproval", skipApproval);
  • 2 Use an IF/ELSE node and name it Context Gateway. If skipApproval==true, route it to the Auto Approval node. If skipApproval==false, route it to the Approval Task node.

  • 3 Use a Script node for the Auto Approval task.

    Click to display the Auto Approval script
    var content = execution.getVariables();
    var requestId = content.get('id');
    var context = content.get('context');
    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 Use a Script node to create a new organization using the custom request type.

    Click to display the Create Organization script node
    logger.info("Creating Organization");
    
    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 = {
          "name": request.custom.name,
          "description": request.custom.description,
          "admins": []
        };
    
        if (request.custom.parent) {
          payload.parent = {
            "_ref": "managed/alpha_organization/" + request.custom.parent,
            "_refProperties": {}
          }
        }
    
        if (request.custom.admins) {
          request.custom.admins.forEach(function(admin) {
            payload.admins.push({ "_ref": "managed/alpha_user/" + admin, "_refProperties": {}});
          });
        }
    
        if (request.custom.includeSelfAdmin) {
          payload.admins.push({ "_ref": "managed/alpha_user/" + requestObj.requester.id.split('/')[2], "_refProperties": {}});
        }
    
        /** Create new org **/
        var result = openidm.create('managed/alpha_organization', null, payload);
    
        /** Send new org admins email **/
    
        payload.admins.forEach(function(admin) {
          var id = admin._ref
          var adminUser = openidm.read(id);
          var body = {
            subject: "Welcome " + adminUser.givenName + " " + adminUser.sn + "!",
            to: adminUser.mail,
            body: "You have been added as an administrator for a new organization.\nOrganization: " + payload.name + "",
            object: {}
          };
          openidm.action("external/email", "send", body);
        });
      }
      catch (e) {
        failureReason = "Creating org failed: Error during creation of organization " + request.custom.name + ". Error: " + 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.");
    }
  • 5 The Approval node assigns an approval task to users and roles. The node chains tasks in conjunction with a Switch node to implement serial or parallel flows.

    Click to display the Approval Task properties
    Item Description

    Name

    Approval Task

    Approvers

    Two options are available:

    • Add users who can approve the request, such as fyork, and define the Approver type. For this example, click . In the Approver Type field, select User, and then select a user. Give the approver all permissions below. Click Add.

      • Approve

      • Reject

      • Reassign

      • Modify

      • Comment

    • Define users using a script:

    Form

    Select a form to present to the reviewer.

    • Dynamic form selection. Skip this step

    • Click Choose a form and select Create Organization.

    Expiration Settings

    Options are:

    • Reject request. For this example, you can select this option.

    • Reassign request

    • Do nothing

    Notification Settings

    Options are:

    • Assignment notification and email templates, such as requestAssigned.

    • Reassignment notification and email templates, such as requestReassigned.

    • Assignee reminders and email templates, such as requestReminder.

      • Sends every number of time periods, such as 3 day(s).

    • Escalation notifications and email templates, such as requestEscalated.

      • Send every number of day(s), such as 5 day(s).

      • Send to Send escalation to to User and select User.

      • Send escalation to: Select User and select a user to handle the escalation.

    • Expiration notification and email templates, such as requestExpired.

      • Send a configured number of days before expiration.

  • 6 Use the Script node to process any request rejections.

    Click to display the 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);

Download the JSON file for this workflow here.

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

Task 4: Submit the organization request

You can enter a curl command to submit a Create Organization request.

curl --location 'https://<hostname>/iga/governance/requests/createOrganization?_action=publish' \
--header 'Authorization: Bearer token' \
--header 'Content-Type: application/json' \
--data '{
    "custom": {
        "name": "IGA Organization",
        "description": "Organization for all IGA members.",
        "parent": "a27172ad-8adf-425e-a93f-ebd7c7aaa776",
        "admins": [
            "c51d9ee1-43b3-49d1-8742-cbb33842a5cc",
            "e140e883-8ec7-4201-889d-f77ea118fcf3",
            "adeb08c0-f3b7-44eb-a31a-a31fba305a40"
        ],
        "includeSelfAdmin": false
    },
    "common": {

    }
}'

Currently, you must include the common key either unpopulated or to provide additional information, such as justification.

Approver Task: Process the organization request

Once the administrator submits the request, the approver (for example, "Frank York") can review the request.

+ image::governance-end-user-approver-org-request.png["Organization request", role="border"]