There are many reasons for a company to need to move Active Directory (AD) accounts to different organizational units (OUs).
On Termination: Move to a "Disabled Users" OU
Many clients have a designated OU to hold terminated users. One reason to do this is to set specific AD policies on that OU. For example, accounts may be in held this OU for 90 days before they are automatically deleted. Alternatively, some clients who have integrations with AD, such as Azure, may sync users only in a particular branch and exclude the "Disabled Users" OU. This approach allows flexibility to continue to manage the user accounts, but also remove them from the scope of other applications.
On Mover: Move to the Designated OU
In the past, it was a good policy to have separate OUs for logical distribution of users. For example, some clients have specific department OUs to logically group users in a single department together. Other clients who have many subsidiaries might have company-specific OUs. By separating users, it's easy to give granular AD policies to users based on an OU. More recently, Microsoft has other tools to accomplish this and it is now best practice to use a flat structure with a single OU.
Because many older customers have not migrated their directories to match Microsoft’s guidance, using multiple OUs is still a common and valid use case.
Technical Complexities
The OU that an account resides in makes up the latter part of a user's distinguishedName. The first part is usually the CN, or common name. For example, if the user's CN is "John Smith" and the parent OU is "Services" in the "Users" OU of acme.com, then the distinguishedName will be
CN=John Smith,OU=Services,OU=Users,DC=acme,DC=com
A user's distinguishedName MUST be unique in an AD domain. That is to say if there is another John Smith in the Services OU, it will need a separate CN. A typical approach is to use serialization and add a number, e.g.
CN=John Smith 2,OU=Services,OU=Users,DC=acme,DC=com
Any time an account needs to be moved, it will require a uniqueness check. Otherwise, there may already exist a duplicate user in the other OU. There can be a "John Smith" in the "Services" OU and one in the "Support" OU. If you move the one in the "Support" OU to "Services," then there will be a clash on the CN and the moved account will require a different name, such as CN=John Smith 2.
In IdentityNow, the AD source uses distinguishedName as the Account Id, a unique identifier, so moving users in AD appears to the system like deleting and adding separate accounts. In addition, IdentityNow cannot simply update the distinguishedName since it is an AD identifier.
The connector has the ability to move AD accounts using two key attributes.
AC_NewParent - This is the new parent OU where the account will reside. Using the same example from above, "OU=Services,OU=Users,DC=acme,DC=com"
AC_NewName - This is the new CN value of the distinguishedName that the account will have. It is rarely necessary to set this value, as the CN normally stays the same during OU moves. However, if the new OU location would result in a distinguishedName value which is identical to another pre-existing Active Directory object, then setting AC_NewName can help resolve the conflict. For example, "CN=John Smith 2"
These attributes need to be added to the provisioning plan during a Modify/Enable/Disable operation in order to move the AD account.
When the AD account is moved to a different OU, it is unlinked from the identity. Because IdentityNow utilizes the distinguished name to execute provisioning requests and the distinguished name has changed, a delta aggregation will not suffice to read and update the account. It is necessary to fully aggregate Active Directory to re-link the account to the identity. Any modifications (Attribute Sync, Access Request) executed after the account move will fail until a full aggregation is run and completed. Until the connector seamlessly updates the distinguished name in IdentityNow, ensure that your automated processes account for the delay.
If any attribute synchronizations need to occur in addition to moving the account, utilize the BeforeProvisioning rule to set the attributes at the same time as the account move. That way additional attribute synchronizations will not be necessary on the account and no provisioning attempts will fail.
The BeforeProvisioning rule sample below is one way in which attribute sync requests can be included within the same plan as the OU move operation:
import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AccountRequest.Operation;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
import sailpoint.object.Identity;
import sailpoint.tools.Util;
import sailpoint.object.Link;
import sailpoint.object.Application;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import sailpoint.rule.Account;
import sailpoint.rule.ManagedAttributeDetails;
import sailpoint.rule.RuleObjectFactory;
if (plan != null) {
Identity identity = plan.getIdentity();
String destinationOU = "OU=Disabled,OU=Users,OU=Corporate,DC=company,DC=com";
List accountRequests = plan.getAccountRequests();
if (accountRequests != null) {
for (AccountRequest accountRequest : accountRequests) {
String accountNativeIdentity = accountRequest.getNativeIdentity();
if(accountRequest.getOperation().equals(ProvisioningPlan.AccountRequest.Operation.Disable)) {
log.debug( "Operation is a Disable. We want to move OUs." );
accountRequest.add(new AttributeRequest("AC_NewParent", ProvisioningPlan.Operation.Set, destinationOU));
Account link = idn.getAccountByNativeIdentity("Active Directory [source]", accountNativeIdentity);
Map acctAttrs = link.getAttributes();
//Adding an attribute request for extensionAttribute5, which is being kept in sync with identity attribute departmentCode, if the two values aren't equal
if(!acctAttrs.get("extensionAttribute5").toString().equals(identity.getAttribute("departmentCode").toString())) {
accountRequest.add(new AttributeRequest("extensionAttribute5", ProvisioningPlan.Operation.Set, identity.getAttribute("departmentCode").toString()));
}
}
}
}
}
It is important to note that, as of August 2023, there has been support at the Active Directory connector level to automatically return the new account's location back to IdentityNow during an account move provided that the AC_NewParent attribute was used to effect the move. In other words, for the best supportability of OU moves within IdentityNow, it is strongly recommended that customers use a BeforeProvisioning rule to add the AC_NewParent attribute to the provisioning plan; natively moving the account in Active Directory itself will result in the de-linking and re-linking of the account to the identity as described earlier in this section. This can result in unintended behavior such as duplicate account creation or errors related to not being able to find the target account of an access request.
SailPoint recommends the following when dealing with Active Directory account moves through IdentityNow:
When dealing with unique values in IdentityNow, the best place is via an attribute generator rule. These are found in the AccountCreate Profile. Since this is not available during updates (modify/enable/disable), it's best to use a unique value for the CN attribute. That way account moves are less likely to clash. Possible unique values include:
sAMAccountName—This is already guaranteed to be unique in the domain.
HR Unique Identifier like employeeId—This should be unique per person. However, this could be difficult if users have multiple accounts. Privileged accounts could use a format such as employeeId-p.
Other unique values could be generated using a string randomization process, UUID generation, etc.
It's best to only move accounts when necessary. The simplest way to do that is to trigger off an AttributeSync event when one of the attributes used in the OU calculation changes.
The best way to calculate where a user should be is in an identity attribute rule. That way you can manage the parent OU via transforms. One suggestion is to use two attributes:
activeParentOU—This is the OU for when the user is active. Use this in the Create profile and the Before Provisioning Rule when handling active user moves and rehires.
disabledParentOU—This is the OU for terminated users. Use this in the Before Provisioning Rule when handling terminations.
IdentityNow allows two avenues for adding AC_NewName and AC_NewParent to the provisioning plan: AccountProfiles (modify, enable, or disable) and the Before Provisioning Rule. AccountProfiles can be updated through the APIs. Before Provisioning Rules can only be updated through SailPoint Professional Services.
It is not recommended to go through the account profiles because attributes set there go through a verification stage after provisioning. Since AC_NewParent and AC_NewName are not read via the connector, they can never be verified. This results in “stranded” account activity entries within the IdentityNow tenant.
Account moves are handled by specific use cases. Make sure to think through other use cases that could trigger provisioning to ensure the account move occurs appropriately. In particular, with the recent introduction of event-based attribute sync, take into consideration that identity refreshes can trigger due to upstream changes in incoming account data, and these refreshes run independently of lifecycle state transitions. This means that, if an OU move needs to occur due to both attribute sync and lifecycle state transitions, all modifications should be encapsulated within a single BeforeProvisioning rule and no alternative operations such as enable and disable should be configured for lifecycle state provisioning.
Handling OU moves is easiest to handle using an identity attribute. That way you can reference the identity attribute rather than calculate in the rule. This allows updates to be handled by transform updates, which can be handled solely by administrators rather than rule updates which rely on SailPoint assistance.
The transform below is a simple example of setting the OU based on the user's department. This can be tied to the identity attribute "Active Parent OU" where "activeParentOu" is the technical name.
{
"attributes": {
"input": {
"attributes": {
"attributeName": "Department",
"sourceName": "Employees"
},
"type": "accountAttribute"
},
"table": {
"Services": "OU=Services,OU=Users,DC=example,DC=com",
"Human Resources": "OU=HR,OU=Users,DC=example,DC=com",
"Information Technology": "OU=IT,OU=Users,DC=example,DC=com",
"Engineering": "OU=Engineering,OU=Users,DC=example,DC=com",
"default": "OU=Default,OU=Users,DC=example,DC=com"
}
},
"id": "Calculate AD OU",
"type": "lookup"
}
The following example represents how to use these suggestions in a single Before Provisioning rule to handle account moves. This rule handles:
Rehire use case
Move use case, in case of a department change
Termination use case
Note: This Rule does NOT account for uniqueness checks when moving the account. It assumes the CN is unique across the domain as recommended above.
import sailpoint.object.Identity;
import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
List accountRequests = plan.getAccountRequests();
if (accountRequests != null) {
for (AccountRequest accountRequest: accountRequests) {
AccountRequest.Operation op = accountRequest.getOperation();
if(op == null) continue;
String nativeIdentity = accountRequest.getNativeIdentity();
Identity identity = plan.getIdentity();
String currentLCS = null;
String activeOU = null;
String disabledOU = null;
if(identity != null){
currentLCS = identity.getAttribute("cloudLifecycleState");
activeOU = identity.getAttribute("activeParentOu");
disabledOU = identity.getAttribute("disabledParentOu");
}
boolean departmentChanged = false;
if(op.equals(AccountRequest.Operation.Modify)){
List dAttrReqs = accountRequest.getAttributeRequests("department");
if(dAttrReqs != null && !dAttrReqs.isEmpty()){
departmentChanged = true;
}
}
String newOU = null;
/*
Get a new OU only if we are in
Rehire Use Case (Enable, user LCS is active and user was in the disabled OU)
Mover Use Case (Modify and user LCS is active)
Termination Use Case (Disable and user LCS is inactive)
*/
if ("active".equals(currentLCS) && op.equals(AccountRequest.Operation.Enable)
&& disabledOU != null && activeOU != null && nativeIdentity.endsWith(disabledOU)){
//Rehire Use Case
newOU = activeOU;
}
else if ("active".equals(currentLCS) && departmentChanged && activeOU != null){
//Mover Use Case
newOU = activeOU;
}
else if("inactive".equals(currentLCS) && op.equals(AccountRequest.Operation.Disable)){
//Termination Use Case
newOU = disabledOU;
}
if(newOU != null && !nativeIdentity.endsWith(newOU)){
//Account not currently in the proper OU. Put them there
accountRequest.add(new AttributeRequest("AC_NewParent", ProvisioningPlan.Operation.Set, newOU));
}
}
}
Can we use the attribute generator transform on an identity attribute to synchronize distinguishedName?
It is not recommended to use attribute generators in identity attributes. First, it would be a big performance hit. Second, due to the multi-threaded nature of IdentityNow's architecture, the uniqueness check at the identity attribute level is not guaranteed to return a unique value. Additionally, attribute generators use a source context that identity attributes do not have.
Is there a reason for why we can't change the Account ID to another identifier, such as 'objectguid'? I believe this would resolve a lot of the complexities with handling account moves and will allow for delta aggregations.
@bostelmannper the July 2021 release notes:
The default native identifier for Active Directory applications has changed back to
distinguishedName. All new Active Directory applications created will have
distinguishedName as the native identifier. The use of objectGUID as the native
identifier introduced in previous patches introduced some usability and functional
limitations that will be corrected in a future release. The current recommended best
practice is to continue to use distinguishedName as the native identifier for Active
Directory applications.
Hi,
We have a strange situation where we need to enable account and move it to a different location. That we can handle based on the above in before provisioning rule but its not enabling the account as a password is needed to be set on the account before enabling.
Is there a way to set a password on the account and then enable it sequentially i.e. modify the account to set the password and then call the enable operation on the account?
Many thanks for any prompt ideas.
Regards,
Priyank
"The default native identifier for Active Directory applications has changed back to distinguishedName. All new Active Directory applications created will have distinguishedName as the native identifier. The use of objectGUID as the native identifier introduced in previous patches introduced some usability and functional limitations that will be corrected in a future release."
This is ridiculous. Novell/NetIQ IDM used GUIDs for (their version of) nativeIdentity >15yrs ago. Using DN, then requiring an agg (and not even a targeted/single-user agg, it has to be a full aggregation) is the most short-sighted thing I've ever seen for a Gartner "Magic Quadrant" product. I was so excited to hear at Navigate '22 that this was being addressed... now I'm heartbroken.
Maybe I can try modifying the 'move' rule to modify the nativeIdentity value on the app link... but shouldn't have to.
Would this still be the best practice after the 8-14-23 update??
Connectivity (CB-DIR 6.1 ) - Active Directory | CONETN-4261 | The Active Directory connector now instantly returns the Resource Object to IdentityNow on any OU changes done by the AC_New Parent, which can be further utilized to any rule to work with the updated Resource Object data values. |
@mdeavers I second this. Currently refactoring rehires for a customer and we need some clarity on the current best practice
Instead of using a Before Provisioning Rule, can we use a Before Modify Rule on the Connector? This would be so much easier if we handled this in powershell to modify the AccountRequest to add AC_NewParent and could handle uniqueness checks as well!
@hari_patel @ross_shwarts @BeckyJorgensen
there used to be a section in the FAQ about moving accounts outside of IDN.
Can you explain what the reason for removing that is and what the expectation is now?
Hi @tomkelly, that specific FAQ which you're referring to was removed at the specific request of a couple of users who found it "confusing" and "contradictory." They weren't able to provide clarity on what changes would clear up the confusion, and instead asked for it to simply be removed.
Rest assured, though, that the IdentityNow behavior here has not changed. Active Directory admins are still free to move accounts natively within the directory - IdentityNow does not prohibit native changes in target systems, and it would still drop the link to the account from the previous location and add a new link to the account at its new location. However, as explained in the article, doing these changes natively in Active Directory instead of through IdentityNow itself (via the AC_NewParent attribute), will almost definitely lead to unintended provisioning behavior: duplicate account creations, repetitive provisioning attempts, etc. One way to avoid that behavior is to ensure a full aggregation has been run on the Active Directory source before any provisioning activity is directed towards the moved account, but even this is not guaranteed to prevent issues. The strong recommendation from SailPoint today is to leverage AC_NewParent via a BeforeProvisioning rule to effect OU moves in Active Directory.
HI @hari_patel that is a interesting statement you've been told.
I think one of these users was me as I've got a ticket about this functionally not working as laid out in the old FAQ. SailPoint support have specifically told me that its not possible to move accounts outside of IDN without using AC_NewParent.