Account not disabled after user moved in AD

Hi Experts,

We have a BeforProvisioning rule where we are setting the AC_NewParent AttributeRequest for the disabled user. This works perfectlöy fine when the LCS of the user is updated and the user is moved to the disabled OU ax expected. However, the account is not disabled, although we have the following configuration set on the Identity Profile:

Any ideas why this is so?

Looking forward to your inputs!

Thanks,
Tamalika

It should disable the user by default, nothing needs to be done.

Check if there is any error while disabling account.

Also check your BP Rule, if the Account request operation is modified from disable to modify.

– Krish

Yes the Operation is indeed Modify, as the rule is triggered after the LCS is changed. We mapped the cloudifecyclestate directly to one of the AD Attributes in AttributeSync. I also enabled logging in the AfterModify native rules and I see that no “Disable” operation is being called on that user, only modify.

When LCS becomes inactive, SailPoint triggers account disable operation automatically.

Provisioning Plan will have Access request with operation as Disable, which will disable AD account as an OOTB feature.

If you change the operation to modify then it cannot disable the account.

So you need to add additional Account request to the Plan with AC_NewParent attribute Request.

or you can use UAC (UserAccountControl) attribute and set it as 514 to disable.


Actually you don’t need BP Rule to disable account and move to different OU, you can make use of Disable Account Provisioning Policy.

– Krish

1 Like

@tamalika01 you refer to the sample snippet for your reference

