Hey everyone,
I’m working on an ISC / IdentityNow Active Directory account creation rule and wanted to get another set of eyes on it.
The goal is to generate a unique AD `sAMAccountName` during account creation. That same generated value is also being used as the CN in the `distinguishedName`.
The naming pattern is based on last name + first name characters, max 8 characters.
Example:
First Name: Owen
Last Name: Sullivan
Expected first generated value:
sullivao
If `sullivao` already exists, I expect the rule to keep trying the next patterns, for example:
sullivo
sulliow
sullowe
sulowen
suowen
sowen
The issue I’m seeing:
The first account was created successfully with:
sAMAccountName: sullivao
cn: sullivao
distinguishedName: CN=sullivao,OU=Employees,OU=,DC=,DC=com
After aggregation, I confirmed through the Accounts API that the account exists in ISC and has:
attributes.sAMAccountName = sullivao
attributes.cn = sullivao
attributes.distinguishedName = CN=sullivao,OU=Employees,OU=,DC=,DC=com
But when I create another identity with the same first and last name, the rule still returns:
sAMAccountName: sullivao
nativeIdentity: CN=sullivao,OU=Employees,OU=,DC=,DC=com
Then AD rejects the create with `ENTRY_EXISTS` because the CN already exists.
I confirmed:
- The account is aggregated and visible in ISC before retesting.
- The source ID in the rule matches the source ID shown in the Accounts API / Account Activity.
- The Accounts API shows `attributes.sAMAccountName` populated.
- The Create Account activity still shows the generated `sAMAccountName` value as `sullivao`.
```java
import sailpoint.tools.GeneralException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Checks whether the generated sAMAccountName already exists
* on any of the AD sources listed below.
*/
public boolean isUnique(String samAccountName) throws GeneralException {
// AD source IDs redacted
// These are the AD sources where the rule should check for existing accounts.
List adSources = new ArrayList(Arrays.asList(new String[] {
"<REDACTED_AD_SOURCE_ID_1>",
"<REDACTED_AD_SOURCE_ID_2>"
}));
String equalsOp = "Equals";
// Value we are checking for uniqueness.
List searchValues = new ArrayList(Arrays.asList(new String[] {
samAccountName
}));
// This is the part I am questioning.
// The Accounts API shows attributes.sAMAccountName populated,
// but this does not seem to detect the existing account.
if (idn.attrSearchCountAccounts(adSources, "sAMAccountName", equalsOp, searchValues) > 0) {
return false;
}
return true;
}
/**
* Main generation method.
* Takes first name and last name, normalizes them to lowercase,
* then tries several naming patterns until a unique value is found.
*/
public String generateSAMAccountName(String firstName, String lastName) throws GeneralException {
if (firstName == null || lastName == null) {
throw new GeneralException("First name and last name are required to generate sAMAccountName.");
}
firstName = firstName.toLowerCase();
lastName = lastName.toLowerCase();
// Strategy 1:
// Try an 8-character ladder pattern using last name + first name.
//
// Example:
// First Name: Owen
// Last Name: Sullivan
//
// Expected attempts:
// sullivao
// sullivo
// sulliow
// sullowe
// sulowen
// suowen
// sowen
int startChars;
if (lastName.length() >= 8) {
startChars = 8;
} else {
startChars = lastName.length();
}
String result = tryLadderStrategy(lastName, firstName, startChars);
if (result != null) {
return result;
}
// Strategy 2:
// Try last name + full first name, then reduce the last name
// until the result fits the length rules and is unique.
result = tryLastNameReduction(lastName, firstName, lastName.length());
if (result != null) {
return result;
}
// Strategy 3:
// Keep full last name and reduce first name.
result = tryFullLastNameWithReducedFirstName(lastName, firstName, firstName.length());
if (result != null) {
return result;
}
throw new GeneralException("Unable to generate a unique sAMAccountName for identity: " + identity.getName());
}
/**
* Strategy 1:
* Recursive ladder strategy.
*
* It starts with up to 8 characters from the last name.
* Then it reduces the last name portion and adds more first name characters.
*
* Example:
* First Name: Owen
* Last Name: Sullivan
*
* Attempts:
* sullivao
* sullivo
* sulliow
* sullowe
* sulowen
* suowen
* sowen
*/
private String tryLadderStrategy(String lastName, String firstName, int lastNameChars) throws GeneralException {
// Stop when there are no last name characters left to try.
if (lastNameChars < 1) {
return null;
}
int neededFirst = 8 - lastNameChars;
// Only build a candidate if the first name has enough characters.
if (neededFirst >= 1 && firstName.length() >= neededFirst) {
String candidate = lastName.substring(0, lastNameChars) + firstName.substring(0, neededFirst);
// Candidate must be between 3 and 8 characters and must be unique.
if (candidate.length() >= 3 && candidate.length() <= 8 && isUnique(candidate)) {
return candidate;
}
}
// Try again with one less character from the last name.
return tryLadderStrategy(lastName, firstName, lastNameChars - 1);
}
/**
* Strategy 2:
* Try reduced last name + full first name.
*
* Example:
* Last Name: Sullivan
* First Name: Owen
*
* It would try combinations like:
* sullivanowen, sullivaowen, sullivowen, etc.,
* but only returns candidates that fit the 3-8 character rule.
*/
private String tryLastNameReduction(String lastName, String firstName, int lastNameChars) throws GeneralException {
// Stop when there are no last name characters left to try.
if (lastNameChars < 1) {
return null;
}
String candidate = lastName.substring(0, lastNameChars) + firstName;
// Candidate must be between 3 and 8 characters and must be unique.
if (candidate.length() >= 3 && candidate.length() <= 8 && isUnique(candidate)) {
return candidate;
}
// Try again with one less character from the last name.
return tryLastNameReduction(lastName, firstName, lastNameChars - 1);
}
/**
* Strategy 3:
* Keep the full last name and reduce the first name.
*
* Example:
* Last Name: Z
* First Name: Prasad
*
* Attempts:
* zprasad
* zprasa
* zpras
* zpra
* zpr
*/
private String tryFullLastNameWithReducedFirstName(String lastName, String firstName, int firstNameChars) throws GeneralException {
// Do not try zero characters from first name.
if (firstNameChars < 1) {
return null;
}
// Safety check so we do not take more characters than the first name has.
if (firstNameChars > firstName.length()) {
return tryFullLastNameWithReducedFirstName(lastName, firstName, firstName.length());
}
// Stop if the final result would be less than 3 characters.
int minFirstNameChars = 3 - lastName.length();
if (firstNameChars < minFirstNameChars) {
return null;
}
String candidate = lastName + firstName.substring(0, firstNameChars);
// Candidate must be between 3 and 8 characters and must be unique.
if (candidate.length() >= 3 && candidate.length() <= 8 && isUnique(candidate)) {
return candidate;
}
// Try again with one less character from the first name.
return tryFullLastNameWithReducedFirstName(lastName, firstName, firstNameChars - 1);
}
// Pull first name and last name from the identity object.
// Spaces are removed before generating the sAMAccountName.
String fName = null;
String lName = null;
if (identity.getFirstname() != null) {
fName = identity.getFirstname().toLowerCase().replaceAll("\\s", "");
}
if (identity.getLastname() != null) {
lName = identity.getLastname().toLowerCase().replaceAll("\\s", "");
}
return generateSAMAccountName(fName, lName);
My main question:
Should this line be able to find an aggregated AD account where the Accounts API shows attributes.sAMAccountName is populated?
idn.attrSearchCountAccounts(adSources, "sAMAccountName", "Equals", searchValues)
Or does attrSearchCountAccounts need a different key for Active Directory accounts, such as:
- samAccountName
- cn
- distinguishedName
or another internal/indexed attribute name?
I’m trying to figure out if the issue is:
- The attribute key being used in
attrSearchCountAccounts - AD account attribute indexing/search behavior in ISC
- Something wrong with my rule logic
As a possible workaround, I’m considering checking both sAMAccountName and cn, since cn is populated and the AD failure is on the duplicate CN/nativeIdentity:
if (idn.attrSearchCountAccounts(adSources, “sAMAccountName”, equalsOp, searchValues) > 0 ||
idn.attrSearchCountAccounts(adSources, “cn”, equalsOp, searchValues) > 0) {
return false;
}
Has anyone seen attrSearchCountAccounts not match sAMAccountName even though the Accounts API clearly shows attributes.sAMAccountName populated?
I want to be absolutely sure about the attrSearchCountAccounts before i go ahead and ask for more code updates to be pushed down stream.
I really appreciate any help!