sAMAccountName is not provided. samAccountName will be generated randomly by Active Directory

I created a rule designed to take an account from a source and retrieve the `employeeID` attribute; if the identity attribute (of the “user” type) is set to “TERCEIRO” (Third-Party), the rule should generate a sequential login—for example, if the source `employeeID` is T001234, the rule should generate T001235. However, I am encountering the following error: “sAMAccountName is not provided. samAccountName will be generated randomly by Active Directory.”

I’ve already mapped all the identity attributes and everything is correct, but I don’t know what else it could be.

import br.com.sec4you.idn.Artefact;
import br.com.sec4you.idn.ArtefactBody;
import br.com.sec4you.idn.ArtefactIgnore;
import br.com.sec4you.idn.IgnoreRule;
import org.apache.log4j.Logger;
import sailpoint.object.Identity;
import sailpoint.rule.Account;
import sailpoint.server.IdnRuleUtil;
import sailpoint.tools.GeneralException;

import java.text.Normalizer;
import java.text.ParseException;
import java.util.*;

@IgnoreRule(rule = "notHaveTryCatch")
@Artefact(target = "/RULE/Rule - AttributeGenerator - ThirdUsernameGenerator.xml", type = "IdentityAttribute")
public class ThirdUsernameGeneratorRule {
  @ArtefactIgnore
  Logger log;

  @ArtefactIgnore
  Identity identity;

  @ArtefactIgnore
  IdnRuleUtil idn;

  String LOG_ID = "[TEST] ";
  String validateIds;


  //variável de tantativas máximas
  int maxAttempts = 0;
  //tamanho máximo do login

  // Check Active Directory for uniqueness
  public Boolean isUnique(String value) {

    String[] sourceID = validateIds.split(";");
    List SOURCE_ID_AD=new ArrayList<>();
    for(String id : sourceID){
      SOURCE_ID_AD.add(id);
    }

    String PROMOTED_ATTR_NAME_AD = "promotedSAMAccountName";
    String SEARCH_OP = "Equals"; // Can also use "StartsWith" or "Equals"
    List SEARCH_VALUES = new ArrayList(Arrays.asList(new String[] { value }));



    int numberOfAccountsAD = idn.attrSearchCountAccounts(SOURCE_ID_AD, PROMOTED_ATTR_NAME_AD, SEARCH_OP, SEARCH_VALUES);
    if (numberOfAccountsAD > 0 || value.isEmpty()) {
      log.debug(LOG_ID + "Match Found");
      return false;
    } else {
      log.debug(LOG_ID + "Match NOT Found");
      return true;
    }
  }

  public void fail(String msg) throws GeneralException {
    log.error(LOG_ID + msg);
    throw new GeneralException(LOG_ID + msg);
  }

  //Start Rule
  public String generateLogin(String prefix, int funct) throws GeneralException {

    //incremento de número. EX: funct = 12. Após está linha funct será 13.
    String login = String.valueOf(funct);

    //Caulcula quantos dígitos faltam para completar 6 dígitos
    int[] completeLength = new int[(6 - (login.length()))];

    //incrementa zero a esquerda para deixar o login com 6 dígitos.
    for(int y : completeLength){
      login = "0" + login;
    }
    //junta o prefixo com o login gerado acima
    String newFuncional = prefix + login;
    log.debug("Login generated: "+newFuncional);

    if (isUnique(newFuncional)){
      log.debug(LOG_ID+"AD Create Login | Unique Login generated: "+newFuncional);
      log.debug(LOG_ID +"AD Create Login | Exit from the GenerateUsername Method");
      return newFuncional;
    }else {
      log.warn(LOG_ID + "Login já existe: "+newFuncional);
      fail("Login já existe: "+newFuncional);
      return "Login já existe!";
    }
  }
  //End Rule

  @ArtefactBody
  public String script(Identity identity) throws GeneralException, ParseException {
    try {
      log.info(LOG_ID + "starting Generate sAMAccountName");
      if (identity == null) {
        log.error("Identity is required, but is null");
        fail("Identity is required, but is null");
      }

      validateIds = (String) identity.getAttribute("validSourceIds");

      //Nome da sorce e da conta nativa
      Account account = idn.getAccountByNativeIdentity("Ultimo Login", "contador.terceiro");
      Map attributes = account.getAttributes();

      //coleta o atributo da identidade e torna ele um valor inteiro
      maxAttempts = Integer.parseInt(String.valueOf(identity.getAttribute("maxAttempts")));

      //armazena o employeeID da conta na funcional
      String funcional = (String) attributes.get("employeeID");

      //armazena a funcional sem a letra P e como um valor inteiro
      if (funcional == null || funcional.isEmpty()){
        log.error("O atributo de conta employeeID nao retornou um valor valido");
        fail("O atributo de conta employeeID nao retornou um valor valido");
      }

      int funct = Integer.parseInt(funcional.substring(1));
      //coleta o valor do att tipoUsuario
      String tipoColaborador = (String) identity.getAttribute("tipoUsuario");

      String prefix = "";

      if(tipoColaborador.equalsIgnoreCase("TERCEIRO")){
        prefix = "T";
      }else {
        log.error("O atributo tipoFuncionario contém um valor inesperado. ");
      }

      int[] cont = new int[maxAttempts];

      String newLogin = "";

      for(int x : cont){
        funct = funct+1;
        newLogin = generateLogin(prefix, funct);
        if(isUnique(newLogin)){
          return newLogin;
        }
      }
      fail("ERRO ao gerar o username");
      return "ERRO";

    } catch (Exception e) {
      log.error(LOG_ID + "Exception: " + e.getMessage());
      fail(e.getMessage());
      return "";
    }
  }
}

Hi @guilherme_sec

Since the goal is to generate the sAMAccountName at the time the AD account is created, a better approach would be to use an Account Profile Attribute Generator Rule and assign it directly to the sAMAccountName in the AD Create Account.

Thanks

Hello Guilherme. Shawn has the right direction. I would move this from the IdentityAttribute rule to an Account Profile Attribute Generator (type="AttributeGenerator") and assign it directly to sAMAccountName in the AD Create Account profile. The warning means the Create Account request is reaching the connector without a sAMAccountName, so AD generates one itself.

SailPoint specifically discourages uniqueness checks inside Identity Attribute rules because those calculations can run concurrently and be re-executed. Account generation is the better place for this logic: Identity Attribute Rule.

For the uniqueness check, I would also consider:

idn.isUniqueLDAPValue(
    identity.getId(),
    application.getId(),
    "sAMAccountName",
    value
)

This checks the LDAP target directly instead of relying only on aggregated ISC account data: IdnRuleUtil. One issue in the current code as well: generateLogin() calls fail() on the first duplicate, so the maxAttempts loop stops immediately. I would continue to the next candidate and only fail after all attempts are exhausted.

Hello,

I agree with solutions provided by other collegues! In the past I have committed the same mistake of creating an “Identity Attribute Cloud Rule” instead of “Account Attribute Cloud Rule”.

Kindly note that as you are referring to other identity attributes inside your rule, refer the below screenshots.

The sequence of processing of identity attributes is not in our control therefore, in some instances, your rule will not work. The correct output of your rule will depend upon whether all the subsequent identity attributes used in your rule for manipulations are populated prior to execution or not. If yes, all work fine but if No, then, rule will not work as expected.

Therefore, move this to Create Account policy of your AD source where create an “Account Attribute Generator Cloud Rule”, deployed it in cloud and then, map it to SamAccountName - account attribute.