ty    import sailpoint.object.ProvisioningPlan;
    import sailpoint.object.ProvisioningPlan.AccountRequest;
    import sailpoint.object.ProvisioningPlan.AttributeRequest;
    import sailpoint.object.ProvisioningPlan.Operation;
    import sailpoint.object.Identity;
    import sailpoint.tools.Util;
    import sailpoint.tools.GeneralException;
    import sailpoint.rule.Account;

    import java.text.SimpleDateFormat;
    import java.util.Date;

    //Stores the name of the AD application.
    String AD_APP_NAME = (application != null) ? application.getName() : null;
    String ACTIVE_OU = "OU=People,DC=dev,DC=Appal,DC=local";
    String DISABLED_OU = "OU=Disabled,DC=dev,DC=Appal,DC=local";

    /*
     *Method to return App AD Account Request from Plan
     *@return adAcctRequest
     */
    public AccountRequest getADAccountRequest() {
        log.info("Entering App-ActiveDirectory BeforeProvisioning: getADAccountRequest()");
        
        AccountRequest adAcctRequest = null;
        if (null != plan && !plan.isEmpty()) {
            List accRequests = Util.filterNulls(plan.getAccountRequests());
            if (null != accRequests && !accRequests.isEmpty()) {
                for (AccountRequest acctRequest: accRequests) {
                    String appName = acctRequest.getApplication();
                    if (null != appName && appName.contains("Active Directory")) {
                        adAcctRequest = acctRequest;
                        log.debug("Found an account request for Active Directory.");
                        break;
                    }
                }
            }
        }
        
        log.debug("Exiting App-ActiveDirectory BeforeProvisioning: getADAccountRequest()");
        return adAcctRequest;
    }

    /*
     *Method to revoke groups when user is terminated
     *@param adAcctRequest: App AD AccountRequest
     *@return adAcctRequest: Updated App AD AccountRequest with revoke groups AttributeRequest added
     */
    public AccountRequest removeGroups(AccountRequest adAcctRequest) {
        log.debug("Entering App-ActiveDirectory BeforeProvisioning: removeGroups()");
        log.debug("Input:");
        log.debug("adAcctRequest: " + adAcctRequest);

        List existingGroups = Util.filterNulls(getGroupsFromADAccount());
        if (null != existingGroups && !existingGroups.isEmpty()) {
            for (String group: existingGroups) {
                AttributeRequest removeGroup = new AttributeRequest("memberOf", ProvisioningPlan.Operation.Remove, group);
                adAcctRequest.add(removeGroup);
                log.debug("Removing group: " + group);
            }
        }
        
        log.debug("Exiting App-ActiveDirectory BeforeProvisioning: removeGroups()");
        return adAcctRequest;
    }

    /*
     *Method to return AD groups that are already provisioned to user
     *@return existingGroups: list of groups that are already provisioned to user
     */
    public List getGroupsFromADAccount() {
        log.debug("Entering App-ActiveDirectory BeforeProvisioning: getGroupsFromADAccount()");

        List existingGroups = new ArrayList();
        if(null != plan) {
            Identity identity = plan.getIdentity();
            String sAMAccountName = identity.getAttribute("lanid");
            if (Util.isNotNullOrEmpty(sAMAccountName) && Util.isNotNullOrEmpty(AD_APP_NAME) && null != idn) {
                Account account = idn.getAccountByDisplayName(AD_APP_NAME, sAMAccountName);
                log.debug("AD account: " + account);
                
                existingGroups = Util.filterNulls(Util.asList(idn.getRawAccountAttribute(account, "memberOf")));
                log.debug("Existing groups: " + existingGroups);
                
                if(null!=existingGroups) log.debug("Found " + existingGroups.size() + " groups");
                else log.debug("No groups found on the user's AD account");
            }
        }

        log.debug("Exiting App-ActiveDirectory BeforeProvisioning: getGroupsFromADAccount()");
        return existingGroups;
    }

    /*
     *Method to check if AD account exists on IdentityNow
     *@return boolean true/false
     */
    public boolean adAccountExistsOnIDN(String adDN) {
        log.debug("Entering App-ActiveDirectory BeforeProvisioning: adAccountExistsOnIDN()");
        log.debug("Input:");
        log.debug("adDN: " + adDN);
        
        boolean accountExists = false;
        if(Util.isNotNullOrEmpty(adDN) && Util.isNotNullOrEmpty(AD_APP_NAME) && null != idn) {
            Account account = idn.getAccountByNativeIdentity(AD_APP_NAME, adDN);
            if(null != account) accountExists = true;
            else log.debug("Could not find an AD account for the user " + adDN);
        }
        
        log.debug("Exiting App-ActiveDirectory BeforeProvisioning: adAccountExistsOnIDN()");
        return accountExists;
    }

    /*
     *Method to check if AD account is disabled on IdentityNow
     *@return boolean true/false
     */
    public boolean isADAccountDisabled(String adDN) {
        log.debug("Entering App-ActiveDirectory BeforeProvisioning: isADAccountDisabled()");
        log.debug("Input:");
        log.debug("adDN: " + adDN);
        
        boolean disabled = false;
        if(Util.isNotNullOrEmpty(adDN) && Util.isNotNullOrEmpty(AD_APP_NAME) && null != idn) {
            disabled = idn.getAccountAttributeBool(AD_APP_NAME, adDN, "IIQDisabled");
        }
        log.debug("Is AD Account Disabled: " + disabled);
        
        log.debug("Exiting App-ActiveDirectory BeforeProvisioning: isADAccountDisabled()");
        return disabled;
    }


    /*
     *Method to disable AD Account on the target
     *@return adAcctRequest
     */
    public AccountRequest disableADAccount(AccountRequest adAcctRequest, String adDN, String employeeType, String cloudLifecycleState) {
        log.debug("Entering App-ActiveDirectory BeforeProvisioning: disableADAccount()");
        log.debug("Inputs:");
        log.debug("adAcctRequest: " + adAcctRequest);
        log.debug("adDN: " + adDN);
        log.debug("employeeType: " + employeeType);
        log.debug("cloudLifecycleState: " + cloudLifecycleState);

        String newParent = null;
        if(Util.isNotNullOrEmpty(adDN) && null != adAcctRequest) {
            if(adDN.contains(ACTIVE_OU)) {
                if("Inactive".equalsIgnoreCase(cloudLifecycleState)) {
                    log.debug("Moving user to LOA OU.");
                    newParent = "OU=LOA," + DISABLED_OU;
                }
                
            }
            else {
                log.debug("User is not currently in the active users OU and might already be disabled.");
            }
            if(Util.isNotNullOrEmpty(newParent)) {
                AttributeRequest newParentRequest = new AttributeRequest("AC_NewParent", ProvisioningPlan.Operation.Set, newParent); 
                adAcctRequest.add(newParentRequest);
                adAcctRequest.setOperation(AccountRequest.Operation.Disable);
            }
            else {
                log.debug("Error: AD Account "+ adDN +" could not be disabled.");
            }
        }
        
        log.debug("Exiting App-ActiveDirectory BeforeProvisioning: disableADAccount()");
        return adAcctRequest;
    }


    log.debug("Entering App-ActiveDirectory BeforeProvisioning");
    log.debug("AD_APP_NAME: " + AD_APP_NAME);

    if (null != plan) {
        if (null != plan.getIdentity()) {
            Identity identity = plan.getIdentity();
            AccountRequest adAcctRequest = getADAccountRequest();

            if (
                null != adAcctRequest && 
                null != adAcctRequest.getOperation() && 
                Util.isNotNullOrEmpty(identity.getAttribute("cloudLifecycleState"))
            ) 
            {
                String cloudLifecycleState = identity.getAttribute("cloudLifecycleState");
                String employeeType = identity.getAttribute("employeeType");
                String termDate = identity.getAttribute("termDate");
                String adDN = adAcctRequest.getNativeIdentity();
                
                //LCS Specific Business Logic
                if ("Active".equalsIgnoreCase(cloudLifecycleState))
                      
                 {
                    log.debug("Prehire/Active/LOAActive LCS Triggered.");

                    if(
                        AccountRequest.Operation.Create.equals(adAcctRequest.getOperation()) || 
                        AccountRequest.Operation.Modify.equals(adAcctRequest.getOperation())
                    ){
                        log.debug("Create/Modify AD Request identified.");
                       
                       
                    }
                   
                }
                else if ("Inactive".equalsIgnoreCase(cloudLifecycleState) && Util.isNotNullOrEmpty(adDN)) {
                    if(
                        AccountRequest.Operation.Disable.equals(adAcctRequest.getOperation())
                    ){
                        log.debug("Disabled LCS Triggered: AD Account Disable Operation Identified.");

                        log.debug("Removing user from all their AD Groups");
                        adAcctRequest = removeGroups(adAcctRequest);

                        log.debug("Disabling user AD account");
                        adAcctRequest = disableADAccount(adAcctRequest, adDN, employeeType, cloudLifecycleState);
                    }
                }
                 
                
                
            }
            else {
                log.debug("Account Request is null for App AD: " + identity.getName());
            }
        } else {
            log.debug("Identity is null");
        }
    } else {
        log.debug("ProvisioningPlan is null for App AD");
    }

    log.debug("Exiting App-ActiveDirectory BeforeProvisioning");
