I'm developing a web app that let users reset their own passwords in Active Directory. I've been doing it by binding as an administrator and it works fine, but the directory policies (reuse history, characters, etc) are not being enforced. I can't bind as a user because I don't have the current password.
I read about the LDAP_SERVER_POLICY_HINTS control introduced in Windows 2008 R2 SP1 for doing that in Active Directory and even found someone who made it using Spring LDAP
Since I'm using UnboundID and there is no standard control shipped for that, I figured that I had to create my own control class. The documented OID is 1.2.840.113556.1.4.2239 and the value {48, 3, 2, 1, 1}
public class PolicyHintsControl extends Control {
private static final long serialVersionUID = 1L;
public final static String LDAP_SERVER_POLICY_HINTS_OID = "1.2.840.113556.1.4.2066";
public final static byte[] LDAP_SERVER_POLICY_HINTS_DATA = { 48,
(byte) 132, 0, 0, 0, 3, 2, 1, 1 };
public PolicyHintsControl() {
super(LDAP_SERVER_POLICY_HINTS_OID, false, new ASN1OctetString(
LDAP_SERVER_POLICY_HINTS_DATA));
}
@Override
public String getControlName() {
return "LDAP Server Policy Hints Control";
}
@Override
public void toString(StringBuilder buffer) {
buffer.append("LDAPServerPolicyHints(isCritical=");
buffer.append(isCritical());
buffer.append(')');
}
}
So I added this new control in the modify request like this:
public static void main(String[] args) throws Exception {
final String host = "ldap.example.com";
final int port = 636;
String adminDn = "admin@example.com";
String adminPassword = "passwd";
String userDn = "CN=user,ou=people,dc=example,dc=com";
String userPassword = "passwd";
String keystoreFile = "/path/to/keystore.jks";
String keystorePassword = "passwd";
String passwordAttribute = "unicodePwd";
//Password change requires SSL
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(keystoreFile), keystorePassword.toCharArray());
TrustManagerFactory factory = TrustManagerFactory.getInstance("x509");
factory.init(keyStore);
final SSLUtil sslUtil = new SSLUtil(factory.getTrustManagers());
SSLSocketFactory socketFactory = sslUtil.createSSLSocketFactory();
Debug.setEnabled(true);
// Connect as the configured administrator
LDAPConnection ldapConnection = new LDAPConnection(socketFactory, host,
port, adminDn, adminPassword);
// Set password in AD format
final String newQuotedPassword = "\"" + userPassword + "\"";
final byte[] newPasswordBytes = newQuotedPassword.getBytes("UTF-16LE");
String encryptedNewPwd = new String(newPasswordBytes);
//Build modifications array and request
final ArrayList<Modification> modifications = new ArrayList<Modification>();
modifications.add(new Modification(ModificationType.REPLACE,
passwordAttribute, encryptedNewPwd));
ModifyRequest modifyRequest = new ModifyRequest(userDn, modifications);
//Add the policy hints control
modifyRequest.addControl(new PolicyHintsControl());
//Modify already
ldapConnection.modify(modifyRequest);
ldapConnection.close();
}
I get the following exception:
Exception in thread "main" LDAPException(resultCode=53 (unwilling to perform), errorMessage='0000052D: SvcErr: DSID-031A120C, problem 5003 (WILL_NOT_PERFORM), data 0
', diagnosticMessage='0000052D: SvcErr: DSID-031A120C, problem 5003 (WILL_NOT_PERFORM), data 0
')
After researching a bit more I found that there was another update in Windows 2012 for the same control which changed the OID to 1.2.840.113556.1.4.2066 and deprecated the old OID.
Since this app can be configured with any version of AD I'd like to handle gracefully every scenario (Windows 2012, Windows 2008 R2 SP1, others). My questions are:
- Does anyone have successfully done this with UnboundID?
- Is there anyway to know if the controls are available before the modification request?
- What would be the best way to handle different OID's for different versions of AD for the same control? Same class or different classes?