We are working one task to disable the AD account of the user those who are not login from last 90 day’s. For that we have AD attribute called LastloginTimeStamp so we need to get that attribute and compair with the current/today’s date. Base on this condition we need to disable the only AD account for those user’s. Do’s anyone is have this logic or sample logic so i can get idea to implement that.
If i setup the rapidsetup for this actives the hole identity account is disable?. But here we just want to disable the user’s/identity AD application account rest other account should be active state, they should not be disable.
I have imported rule from Globule Setting–> Import from file–Import Localized Attributes, But i don’s see that rule from the debug, Where i need to check that rule?
I’ve implemented something similar to this as a lifecycle event, with a trigger that selects identities based on the confluence of lastLoginTimestamp, lastLogin and an Azure last login sync’d to an Active Directory extended attribute. When triggered, the standard workflow model is invoked, along with notifications and audit entries.
Obviously, the workflow generates a provisioning plan against the AD link changing its state from active to inactive. It also sets the AD description to indicate what was done and why.
Thank you for the response, Just wants to conform you have implemented through the customization rule or the lifecycle event?. I have the rule but when i’m tying to import getting some error and not able to see that rule in the debug. might be have some issue in the code itself. Is if possible to share that code hiding the confidential information?.
I have tried to import the attached file getting below error.
Error: Unable to locate object type: [Application] with name: [ accList.add(String.format([%s] %s] for import
Error: Unable to locate object type: [Application] with name: [ taskResult.setAttribute(accounts] for import
Import complete:
total lines processed: 154
bad data lines: 140
localized attributes created: 14
descriptions updated: 0 AD Account Disable.txt (6.6 KB)
My approach was to use a lifecycle event. The event uses an identityTrigger rule as the selector and fires a workflow whose purpose is to create a provisioning plan to disable and update the description on the Active Directory account for a selected identity.
Please share the steps to setup the lifecycle event how it looks like and please share the code as well hiding the confidential information that will be helpful for me.
Background: The requirement is to disable any non-privileged Active Directory after 30 days of inactivity as measured by the lastLogonTimestamp, lastLogon and a date propagated from Azure into msExchExtensionAttribute41 (we found that there are circumstances where users may be actively using Azure resources, but the on-prem timestamps did not get updated). We also avoid disabling accounts for new hires by consulting the whenCreated attribute, giving them a grace period of 60 days from account creation before considering them for inactivity disablement.
The basic date format for most things in my implementation is yyyyMMdd, but the native AD formats vary from that.
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Rule language="beanshell" modified="1716384137414" name="Rule - IdentityTrigger - 30 Day Inactivity Disable" type="IdentityTrigger">
<ReferencedRules>
<Reference class="sailpoint.object.Rule" name="SP Library - Provisioning Rules"/>
<Reference class="sailpoint.object.Rule" name="SP Library - Attr Synch Rules"/>
<Reference class="sailpoint.object.Rule" name="LCE Library - Common Rules"/>
</ReferencedRules>
<Signature returnType="boolean">
<Inputs>
<Argument name="log">
<Description>
The log object associated with the SailPointContext.
</Description>
</Argument>
<Argument name="context">
<Description>
A sailpoint.api.SailPointContext object that can be used to query the database if necessary.
</Description>
</Argument>
<Argument name="previousIdentity">
<Description>
The identity before the refresh/aggregation (this will be null when an
identity is created).
</Description>
</Argument>
<Argument name="newIdentity">
<Description>
The identity after the refresh/aggregation (this will be null when an
identity is deleted).
</Description>
</Argument>
</Inputs>
<Returns>
<Argument name="result">
<Description>
A boolean describing the result of the rule.
</Description>
</Argument>
</Returns>
</Signature>
<Source>
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import sailpoint.api.SailPointFactory;
import sailpoint.api.SailPointContext;
import java.util.*;
import java.text.*;
import java.time.*;
import javax.naming.Context;
import javax.naming.InitialContext;
private static Log logger = LogFactory.getLog("rule.30dayInactivityTrigger");
logger.trace("Enter Rule - IdentityTrigger - 30 Day Inactivity Disable");
Boolean disable = false;
boolean timeToDisable = false;
Link adLink;
String lastLogon;
String lastLogonTimestamp;
String azureLogin;
String whenCreated;
String dn;
String uac;
// We only want to disable accounts in the evening...
String now = LocalTime.now().getHour().toString();
if (now.equals("17") || now.equals("18") || now.equals("19") || now.equals("20") || now.equals("21") || now.equals("22") || now.equals("23") || now.equals("00") || now.equals("01") || now.equals("02") || now.equals("03")) {
timeToDisable = true;
}
if (null != newIdentity && void != newIdentity) {
// Calls a method in the common rules library which returns the non-privileged link, ignoring privileged accounts.
adLink = getNonPrivLink(newIdentity,"AD.local");
}
DateFormat adlFormat = new SimpleDateFormat("yyyy.MMM.dd hh:mm:ss aa");
DateFormat wdFormat = new SimpleDateFormat("MMddyyyy");
DateFormat azFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'"); //2024-01-26T13:10:31Z
Date lastLogonDate;
Date lastLogonTimestampDate;
Date azLogonDate;
Date whenCreatedDate;
Date today = new Date();
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -42); // to make up for long weekends, holidays, etc.
Date datePast30 = cal.getTime();
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -60);
Date datePast60 = cal.getTime();
if (null != adLink && void != adLink) {
dn = adLink.getAttribute("distinguishedName");
if (logger.isTraceEnabled()) logger.trace("dn: " + dn);
uac = adLink.getAttribute("userAccountControl").toString();
if (logger.isTraceEnabled()) logger.trace("uac: " + uac);
lastLogon = adLink.getAttribute("lastLogon");
if (logger.isTraceEnabled()) logger.trace("lastLogon: " + lastLogon);
if (null == lastLogon || void == lastLogon) {
lastLogonDate = datePast60;
} else {
lastLogonDate = adlFormat.parse(lastLogon);
}
lastLogonTimestamp = adLink.getAttribute("lastLogonTimestamp");
if (logger.isTraceEnabled()) logger.trace("lastLogonTimestamp: " + lastLogonTimestamp);
if (null == lastLogonTimestamp || void == lastLogonTimestamp) {
lastLogonTimestampDate = datePast60;
} else {
lastLogonTimestampDate = adlFormat.parse(lastLogonTimestamp);
}
azureLogin = adLink.getAttribute("msExchExtensionAttribute41");
// "2023-02-27T19:41:30.169Z" vs. "2023-12-02T08:36:19Z"
if (logger.isTraceEnabled()) logger.trace("Input azureLogin: " + azureLogin);
// a bit of consistency checking to account for varied date format from Azure
if (null == azureLogin || void == azureLogin) {
azLogonDate = datePast60;
} else if (azureLogin.length() == 20) {
azLogonDate = azFormat.parse(azureLogin);
if (logger.isTraceEnabled()) logger.trace("azLogonDate: " + azLogonDate);
} else if (azureLogin.length() == 24 || azureLogin.length() == 23) {
azLogonDate = azFormat.parse(azureLogin.substring(0,azureLogin.indexOf(".")-1) + "Z");
if (logger.isTraceEnabled()) logger.trace("from long format azLogonDate: " + azLogonDate);
}
whenCreated = adLink.getAttribute("whenCreated");
if (logger.isTraceEnabled()) logger.trace("whenCreated: " + whenCreated);
if (null == whenCreated || void == whenCreated) {
whenCreatedDate = datePast60;
} else {
whenCreated = whenCreated.substring(4,8) + whenCreated.substring(0,4); // need to flip/flop
whenCreatedDate = wdFormat.parse(whenCreated);
// We move terminated accounts to an OU containing "Deprovision" where they are stored, disabled, for some time before deletion. No need to disable anything already disabled or in the "Deprovision" OU.
if ((lastLogonDate.before(datePast30) && lastLogonTimestampDate.before(datePast30) && whenCreatedDate.before(datePast30) && azLogonDate.before(datePast30)) &&
!dn.contains("Deprovision") && !uac.equals("514") && timeToDisable) {
disable = true;
}
}
}
if (logger.isTraceEnabled()) logger.trace("Exit Rule - IdentityTrigger - 30 Day Inactivity Disable with " + disable);
return disable;
</Source>
</Rule>
**
The workflow itself is very simple: call the build plan rule, call the OOTB LCM provisioner, send an e-mail to the manager of the disabled account.
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Rule language="beanshell" name="Rule - 30 Day Disable Build Plan" type="Workflow">
<Description>A rule used by a Workflow to determine a step action or variable value.
Note that an Attributes map of all variables from the current WorkflowContext, merged with the arguments from the Step, is also passed into the workflow rule.</Description>
<ReferencedRules>
<Reference class="sailpoint.object.Rule" name="LCE Library - Common Rules"/>
</ReferencedRules>
<Signature returnType="Object">
<Inputs>
<Argument name="log">
<Description>
The log object associated with the SailPointContext.
</Description>
</Argument>
<Argument name="context">
<Description>
A sailpoint.api.SailPointContext object that can be used to query the database if necessary.
</Description>
</Argument>
<Argument name="wfcontext">
<Description>
The current WorkflowContext.
</Description>
</Argument>
<Argument name="handler">
<Description>
The workflow handler associated with the current WorkflowContext.
</Description>
</Argument>
<Argument name="workflow">
<Description>
The current Workflow definition.
</Description>
</Argument>
<Argument name="step">
<Description>
The current Step.
</Description>
</Argument>
<Argument name="approval">
<Description>
The current Approval.
</Description>
</Argument>
<Argument name="item">
<Description>
The WorkItem being processed.
</Description>
</Argument>
</Inputs>
<Returns>
<Argument name="Object">
<Description>
The result of the workflow rule; dependent on the rule itself.
</Description>
</Argument>
</Returns>
</Signature>
<Source>
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
import java.util.List;
import java.util.ArrayList;
import sailpoint.object.Identity;
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
Logger logger = Logger.getLogger("rule.30DayDisable.Rule.BuildPlan");
logger.trace("30 Day Disable - Build Plan Rule");
Identity identityObject = (Identity) wfcontext.getVariable("identityObject"); //defined as a workflow variable
ProvisioningPlan plan = new ProvisioningPlan();
// List of account requests in the plan
List accreqs = new ArrayList();
String identityName = identityObject.getName();
if (logger.isTraceEnabled()) logger.trace("Identity Name " + identityName);
// Don't need to check anything, just set Description and disable the account
Date today = new Date();
DateFormat dtFormat = new SimpleDateFormat("MM/dd/yyyy");
String dateString = dtFormat.format(today);
String descString = "Disabled " + dateString + " - LastLogon > 30 Days";
// Links are defined in the Workflow as workflow variables, must null check
if (null != adlLink) {
logger.trace("AD.local Link found");
AccountRequest adAcct = new AccountRequest(AccountRequest.Operation.Modify, (String) wfcontext.getVariable("adlAppName"), null, adlLink.getNativeIdentity());
logger.trace("Declared AD.local AccountRequest");
adAcct.add(new AttributeRequest("description", ProvisioningPlan.Operation.Set, descString));
adAcct.add(new AttributeRequest("userAccountControl", opSet, "514"));
logger.trace("Attribute Request Set");
accreqs.add(adAcct);
}
plan.setAccountRequests(accreqs);
plan.setIdentity(identityObject);
if (logger.isTraceEnabled()) logger.trace("30 Day Disable - Build Plan = " + plan.toXml());
return plan;
</Source>
</Rule>