You have to consider expired time as a rejected status in the workflow.
I faced the same issue while implementing a custom workflow. Therefore, I considered expired requests as rejected, and then the transaction was completed by marking the request as rejected.
Could you please advise where the best place in the workflow would be to customize the logic so that, if the work item expires at the first approval level, the access request is automatically denied (DENIED) and no provisioning is executed and so it would not impact the lcm provisioning workflow ?
I dont see the lastApprovalState variable in the workflows i think it’s a variable that is inside the workitem
Here is the code of the Provisioning Approval Subprocess workflow :
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Workflow PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Workflow explicitTransitions="true" libraries="Identity" name="Provisioning Approval Subprocess" type="Subprocess">
<Variable initializer="serial" input="true" name="approvalMode">
<Description>
A string that specifies how we should handle the approvals.
By default this is serial since most of these request with
the exception of manager transfers will have only one approver.
parallel
Approvals are processed concurrently and there must be consensus,
we wait for all approvers to approve. The first approver that
rejects terminates the entire approval.
parallelPoll
Approvals are processed concurrently but consensus is not required.
All approvals will be processed, we don't stop if there are any
rejections.
serial
Approvals are processed one at a time and there must be consensus.
The first approver that rejects terminates the entire approval.
serialPoll
Approvals are processed in order but consensus is not required.
All approvals will be processed, we don't stop if there are any
rejections. In effect we are "taking a poll" of the approvers.
any
Approvals are processed concurrently, the first approver to
respond makes the decision for the group.
</Description>
</Variable>
<Variable input="true" name="approvalScheme">
<Description>
A csv string that specifies how approval items should be generated
for the incoming request.
The value can be "none", in which case approvals are disabled.
The value can also be a combination of any of the values below
in any order, separated by commas. The order in which they are
specified is the order in which they are processed:
owner
The object owner gets the approval item.
For Role approvals this is the Role object owner.
For Entitlement approvals this is the Entitlement object owner.
manager
The manager gets the approval item.
securityOfficer
The identity in the variable securityOfficerName gets the approval item.
identity
The identities/workgroups in the variable approvingIdentities get the approval item.
</Description>
</Variable>
<Variable input="true" name="approvingIdentities">
<Description>
List of identities and/or workgroups names/ids that should be involved in the approval
process.
</Description>
</Variable>
<Variable initializer="spadmin" input="true" name="fallbackApprover">
<Description>
A String that specifies the name of the Identity that will
be assigned any approvals where the owner of the approver
can't be resolved. Example if the scheme is "owner" and the
application doesn't specify and owner.
</Description>
</Variable>
<Variable input="true" name="flow">
<Description>
The name of the LCM flow that launched this workflow.
This is one of these values:
AccountsRequest
EntitlementsRequest
RolesRequest
IdentityCreateRequest
IdentityEditRequest
ForgotPassword
ExpiredPassword
PasswordRequest
joiner
mover
leaver
AttributeSync
</Description>
</Variable>
<Variable input="true" name="identityName">
<Description>The name of the identity being updated.</Description>
</Variable>
<Variable input="true" name="identityDisplayName">
<Description>
The displayName of the identity being updated.
Query for this using a projection query and fall back to the name.
</Description>
</Variable>
<Variable input="true" name="identityRequestId">
<Description>
The ID of the IdentityRequest for this request.
</Description>
</Variable>
<Variable input="true" name="noTriggers">
<Description>
If true, then do no attempt to trigger lifecycle events.
</Description>
</Variable>
<Variable input="true" name="policyViolations">
<Description>
List of policy violations that were found during our initial policy scan.
This list is passed into each work item so the approvers can see
pending violations.
</Description>
</Variable>
<Variable input="true" name="managerElectronicSignature">
<Description>
The name of the electronic signature object that should be used when workitems
are completed by a manager.
</Description>
</Variable>
<Variable input="true" name="ownerElectronicSignature">
<Description>
The name of the electronic signature object that should be used when workitems
are completed by object owners.
</Description>
</Variable>
<Variable input="true" name="securityOfficerElectronicSignature">
<Description>
The name of the electronic signature object that should be used when workitems
are completed by the security officer.
</Description>
</Variable>
<Variable input="true" name="identityElectronicSignature">
<Description>
The name of the electronic signature object that should be used when workitems
are completed by object owners.
</Description>
</Variable>
<Variable initializer="LCM Identity Update Approval" input="true" name="approvalEmailTemplate">
<Description>
Name of the email template to use when notifying the approver of pending approvals.
</Description>
</Variable>
<Variable initializer="LCM Identity Update Approval" input="true" name="managerEmailTemplate">
<Description>
Name of the email template to use when notifying the manager of pending approvals.
</Description>
</Variable>
<Variable initializer="LCM Identity Update Approval" input="true" name="ownerEmailTemplate">
<Description>
Name of the email template to use when notifying the owner of pending approvals.
</Description>
</Variable>
<Variable initializer="LCM Identity Update Approval" input="true" name="securityOfficerEmailTemplate">
<Description>
Name of the email template to use when notifying the security officer of pending approvals.
</Description>
</Variable>
<Variable initializer="LCM Identity Update Approval" input="true" name="identityEmailTemplate">
<Description>
Name of the email template to use when notifying the security officer of pending approvals.
</Description>
</Variable>
<Variable initializer="true" name="trace">
<Description>
Used for debugging this workflow and when set to true trace
will be sent to stdout.
</Description>
</Variable>
<Variable input="true" name="workItemDescription">
<Description>String "template" that will be used as the workitem description.</Description>
</Variable>
<Variable initializer="SomeValue" input="true" name="workItemEscalationTemplate">
<Description>The email template that should be used when sending out reminders.</Description>
</Variable>
<Variable initializer="SomeValue" input="true" name="workItemHoursBetweenReminders">
<Description>The number of hours to wait between sending reminders to approvers.</Description>
</Variable>
<Variable initializer="SomeValue" input="true" name="workItemHoursTillEscalation">
<Description>The number of hourse to wait until an approval should be escalated.</Description>
</Variable>
<Variable initializer="Normal" input="true" name="workItemPriority">
<Description>
The String version of a WorkItem.Priority. This variable is
used to set the priority on all of the workitems generated
as part of this workflow and also set on the IdentityRequest
object.
</Description>
</Variable>
<Variable initializer="LCM Identity Approval Reminder" input="true" name="workItemReminderTemplate">
<Description>The email template that should be used when sending out reminders.</Description>
</Variable>
<Variable input="true" name="workItemEscalationRule">
<Description>The rule used to assign a new ownder during escalation.</Description>
</Variable>
<Variable initializer="SomeValue" input="true" name="workItemMaxReminders">
<Description>The maximum number of reminder emails that will be sent before we begin the escalation process.</Description>
</Variable>
<Variable initializer="true" input="true" name="filterRejects">
<Description>True to filter rejected items when running in Serial/SerialPoll mode.</Description>
</Variable>
<Variable initializer="false" input="true" name="setPreviousApprovalDecisions">
<Description>True to pre-populate approval decisions from previous approvals.</Description>
</Variable>
<Variable initializer="false" input="true" name="clearApprovalDecisions">
<Description>True to clear all decisions when generating approvals</Description>
</Variable>
<Variable initializer="true" input="true" name="dontUpdatePlan">
<Description>True to skip updating the ProvisioningProject masterPlan when processing approval decisions.</Description>
</Variable>
<Variable input="true" name="approvalSet" output="true">
<Description>
ApprovalSet representing the things that were requested and come from
the master provisioning plan.
</Description>
</Variable>
<Variable input="true" name="project" output="true">
<Description>
ProvisioningProject which is just a compiled version of the ProvisioningPlan, the
master plan will be adjusted when approval decisions are applied and a new
revised provisiobion project will be returned.
</Description>
</Variable>
<Variable input="true" name="plan" output="true">
<Description>The provisioning plan.</Description>
</Variable>
<Variable initializer="false" input="true" name="requireCommentsForApproval">
<Description>When true we will require comments when an approval item is approved.</Description>
</Variable>
<Variable initializer="false" input="true" name="requireCommentsForDenial">
<Description>When true we will require comments when an approval item is denied.</Description>
</Variable>
<Description>
Simple approval subrocess introduced into release 6.2 to make
adding custom approvals easier. This approval subprocess
has a configuration form to make it easier to configure
from the Business Process Editor.
</Description>
<RuleLibraries>
<Reference class="sailpoint.object.Rule" name="LCM Workflow Library"/>
</RuleLibraries>
<Step icon="Start" name="Start" >
<Transition to="Approval"/>
</Step>
<Step icon="Approval" name="Approval">
<Approval mode="ref:approvalMode" owner="call:buildCommonApprovals" renderer="lcmWorkItemRenderer.xhtml" send="identityDisplayName,identityName,approvalSet,flow,policyViolations,identityRequestId">
<AfterScript>
<Source>
import sailpoint.workflow.IdentityRequestLibrary;
if ( item == null )
return;
assimilateWorkItemApprovalSet(wfcontext, item, approvalSet);
auditDecisions(item);
IdentityRequestLibrary.assimilateWorkItemApprovalSetToIdentityRequest(wfcontext, approvalSet);
</Source>
</AfterScript>
<Arg name="launcher" value="ref:launcher"/>
<Arg name="workItemDescription" value="ref:workItemDescription"/>
<Arg name="workItemEscalationRule" value="ref:workItemEscalationRule"/>
<Arg name="workItemEscalationTemplate" value="ref:workItemEscalationTemplate"/>
<Arg name="workItemHoursTillEscalation" value="ref:workItemHoursTillEscalation"/>
<Arg name="workItemMaxReminders" value="ref:workItemMaxReminders"/>
<Arg name="workItemEscalationFrequency" value="ref:workItemEscalationFrequency"/>
<Arg name="workItemHoursBetweenReminders" value="ref:workItemHoursBetweenReminders"/>
<Arg name="workItemIdentityRequestId" value="ref:identityRequestId"/>
<Arg name="workItemNotificationTemplate" value="ref:approvalEmailTemplate"/>
<Arg name="workItemPriority" value="ref:workItemPriority"/>
<Arg name="approvalMode" value="ref:approvalMode"/>
<Arg name="approvalScheme" value="ref:approvalScheme"/>
<Arg name="approvalSplitPoint" value="ref:approvalSplitPoint"/>
<Arg name="clearApprovalDecisions" value="ref:clearApprovalDecisions"/>
<Arg name="workItemReminderTemplate" value="ref:workItemReminderTemplate"/>
<Arg name="workItemRequester" value="$(launcher)"/>
<InterceptorScript>
<Source>
import sailpoint.object.Workflow.Approval;
import sailpoint.object.ApprovalItem;
import sailpoint.object.ApprovalSet;
import sailpoint.object.Workflow;
import sailpoint.object.WorkItem;
import sailpoint.tools.Util;
import java.util.Iterator;
import java.util.ArrayList;
import java.lang.Boolean;
if (Workflow.INTERCEPTOR_PRE_ASSIMILATION.equals(method)) {
// promote completion state to Rejected if all items are rejected
ApprovalSet aset = item.getApprovalSet();
if (aset != null) {
List items = aset.getItems();
if (items != null) {
int rejectCount = 0;
for (ApprovalItem item : items) {
// note that isRejected can't be used since that
// assumes no answer means rejected
if (item.getState() == WorkItem.State.Rejected) {
rejectCount++;
}
}
if (rejectCount == items.size()) {
item.setState(WorkItem.State.Rejected);
}
}
}
} else if (Workflow.INTERCEPTOR_START_APPROVAL.equals(method)) {
ApprovalSet currentSet = approval.getApprovalSet();
if (currentSet != null && !Util.isEmpty(currentSet.getItems())) {
//If filterRejects is true, filter any rejected items in the master ApprovalSet from the currentSet
if (Boolean.valueOf(filterRejects)) {
filterRejectsFromApprovalSet(approvalSet, currentSet);
}
//We've filtered all items from the approval set
if (Util.isEmpty(currentSet.getItems())) {
//Complete the approval if it contains no ApprovalItems
approval.setComplete(true);
} else {
if (Boolean.valueOf(setPreviousApprovalDecisions)) {
// If setPreviousApprovalDecisions is enabled, set the decision
// on the items to that of the global item.
setPreviousDecisionsOnApprovalSet(approvalSet, currentSet);
}
}
}
} else if (Workflow.INTERCEPTOR_END_APPROVAL.equals(method)) {
// Owner children approvals need to propagate the rejection state if all children are rejected
Approval parentApp = approval.getParent();
// Never relay the state to the root approval
// If we don't have an approvalSet, must be a container approval
if (parentApp != null && approval.getApprovalSet() == null) {
boolean completeAndRejected = false;
for (Approval child : Util.safeIterable(approval.getChildren())) {
if (child.isComplete() && child.getState() == WorkItem.State.Rejected) {
completeAndRejected = true;
} else {
completeAndRejected = false;
break;
}
}
//If all children complete and rejected, set the status on the parent approval
if (completeAndRejected) {
approval.setState(WorkItem.State.Rejected);
}
}
} else if (Workflow.INTERCEPTOR_OPEN_WORK_ITEM.equals(method)) {
import sailpoint.workflow.IdentityRequestLibrary;
//Sync IdentityRequestItems with the WorkItem
IdentityRequestLibrary.assimilateWorkItemApprovalSetToIdentityRequest(wfcontext, item.getApprovalSet(), false);
}
</Source>
</InterceptorScript>
</Approval>
<Transition to="Process Approval Decisions" when="script:(step.getApproval() != null && step.getApproval().containsApprovalItems())"/>
<Transition to="end"/>
</Step>
<Step action="call:processApprovalDecisions" icon="Task" name="Process Approval Decisions" resultVariable="project">
<Arg name="approvalSet" value="ref:approvalSet"/>
<Arg name="disableAudit" value="true"/>
<Arg name="project" value="ref:project"/>
<Arg name="plan" value="ref:plan"/>
<Arg name="recompilePlan" value="true"/>
<Arg name="dontUpdatePlan" value="ref:dontUpdatePlan"/>
<Transition to="end"/>
</Step>
<Step icon="Stop" name="end"/>
</Workflow>
You need to mark all items in the approvalSet as rejected** before returning to the parent workflow. This is what the parent workflow checks to determine if provisioning should occur. try it and let me know
<Step name="Expiring WorkItem">
<Script>
<Source>
import sailpoint.object.ApprovalSet;
import sailpoint.object.ApprovalItem;
import sailpoint.object.WorkItem;
import org.apache.log4j.Logger;
Logger log = Logger.getLogger("sailpoint.workflow.expiry");
log.error("EXPIRY CHECK: Marking all approval items as Rejected due to expiration");
if (approvalSet != null) {
List items = approvalSet.getItems();
if (items != null) {
for (ApprovalItem item : items) {
item.setState(WorkItem.State.Rejected);
if (item.getApproverComments() == null) {
item.setApproverComments("Auto-rejected: Work item expired");
}
log.error("EXPIRY CHECK: Rejected item - " + item.getValue());
}
}
}
// Also clear the project/plan to prevent provisioning
project = null;
plan = null;
</Source>
</Script>
<Transition to="end"/>
</Step>
And update your transitions in the Approval step to be in this order:
Yeah, my bad “item.setState(WorkItem.State.Rejected);” is correct, may be while copying in my eclipse , it came item.setRejected(true); , because i was just checking.. Glad your issue is resolved.
We took a slightly different approach, by using the “workItemMaxAge” variable in LCM Provisioning.
<Variable initializer="56" name="workItemMaxAge">
<Description>
Number of days before a workitem is expired / canceled
</Description>
</Variable>
<Step icon="Stop" name="end" posX="710" posY="175">
<Script>
<Source>
<![CDATA[
import progressive.core.WorkflowApproval;
import sailpoint.object.ApprovalItem;
import sailpoint.object.IdentityRequest;
import sailpoint.object.IdentityRequestItem;
import sailpoint.object.IdentityRequest.ExecutionStatus;
import sailpoint.workflow.IdentityRequestLibrary;
import java.util.Date;
IdentityRequest ir = IdentityRequestLibrary.getIdentityRequest(wfcontext);
if (ir != null) {
if (WorkflowApproval.hasExpiredApprovals(ir.getApprovalSummaries())) {
Date terminationDate = new Date();
for (IdentityRequestItem item : ir.getItems()) {
item.setApprovalState(WorkItem.State.Canceled);
item.setProvisioningState(ApprovalItem.ProvisioningState.Failed);
item.setEndDate(terminationDate);
}
ir.setExecutionStatus(ExecutionStatus.Terminated);
ir.setState("End");
ir.computeCompletionStatus();
ir.setEndDate(terminationDate);
context.saveObject(ir);
context.commitTransaction();
}
}
]]>
</Source>
</Script>
</Step>
// Custom Java code - can be moved to the workflow step as inline code.
public static boolean hasExpiredApprovals(List<ApprovalSummary> summaries) {
for (ApprovalSummary as : Util.safeIterable(summaries)) {
if (Util.nullSafeEq(as.getState(), State.Expired) == true) {
return true;
}
}
return false;
}
The variable is then added to the “Identity Request Approve Identity Changes” workflow:
<Variable input="true" name="workItemMaxAge">
<Description>
Number of days before a workitem is expired / canceled
</Description>
</Variable>
And then passed as an argument to the “Approval” step:
We also have an additional customization to the Do Provisioning Forms workflow, to handle expiration of provisioning form work items for requests that don’t even make it to the approval sub-workflow. I can share that approach with you as well.
what is it?? I did not get it?? if you have any queries, can you please create a different topic and there we can suggest it. This topic is already closed.