I have a before provisioning rule deployed in my tenant by Sailpoint services. I have added the rule to the source and i can confirm I can see the rule via API.
The rule is supposed to move users in correct OU based on lifecycle states and handled the collision check as well. If user is active they should be placed under Primary OU and if user is terminated, they should be moved to Disabled Inactive OU.
The DistinguishedName value should be updated with proper OU, that’s the goal.
Upon termination, the user’s DN value is still remaining in Primary OU. Ideally the rule should be handling the OU move, but that’s not happening. Similarly, when we rehire the same user, the DN value is suppose to be updated to Primary OU again, but that is not happening.
Any ideas or suggestions what can be the issue here ? Seems like my before provisioning rule is not getting triggered itself.
If the DN is not changing at all during terminate/rehire, it does sound like the before provisioning rule isn’t being executed, rather than a logic issue.
In SailPoint Identity Security Cloud, a few things to verify:
• Is provisioning actually triggered?
Before Provisioning rules only run during provisioning events. If termination/rehire is not generating a modify request to AD, the rule won’t fire.
• Attribute change detection
If ISC doesn’t detect a change in mapped attributes (like DN), it may skip provisioning altogether. Ensure your lifecycle state change is actually causing a provisioning plan.
• Rule attachment point
Double-check the rule is attached correctly to the source under Before Provisioning Rule (not just deployed/visible via API).
Hi @trettkowski - The below is my before provisioning rule:
<?xml version='1.0' encoding='UTF-8'?>
This rule is executed before provisioning to Active Directory and is used to handle OU moves
<![CDATA[
import sailpoint.object.Identity;
import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
import sailpoint.tools.GeneralException;
public String generateUniqueCommonName ( String firstName, String lastName, String middleInitial, String activeDirectoryContainer, int iteration) throws GeneralException {
// Initialize Variables
int maxIteration = 100;
String commonName = null;
// Check Maximum Iterations
if (iteration > maxIteration) {
// ...maximum iterations reached.
throw new GeneralException("Exceeded maximum iterations in calculating commonName.");
}
// Generate Common Name
switch (iteration) {
case 0:
commonName = "CN=" + lastName + "\\, " + firstName + (middleInitial != null && middleInitial.length() > 0 ? " " + middleInitial : "");
break;
default:
commonName = "CN=" + lastName + "\\, " + firstName + (middleInitial != null && middleInitial.length() > 0 ? " " + middleInitial : "") + (iteration + 1);
break;
}
// Check Uniqueness
if (idn.isUniqueLDAPValue(identity.getName(), application.getName() , "distinguishedName", (commonName + "," + activeDirectoryContainer))) {
return commonName;
} else {
return generateUniqueCommonName(firstName, lastName, middleInitial, activeDirectoryContainer, (iteration + 1));
}
}
// Start
List accountRequests = plan.getAccountRequests();
if (accountRequests != null) {
// Process Account Requests...
for (AccountRequest accountRequest: accountRequests) {
AccountRequest.Operation op = accountRequest.getOperation();
Identity identity = plan.getIdentity();
String nativeIdentity = accountRequest.getNativeIdentity();
String activeDirectoryContainer = identity.getAttribute("activeDirectoryContainer");
// Check for OU Change
if (activeDirectoryContainer != null && !nativeIdentity.endsWith(activeDirectoryContainer)) {
// Initialize Variables
String firstName = identity.getFirstname();
String lastName = identity.getLastname();
String middleInitial = Identity.getAttribute("middleInitial");
// Check Dependencies
if (firstName == null | lastName == null) {
throw new GeneralException("Missing required dependencies to calculate Distinguished Name.");
}
// Generate Common Name
String commonName = generateUniqueCommonName(firstName, lastName, middleInitial, activeDirectoryContainer, 0);
// Check for Common Name Change
if(commonName != null && !nativeIdentity.startsWith(commonName)) {
// Add New Name Attribute Request
accountRequest.add(new AttributeRequest("AC_NewName", ProvisioningPlan.Operation.Set, commonName));
}
// Add New Parent Attribute Request
accountRequest.add(new AttributeRequest("AC_NewParent", ProvisioningPlan.Operation.Set, activeDirectoryContainer));
}
}
}
• Is provisioning actually triggered?
Before Provisioning rules only run during provisioning events. If termination/rehire is not generating a modify request to AD, the rule won’t fire.
Answer → I do see a modify event getting generated: Here is the screenshot
• Attribute change detection
If ISC doesn’t detect a change in mapped attributes (like DN), it may skip provisioning altogether. Ensure your lifecycle state change is actually causing a provisioning plan.
I see one thing that could definitely be throwing off the rule, but I would still add connector logging under each IF/SWTICH check to be sure you are getting the proper data at each step.
This line has a capital “I” instead of a lowercase I for Identity. This is calling the Identity class instead of the identity object that you are wanting to grab.
Thats a good catch. Also I wanted to check if we can use native functions like Disable and Enable provisioning policy from VS code ? Will that handle the OU move by ISC ? I have tried the Disable provisioning policy and it was moving the users from Primary OU to Disabled OU upon termination but the Enable/Update provisioning policy was not moving the DN value back to Primary OU upon rehire process.
You can use the standards services provisioning rule, which can handle things like OU moves and such, but since you are only allowed one cloud rule per source, that won’t work for your use case unless you remove the unique username generator requirement.
Provisioning policies are probably the best way of going about this unless you have a lot of complex logic for calculating the unique DN value. Since your before provisioning rule is pretty small right now, I would pivot to using the enable/disable policies instead and if you need complex logic for calculating a unique DN/CN value, let that live on the identity attribute and use a rule to calculate it there instead.
Here’s more info/examples on using policies for moving users to OU’s:
Hi,
Is there any particular reason that you are going down the route of a BR rule, rather than the DISABLE/ENABLE profiles in the provisioning plan and utilising AC_NewParent?
@pateldivyang0319 - Yes Divyang, you can use native functions Enable/Disable Provisioning Policies for the OU move and that is the recommanded approach as well.
The best practice is to move away from complex Before Provisioning Rules for standard OU moves and instead utilize Provisioning Policies with the AC_NewParent attribute. To understand and get a clear picture refer the below link.
Hi @phil_awlings - I tried using Disable and Enable provisioning policy for my requirements. The problem i saw that when user is terminated, DN value is correctly getting recalculated and moved to Disabled OU. But if i rehire same user, the DN value is not moved back to Primary Active OU.
Is the rehire DN non-standard? ie. lots of different OU branches that it could have been so you can’t use a static value like: OU=Users, DC=abc, DC=xyz
If they are static then this is all that you need:
Just go into VSC/sources/‘sourceName’/Provisioning Policies
right click, hit the ‘+’ and choose Enable or Disable
They will move the user if the LCS happens automatically, if you manually move them using the UI, and if you move via Postman API.
The only caveat is that if they are already disabled but in the wrong OU, then it won’t work. The enable/disable function gets stripped from the provisioning plan it the target identity is already in that state. You can witness that in the UI, on an identity, the Disable function for a source is greyed out if they are already disabled
We have a scenario where AD sources that are Entra linked need to be disabled on termination (LCS => inactive) but not moved until LCS => inactive14plus. That requires slighty more interesting logic