AttributeGenerator rule not detecting existing AD sAMAccountName with attrSearchCountAccounts

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:

  1. The attribute key being used in attrSearchCountAccounts
  2. AD account attribute indexing/search behavior in ISC
  3. 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!

Hello Mayur. attrSearchCountAccounts does not search native account attributes directly. It searches promoted account attributes configured through the Search Attributes API.

So even though the Accounts API shows attributes.sAMAccountName, the method will not search it under "sAMAccountName" unless a corresponding search attribute configuration exists. You can check the configuration with:

GET /v2026/accounts/search-attribute-config
X-SailPoint-Experimental: true

Look for a configuration whose applicationAttributes maps your AD source IDs to sAMAccountName. The rule must use that configuration’s name, such as promotedSamAccountName:

idn.attrSearchCountAccounts(
    adSources,
    "promotedSamAccountName",
    "Equals",
    searchValues
);

After creating or updating the configuration, run a non-optimized aggregation on both sources to seed the existing accounts.

For AD, a simpler option requiring less configuration is isUniqueLDAPValue(), which queries the LDAP connector directly:

public boolean isUnique(String samAccountName) throws GeneralException {
    String identityId = identity.getId();
    String applicationId = application.getId();
    return idn.isUniqueLDAPValue(identityId, applicationId, "sAMAccountName", samAccountName);
}

This checks the AD source currently being provisioned (application.getId() resolves to that single source). If the value must be unique across both AD sources, call the method once for each source ID and accept the candidate only when both calls return true.

Checking cn through attrSearchCountAccounts would have the same promoted-attribute requirement. Since your CN uses the same generated value, fixing the sAMAccountName uniqueness check should prevent this collision.

The immediate issue is most likely the search attribute configuration rather than anything in your candidate-generation logic.

Hi @mayurSuresh Can I ask - what are you using as Account ID and Account Name for your AD source?