From documentation for UnicodePwd:
If the Modify request contains a delete operation containing a value Vdel for unicodePwd followed by an add operation containing a value Vadd for unicodePwd, the server considers the request to be a request to change the password. The server decodes Vadd and Vdel using the password decoding procedure documented later in this section. Vdel is the old password, while Vadd is the new password.
If the Modify request contains a single replace operation containing a value Vrep for unicodePwd, the server considers the request to be an administrative reset of the password, that is, a password modification without knowledge of the old password. The server decodes Vrep using the password decoding procedure documented later in this section and uses it as the new password.
I have not yet found way to let a user do a "forget password -> confirm email -> set new password" procedure on a webserver that uses AD as the user store while verifying the password according to PwdHistoryLength
's check for password reuse. How should I do it?
public class Test {
private static DirContext adminLdapContext;
public static void main(String[] args) throws Exception {
initLdapContext();
createNewUser("user.x");
assertThrows(() -> updateUserPasswordByAdmin("user.x", "X"), NamingException.class,
"X should fail MinPwdLength");
updateUserPasswordByAdmin("user.x", "good-Pwd$%^123");
updateUserPassword("user.x", "good-Pwd$%^123", "good-Pwd$%^1237");
updateUserPasswordByAdmin("user.x", "good-Pwd$%^1234");
updateUserPasswordByAdmin("user.x", "good-Pwd$%^12345");
updateUserPasswordByAdmin("user.x", "good-Pwd$%^123456");
updateUserPassword("user.x", "good-Pwd$%^123456", "good-Pwd$%^1234567");
assertThrows(() -> updateUserPassword("user.x", "good-Pwd$%^1234567", "good-Pwd$%^123456"),
NamingException.class, "the password with 123456 should be rejected for reuse");
assertThrows(() -> updateUserPasswordByAdmin("user.x", "good-Pwd$%^12345"), NamingException.class,
"the password with 12345 should be rejected for reuse");
}
// static void initLdapContext(): initialize adminLdapContext
// static void createNewUser(String uname): use adminLdapContext and create user with sAMAccountName = cn = uname
// static void updateUserPassword(String uname, String oldpass, String newpass): Use Vdel+Vadd on UnicodePwd
// static void updateUserPasswordByAdmin(String uname, String newpass): Use Vrep on UnicodePwd
// static void assertThrows(RunnableThrows r, Class<? extends Throwable> ex, String message) throws AssertionError: throw AssertionError if r.run() doesn't throw ex
// @FunctionalInterface static interface RunnableThrows: see https://stackoverflow.com/a/18198349/2185599
}
Output:
Exception in thread "main" java.lang.AssertionError: the password with 12345 should be rejected for reuse
at sketchbook.Main.assertThrows(Main.java:97)
at sketchbook.Main.main(Main.java:67)
Caused by: java.lang.AssertionError: r.run() didn't throw
at sketchbook.Main.assertThrows(Main.java:94)
... 1 more
Workaround:
Double the password history count and generate random UUID's to be set into AD.
- Change Password: oldPassword --vdel vadd--> UUID --vdel vadd--> newPassword
- Reset Password: oldPassword (unknown) --vrep--> UUID --vdel vadd--> newPassword
The downside is the the password history would contain half generated UUIDs.