+package org.argeo.naming;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.StringTokenizer;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.osgi.useradmin.UserDirectoryException;
+
+/**
+ * LDAP authPassword field according to RFC 3112.
+ *
+ * @see https://www.ietf.org/rfc/rfc3112.txt
+ */
+public class AuthPassword implements CallbackHandler {
+ private final String authScheme;
+ private final String authInfo;
+ private final String authValue;
+
+ public AuthPassword(String value) {
+ StringTokenizer st = new StringTokenizer(value, "$");
+ // TODO make it more robust, deal with bad formatting
+ this.authScheme = st.nextToken().trim();
+ this.authInfo = st.nextToken().trim();
+ this.authValue = st.nextToken().trim();
+
+ String expectedAuthScheme = getExpectedAuthScheme();
+ if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
+ throw new IllegalArgumentException(
+ "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
+ }
+
+ protected AuthPassword(String authInfo, String authValue) {
+ this.authScheme = getExpectedAuthScheme();
+ if (authScheme == null)
+ throw new IllegalArgumentException("Expected auth scheme cannot be null");
+ this.authInfo = authInfo;
+ this.authValue = authValue;
+ }
+
+ protected AuthPassword(AuthPassword authPassword) {
+ this.authScheme = authPassword.getAuthScheme();
+ this.authInfo = authPassword.getAuthInfo();
+ this.authValue = authPassword.getAuthValue();
+ }
+
+ protected String getExpectedAuthScheme() {
+ return null;
+ }
+
+ protected boolean matchAuthValue(Object object) {
+ return authValue.equals(object.toString());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AuthPassword))
+ return false;
+ AuthPassword authPassword = (AuthPassword) obj;
+ return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
+ && authValue.equals(authValue);
+ }
+
+ public boolean keyEquals(AuthPassword authPassword) {
+ return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ return authValue.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return toAuthPassword();
+ }
+
+ public final String toAuthPassword() {
+ return getAuthScheme() + '$' + authInfo + '$' + authValue;
+ }
+
+ public String getAuthScheme() {
+ return authScheme;
+ }
+
+ public String getAuthInfo() {
+ return authInfo;
+ }
+
+ public String getAuthValue() {
+ return authValue;
+ }
+
+ public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
+ try {
+ Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
+ NamingEnumeration<?> values = authPassword.getAll();
+ while (values.hasMore()) {
+ Object val = values.next();
+ AuthPassword token = new AuthPassword(val.toString());
+ String auth;
+ if (Arrays.binarySearch(value, '$') >= 0) {
+ auth = token.authInfo + '$' + token.authValue;
+ } else {
+ auth = token.authValue;
+ }
+ if (Arrays.equals(auth.toCharArray(), value))
+ return token;
+ // if (token.matchAuthValue(value))
+ // return token;
+ }
+ return null;
+ } catch (NamingException e) {
+ throw new UserDirectoryException("Cannot check attribute", e);
+ }
+ }
+
+ public static boolean remove(Attributes attributes, AuthPassword value) {
+ Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
+ return authPassword.remove(value.toAuthPassword());
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback)
+ ((NameCallback) callback).setName(toAuthPassword());
+ else if (callback instanceof PasswordCallback)
+ ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
+ }
+ }
+
+}