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.
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.