Search code examples
active-directoryldapspring-data

Spring data with LDAP (Active Directory) returns WILL NOT PERFORM in any writing operation


I'm currently trying to implement an simple CRUD integrated with Active Directory via LDAP, using Spring Data, for managing my internal users.

The problem is, while the reading works as expected, any writing on AD (creating or editing a user, for example) results in a generic error message, shown below:

[LDAP: error code 53 - 0000209A: SvcErr: DSID-031A107A, problem 5003 (WILL_NOT_PERFORM), data 0\n\u0000]; remaining name 'DC=company, DC=com'

The ldap connection is being made using LDAPS with an admin user. I can even work with the same credentials without any issues in a simple nodejs test application. So I'm probably making some mistake with Spring Data.

The relevant source code is attached below.

Entity class:

// Person.java (Entity model)
@Data
@Entry(
   base = "ou=Employees,dc=company,dc=com",
   objectClasses = {"person", "top"}
)
public class Person {

  @Id
  private Name dn;

  @Attribute(name = "cn")
  private String commonName;

  @Attribute(name = "sAMAccountName")
  private String accountName;

  @Attribute(name = "userPrincipalName")
  private String username;

  @Attribute(name = "mail")
  private String mail;

  @Attribute(name = "userPassword")
  private String password;

  @Attribute(name = "description")
  private String desc;

  @Attribute(name = "memberOf")
  private List<String> groups;

  @Attribute(name = "company")
  private String company;

  @Attribute(name = "objectClass")
  private List<String> objectClasses;

  @Attribute(name = "objectCategory")
  private String objectCategory;
}

Repository class:

// PersonRepository.java
@Repository
public interface PersonRepository extends LdapRepository<Person> {
    Person findByMailIgnoreCase(String mail);
}

Service class:

@Service
public class UserService {

   @Autowired
   private PersonRepository personRepository;

    /**
    * Save the user at AD.
    *
    * @param username       the user login name
    * @param name           the user name and surename
    * @param companyExtName the company external name
    * @param email          the user email
    * @param description    the user description
    * @return the newly created user
    */
    public Person createPerson(String username, String name, String companyExtName,
                            String email, String description) {

        final Person user = new Person();
        user.setAccountName(username);
        user.setCommonName(name);
        user.setCompany(companyExtName);
        user.setMail(email);
        user.setUsername(email);

        String tempPass = RandomStringUtils.randomAscii(10);
        user.setPassword(digestSHA(tempPass));
        user.setDn(LdapNameBuilder.newInstance("DC=company, DC=com")
                .build());

        List<String> objClasses = new ArrayList<>();
        objClasses.add("person");
        objClasses.add("top");
        user.setObjectClasses(objClasses);
        user.setObjectCategory("CN=Person,CN=Schema,CN=Configuration,DC=company,DC=com");

        List<String> groups = new ArrayList<>();
        groups.add("CN=Administrators,CN=Builtin,DC=company,DC=com");
        user.setGroups(groups);

        if (description != null && !description.isEmpty()) {
            user.setDesc(description);
        }

        return personRepository.save(user);
    }

    /**
    * Encodes the user password as it is used at Active Directory
    *
    * @param plain the plain text password
    * @return the password hash
    */
    private static String digestSHA(String plain) {
        try {
            MessageDigest digester = MessageDigest.getInstance("SHA-256");
            digester.update(plain.getBytes());
            return String.format("{SHA}%s", Base64.getEncoder().encodeToString(digester.digest()));
        } catch (NoSuchAlgorithmException ex) {
            return null;
        }
    }

The exception is thrown when I call personRepository.save(user);

As a addtional information, I've already tried a few variations of the code attached -- tried to remove almost all user data beforing saving it, different password encodings and hashing -- but the result is always the same. Any help on this will be greatly appreciated.

Thanks!

EDIT: Investigation indicates that the cause is probably something related with the way I'm sending my user DN. Anyway, I'm still wrestling with this issue.


Solution

  • I was able to create/edit my Active Directory users with a workaround. In my UserService, instead of using the Spring Data Ldap repository, I've used the LdapTemplate methods, like shown below.

    // UserService.java
       public void createPerson() {
        Name userDn = LdapNameBuilder
                .newInstance()
                .add("ou", ou)
                .add("cn", accountName)
                .build();
        DirContextAdapter context = new DirContextAdapter(userDn);
    
        context.setAttributeValue("cn", accountName);
        context.setAttributeValue("sn", accountName);
        context.setAttributeValue("userPassword", digestSHA(password));
        context.setAttributeValue("company", company);
        context.setAttributeValue("description", desc);
        context.setAttributeValue("mail", mail);
        context.setAttributeValue("sAMAccountName", accountName);
        context.setAttributeValue("userPrincipalName", username);
        context.setAttributeValue("objectCategory", objectCategory);
    
        context.setAttributeValues("objectClass", objectClasses.toArray());
        DirContextAdapter context = user.getLdapContext("Users");
        ldapTemplate.bind(context);
     }
    

    Since I used the same values for user creation with both Spring Data and LdapTemplate, my original issue is probably related to some treatment Spring does before sending the data to my Active Directory server.

    Since the method above is currently working for me, I'll follow with it. When I have some spare time I'll go back to this to find out what I was doing wrong with Spring.

    For future use, I believe it is related to memberOf attribute. This attribute must be set after the user is created, but it seems that Spring Data is filling this property with an empty string even if I set the attribute to null when creating the user.