Workflow use cases
This section presents workflow use cases featuring workflow nodes. For detailed information on each node, refer to Workflow nodes.
This section covers the following workflows 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
-
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 roleSales App Approver
to approve the request. -
finance
— An Approval node requires members ot the foleFinance App Approver
to approve the request. -
humanResources
— An Approval node requires members of the roleHuman 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, aprivileged
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
-
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
-
Each role has a role owner.
-
You create a role glossary attribute (string with enumerated value),
riskLevel
, that has the following values:-
low
-
medium
-
high
For each role, this attribute is populated.
-
-
Your end users have a manager assigned to them. An administrator populates this property and isn’t modifiable by the end user.
Example
-
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
ornull
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
-
1 The Script node invokes the APIs and checks the context. If the context is
admin
orcertification
, 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 defineApprover
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
toUser
, and selectUser
.
-
-
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 whenApproval task
node returns areject
.Click to display
RejectRequest
scriptlogger.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.
Example
-
1 The Violation node routes the violation to the appropriate outcome. Options are:
Remediate
,Allow
, andExpiration
. -
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.
Example
-
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
andSecurity
roles to a newly created user when a user create event occurs. -
Looks up the two roles in the catalog.
Example
-
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
scriptlogger.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.
Example
-
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
scriptlogger.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
andSecurity
roles.Click to display
Submit request for roles
scriptlogger.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
scriptlogger.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
scriptlogger.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 forUser update
events. For example, in theUser 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
-
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.
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
Using the UI
-
In the Advanced Identity Cloud admin UI, click Governance > Forms.
-
On the New Form modal, click Custom request form, and then click Next.
-
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.
-
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
, andLast Name
.-
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, entercustom.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.
-
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, entercustom.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.
-
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, entercustom.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.
-
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, entercustom.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.
-
-
Click Save.
Using the API
-
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 } } ] } }'
-
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.
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
-
Once the administrator submits the request, the approver (for example, "Frank York") receives a notification email.
-
The approver logs in to the Advanced Identity Cloud end-user UI and clicks Pending Approvals.
-
The approver can carry out different tasks: click Approve, Reject, or ellipsis () to Forward, Add Comment, or View Details.
-
The approver clicks View Details.
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.
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
-
In the Advanced Identity Cloud admin UI, click Governance > Forms.
-
On the New Form modal, click Custom request form, and then click Next.
-
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.
-
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:
-
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 propertiesField 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, entercustom.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.
-
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 propertiesField 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, entercustom.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.
-
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 propertiesField 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, entercustom.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:
-
Click .
-
On the Add Option modal, do the following:
-
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 asa27172ad-8adf-425e-a93f-ebd7c7aaa776
. Enter this managed organization ID in the Value field. -
Label: enter a label for the option field. In this example, enter:
Engineering
. -
Click Selected by default to make the option a default enumerated value for the field.
-
-
Click Add.
-
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.
-
-
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 propertiesField 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, entercustom.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:
-
Click .
-
On the Add Option modal, do the following:
-
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 asc51d9ee1-43b3-49d1-8742-cbb33842a5cc
. Enter this managed user ID in the Value field. -
Label: enter a label for the option field. In this example, enter an admin user, such as
Frank York
. -
Click Selected by default to make the option a default enumerated value for the field. In this example, skip this step.
-
-
Click Add.
-
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.
-
-
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 propertiesField 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, entercustom.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.
-
-
Click Preview to review your form.
-
Click Save.
Task 3: Create a workflow to use the organization request type and form
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 |