sAMAccount Attribute Generator rule

Hello, we trying to get some assistance on an attribute generator rule. It seems to pass both the validation and RDK. Our RDK test takes Joan as firstname, Arnold as lastname. This evaluates properly to joarnold as jarnold is taken. Anything clearly wrong with this rule? We always get object exists in ISC when a duplicate user is provisioned.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<Rule name="PRHCCreateUsername" type="AttributeGenerator">
    <Description>Generate a unique username for Active Directory using progressive firstname prefix + lastname. Uniqueness checked via getAccountAttribute against sAMAccountName.</Description>
    <Source><![CDATA[
import java.text.Normalizer;
import sailpoint.tools.GeneralException;

// --- config ---
int MAX_USERNAME_LENGTH = 15;
boolean ADD_DOT = false; // set true if you want "prefix.lastname"

// --- helpers (JDK-only) ---
String trimToNull(String s) {
  if (s == null) return null;
  s = s.trim();
  return s.isEmpty() ? null : s;
}

String clean(String s) {
  s = Normalizer.normalize(s, Normalizer.Form.NFD)
                .replaceAll("\\p{InCombiningDiacriticalMarks}+", "")
                .replaceAll("[^a-zA-Z0-9]", "");
  return s;
}

String buildAndTrim(String firstPart, String lastPart, String suffix) {
  String sep = ADD_DOT ? "." : "";
  String base = (firstPart + (ADD_DOT ? sep : "") + lastPart).toLowerCase();
  // If we have a suffix, trim the BASE to leave room for it
  if (suffix != null && suffix.length() > 0) {
    int maxBaseLen = Math.max(0, MAX_USERNAME_LENGTH - suffix.length());
    if (base.length() > maxBaseLen) base = base.substring(0, maxBaseLen);
    return (base + suffix).toLowerCase();
  }
  // No suffix case
  if (base.length() > MAX_USERNAME_LENGTH) base = base.substring(0, MAX_USERNAME_LENGTH);
  return base.toLowerCase();
}

boolean isUnique(String username) throws GeneralException {
  // Check for existing account with this sAMAccountName
  Object existing = idn.getAccountAttribute(application.getName(), "sAMAccountName", username);
  return (existing == null);
}

// --- main logic ---
String generateUsername(String firstName, String lastName) throws GeneralException {
  firstName = trimToNull(firstName);
  lastName  = trimToNull(lastName);

  if (firstName != null) firstName = clean(firstName);
  if (lastName  != null) lastName  = clean(lastName);

  if (firstName == null || lastName == null) {
    try { if (log != null) log.debug("AD Create User Name | Missing first/last"); } catch (Throwable t) {}
    return null;
  }

  // Progressive prefixes: j + lastname, jo + lastname, ..., firstname + lastname
  for (int i = 1; i <= firstName.length(); i++) {
    String prefix = firstName.substring(0, i);
    String candidate = buildAndTrim(prefix, lastName, "");
    if (isUnique(candidate)) {
      try { if (log != null) log.debug("AD Create User Name | Unique: " + candidate); } catch (Throwable t) {}
      return candidate;
    }
  }

  // Fallback: append numbers to full firstname+lastname (…1, …2, …3, …)
  for (int n = 1; n <= 9999; n++) {
    String candidate = buildAndTrim(firstName, lastName, String.valueOf(n));
    if (isUnique(candidate)) {
      try { if (log != null) log.debug("AD Create User Name | Unique with suffix: " + candidate); } catch (Throwable t) {}
      return candidate;
    }
  }

  return null;
}

// Kickoff
return generateUsername(identity.getFirstname(), identity.getLastname());
]]></Source>
</Rule>

According to the JavaDoc, the second parameter of the getAccountAttribute function should be the native identity, while the third parameter should be the attribute name whose value you want to retrieve.

Currently, your code looks like this:

idn.getAccountAttribute(application.getName(), "sAMAccountName", username);

It should be updated to:

idn.getAccountAttribute(application.getName(), username, "sAMAccountName");

Also, did you modify the AD source’s identity attribute to use sAMAccountName? By default, it’s set to distinguishedName, and changing it is generally not recommended.

Thank you, currently our sAMAccountName is tied to the Account Name and not the Account ID. Will this still work? Apologies we’re new to SailPoint. Our main issue seems to be checking for uniqueness against the native identity. We’re unsure how to handle the OU portion of the DN. We have a lookup transform built that we wanted to use in a patter to create the DN with the sAMAccountName.

image

You can use the code function instead of getAccountAttribute since sAMAccountName is set up as the account name for the AD source.

idn.accountExistsByDisplayName(application.getName(), username)

Thanks again, just to confirm 100% the idn.accountExistsByDisplayName is actually referencing the Account name attribute in ISC?

This was our original code and it was not evaluating. Where I think we went wrong with it was not having a duplicate Display Name aggregated in ISC at the time of creation. Thank you for your time I will try this out

This worked great. Thank you. Our issue was not having the duplicate identity synced to ISC and just having it present in AD

1 Like

Ahh…glad that its resolved now.