pe or paste code here

@schattopadhy thank you, that was indeed very helpful of you. I see that in addition to adding the AC_NewParent in the AccountRequest, you have also set the operation to disable, right? I will use your script as a reference in that case,

This is a far better and simpler method for moving OUs when disabling an account

1 Like

I have not changed the operation at all, it seems that since we have mapped the LCS identity attribute in the Attribute Sync with an AD attribute, the operation Disable is overwritten by Modify. Also, I was not aware of the Disable Account provisioning policy, I will look that up, thanks! If you have any reference pointing to any documentation, would be great if you could share it here.

You can create Provisioning Policy forms through Postman or VSCode.

It’s convenient with VSCode, just goto sources → expand your source → click + sign next to Provisioning Policies and choose Disable. Use below config

{
    "name": "Disable Account",
    "description": null,
    "usageType": "DISABLE",
    "fields": [
        {
            "name": "AC_NewParent",
            "transform": {
                "type": "static",
                "attributes": {
                    "value": "OU=Disabled,OU=Accounts,DC=xyz,DC=com"
                }
            },
            "attributes": {},
            "isRequired": false,
            "type": "string",
            "isMultiValued": false
        }
    ]
} 
1 Like

@tamalika01 yes we are disable it as well

thank you @KRM7 , this will trigger on “Disable” I believe. However, we have configured 2 LCSs for Disable and Movement.
LCS = retentionPeriod >> Disable AD Account
LCS = inactive >> Move AD Account
I think if i convert the transform into a static transform, where the “value” will be returned only when “cloudLifecyclestate=inactive”. That should work i guess right?

You can use below Transform

{
    "type": "static",
    "attributes": {
        "LCS": {
            "type": "identityAttribute",
            "attributes": {
                "name": "cloudLifecycleState"
            }
        },
        "OU":{
            "type": "static",
            "attributes": {
                "value": "OU=Disabled,OU=Accounts,DC=xyz,DC=com"
            }
        },
        "value": "#if($LCS == 'inactive')$OU#{else}#end"
    }
}

This can be achieved easily using SailPoint Service Standard Before Provisioning Rule. I have attached the document for more details
SSI BeforeProvisioning Rule - README.pdf (110.2 KB)

Thank you for sharing this @iamnithesh . We discussed now in our team the following approaches (traditional BP Rule, Provisioning policies, SSI BP Rule) and what would suit the customer best and we decided we will go with the Enable/Disable provisioning policy at the moment. We will first see if all use cases are satisfied with this. If not, we have to move to the SSI BP Rule.

This works, thanks! Test now with both Enable & DIsable policies.

2 Likes

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.