Massive package refactoring
[lgpl/argeo-commons.git] / org.argeo.util / src / org / argeo / util / directory / ldap / DefaultLdapEntry.java
diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/DefaultLdapEntry.java b/org.argeo.util/src/org/argeo/util/directory/ldap/DefaultLdapEntry.java
deleted file mode 100644 (file)
index c01d1c3..0000000
+++ /dev/null
@@ -1,481 +0,0 @@
-package org.argeo.util.directory.ldap;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.StringJoiner;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.directory.DirectoryDigestUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-
-/** An entry in an LDAP (or LDIF) directory. */
-public class DefaultLdapEntry implements LdapEntry {
-       private final AbstractLdapDirectory directory;
-
-       private final LdapName dn;
-
-       private AttributeDictionary properties;
-       private AttributeDictionary credentials;
-
-//     private String primaryObjectClass;
-//     private List<String> objectClasses = new ArrayList<>();
-
-       protected DefaultLdapEntry(AbstractLdapDirectory directory, LdapName dn) {
-               Objects.requireNonNull(directory);
-               Objects.requireNonNull(dn);
-               this.directory = directory;
-               this.dn = dn;
-
-               // Object classes
-//             Objects.requireNonNull(initialAttributes);
-//             try {
-//                     NamingEnumeration<?> en = initialAttributes.get(LdapAttrs.objectClass.name()).getAll();
-//                     String first = null;
-//                     attrs: while (en.hasMore()) {
-//                             String v = en.next().toString();
-//                             if (v.equalsIgnoreCase(LdapObjs.top.name()))
-//                                     continue attrs;
-//                             if (first == null)
-//                                     first = v;
-//                             if (v.equalsIgnoreCase(getDirectory().getUserObjectClass()))
-//                                     primaryObjectClass = getDirectory().getUserObjectClass();
-//                             else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass()))
-//                                     primaryObjectClass = getDirectory().getGroupObjectClass();
-//                             objectClasses.add(v);
-//                     }
-//                     if (primaryObjectClass == null) {
-//                             if (first == null)
-//                                     throw new IllegalStateException("Could not find primary object class");
-//                             primaryObjectClass = first;
-//                     }
-//             } catch (NamingException e) {
-//                     throw new IllegalStateException("Cannot find object classes", e);
-//             }
-
-       }
-
-       @Override
-       public LdapName getDn() {
-               return dn;
-       }
-
-       public synchronized Attributes getAttributes() {
-               return isEditing() ? getModifiedAttributes() : getDirectory().getDirectoryDao().doGetAttributes(dn);
-       }
-
-       @Override
-       public List<LdapName> getReferences(String attributeId) {
-               Attribute memberAttribute = getAttributes().get(attributeId);
-               if (memberAttribute == null)
-                       return new ArrayList<LdapName>();
-               try {
-                       List<LdapName> roles = new ArrayList<LdapName>();
-                       NamingEnumeration<?> values = memberAttribute.getAll();
-                       while (values.hasMore()) {
-                               LdapName dn = new LdapName(values.next().toString());
-                               roles.add(dn);
-                       }
-                       return roles;
-               } catch (NamingException e) {
-                       throw new IllegalStateException("Cannot get members", e);
-               }
-
-       }
-
-       /** Should only be called from working copy thread. */
-       protected synchronized Attributes getModifiedAttributes() {
-               assert getWc() != null;
-               return getWc().getModifiedData().get(getDn());
-       }
-
-       protected synchronized boolean isEditing() {
-               return getWc() != null && getModifiedAttributes() != null;
-       }
-
-       private synchronized LdapEntryWorkingCopy getWc() {
-               return directory.getWorkingCopy();
-       }
-
-       protected synchronized void startEditing() {
-//             if (frozen)
-//                     throw new IllegalStateException("Cannot edit frozen view");
-               if (directory.isReadOnly())
-                       throw new IllegalStateException("User directory is read-only");
-               assert getModifiedAttributes() == null;
-               getWc().startEditing(this);
-               // modifiedAttributes = (Attributes) publishedAttributes.clone();
-       }
-
-       public synchronized void publishAttributes(Attributes modifiedAttributes) {
-//             publishedAttributes = modifiedAttributes;
-       }
-
-       /*
-        * PROPERTIES
-        */
-       @Override
-       public Dictionary<String, Object> getProperties() {
-               if (properties == null)
-                       properties = new AttributeDictionary(false);
-               return properties;
-       }
-
-       public Dictionary<String, Object> getCredentials() {
-               if (credentials == null)
-                       credentials = new AttributeDictionary(true);
-               return credentials;
-       }
-
-       /*
-        * CREDENTIALS
-        */
-       @Override
-       public boolean hasCredential(String key, Object value) {
-               if (key == null) {
-                       // TODO check other sources (like PKCS12)
-                       // String pwd = new String((char[]) value);
-                       // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
-                       char[] password = DirectoryDigestUtils.bytesToChars(value);
-
-                       if (getDirectory().getForcedPassword() != null
-                                       && getDirectory().getForcedPassword().equals(new String(password)))
-                               return true;
-
-                       AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
-                       if (authPassword != null) {
-                               if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
-                                       SharedSecret onceToken = new SharedSecret(authPassword);
-                                       if (onceToken.isExpired()) {
-                                               // AuthPassword.remove(getAttributes(), onceToken);
-                                               return false;
-                                       } else {
-                                               // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
-                                               return true;
-                                       }
-                                       // TODO delete expired tokens?
-                               } else {
-                                       // TODO implement SHA
-                                       throw new UnsupportedOperationException(
-                                                       "Unsupported authPassword scheme " + authPassword.getAuthScheme());
-                               }
-                       }
-
-                       // Regular password
-//                     byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
-                       if (hasCredential(LdapAttrs.userPassword.name(), DirectoryDigestUtils.charsToBytes(password)))
-                               return true;
-                       return false;
-               }
-
-               Object storedValue = getCredentials().get(key);
-               if (storedValue == null || value == null)
-                       return false;
-               if (!(value instanceof String || value instanceof byte[]))
-                       return false;
-               if (storedValue instanceof String && value instanceof String)
-                       return storedValue.equals(value);
-               if (storedValue instanceof byte[] && value instanceof byte[]) {
-                       String storedBase64 = new String((byte[]) storedValue, US_ASCII);
-                       String passwordScheme = null;
-                       if (storedBase64.charAt(0) == '{') {
-                               int index = storedBase64.indexOf('}');
-                               if (index > 0) {
-                                       passwordScheme = storedBase64.substring(1, index);
-                                       String storedValueBase64 = storedBase64.substring(index + 1);
-                                       byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
-                                       char[] passwordValue = DirectoryDigestUtils.bytesToChars((byte[]) value);
-                                       byte[] valueBytes;
-                                       if (DirectoryDigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
-                                               valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null,
-                                                               null);
-                                       } else if (DirectoryDigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
-                                               // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
-                                               byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
-                                               BigInteger iterations = new BigInteger(iterationsArr);
-                                               byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
-                                                               iterationsArr.length + 64);
-                                               byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
-                                                               storedValueBytes.length);
-                                               int keyLengthBits = keyArr.length * 8;
-                                               valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
-                                                               iterations.intValue(), keyLengthBits);
-                                       } else {
-                                               throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
-                                       }
-                                       return Arrays.equals(storedValueBytes, valueBytes);
-                               }
-                       }
-               }
-//             if (storedValue instanceof byte[] && value instanceof byte[]) {
-//                     return Arrays.equals((byte[]) storedValue, (byte[]) value);
-//             }
-               return false;
-       }
-
-       /** Hash the password */
-       private static byte[] sha1hash(char[] password) {
-               byte[] hashedPassword = ("{SHA}" + Base64.getEncoder()
-                               .encodeToString(DirectoryDigestUtils.sha1(DirectoryDigestUtils.charsToBytes(password))))
-                               .getBytes(StandardCharsets.UTF_8);
-               return hashedPassword;
-       }
-
-       public AbstractLdapDirectory getDirectory() {
-               return directory;
-       }
-
-       public LdapDirectoryDao getDirectoryDao() {
-               return directory.getDirectoryDao();
-       }
-
-       @Override
-       public int hashCode() {
-               return dn.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (this == obj)
-                       return true;
-               if (obj instanceof LdapEntry) {
-                       LdapEntry that = (LdapEntry) obj;
-                       return this.dn.equals(that.getDn());
-               }
-               return false;
-       }
-
-       @Override
-       public String toString() {
-               return dn.toString();
-       }
-
-       private static boolean isAsciiPrintable(String str) {
-               if (str == null) {
-                       return false;
-               }
-               int sz = str.length();
-               for (int i = 0; i < sz; i++) {
-                       if (isAsciiPrintable(str.charAt(i)) == false) {
-                               return false;
-                       }
-               }
-               return true;
-       }
-
-       private static boolean isAsciiPrintable(char ch) {
-               return ch >= 32 && ch < 127;
-       }
-
-       protected class AttributeDictionary extends Dictionary<String, Object> {
-               private final List<String> effectiveKeys = new ArrayList<String>();
-               private final List<String> attrFilter;
-               private final Boolean includeFilter;
-
-               public AttributeDictionary(Boolean credentials) {
-                       this.attrFilter = getDirectory().getCredentialAttributeIds();
-                       this.includeFilter = credentials;
-                       try {
-                               NamingEnumeration<String> ids = getAttributes().getIDs();
-                               while (ids.hasMore()) {
-                                       String id = ids.next();
-                                       if (credentials && attrFilter.contains(id))
-                                               effectiveKeys.add(id);
-                                       else if (!credentials && !attrFilter.contains(id))
-                                               effectiveKeys.add(id);
-                               }
-                       } catch (NamingException e) {
-                               throw new IllegalStateException("Cannot initialise attribute dictionary", e);
-                       }
-                       if (!credentials)
-                               effectiveKeys.add(LdapAttrs.objectClasses.name());
-               }
-
-               @Override
-               public int size() {
-                       return effectiveKeys.size();
-               }
-
-               @Override
-               public boolean isEmpty() {
-                       return effectiveKeys.size() == 0;
-               }
-
-               @Override
-               public Enumeration<String> keys() {
-                       return Collections.enumeration(effectiveKeys);
-               }
-
-               @Override
-               public Enumeration<Object> elements() {
-                       final Iterator<String> it = effectiveKeys.iterator();
-                       return new Enumeration<Object>() {
-
-                               @Override
-                               public boolean hasMoreElements() {
-                                       return it.hasNext();
-                               }
-
-                               @Override
-                               public Object nextElement() {
-                                       String key = it.next();
-                                       return get(key);
-                               }
-
-                       };
-               }
-
-               @Override
-               public Object get(Object key) {
-                       try {
-                               Attribute attr = !key.equals(LdapAttrs.objectClasses.name()) ? getAttributes().get(key.toString())
-                                               : getAttributes().get(LdapAttrs.objectClass.name());
-                               if (attr == null)
-                                       return null;
-                               Object value = attr.get();
-                               if (value instanceof byte[]) {
-                                       if (key.equals(LdapAttrs.userPassword.name()))
-                                               // TODO other cases (certificates, images)
-                                               return value;
-                                       value = new String((byte[]) value, StandardCharsets.UTF_8);
-                               }
-                               if (attr.size() == 1)
-                                       return value;
-                               // special case for object class
-                               if (key.equals(LdapAttrs.objectClass.name())) {
-                                       // TODO support multiple object classes
-                                       NamingEnumeration<?> en = attr.getAll();
-                                       String first = null;
-                                       attrs: while (en.hasMore()) {
-                                               String v = en.next().toString();
-                                               if (v.equalsIgnoreCase(LdapObjs.top.name()))
-                                                       continue attrs;
-                                               if (first == null)
-                                                       first = v;
-                                               if (v.equalsIgnoreCase(getDirectory().getUserObjectClass()))
-                                                       return getDirectory().getUserObjectClass();
-                                               else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass()))
-                                                       return getDirectory().getGroupObjectClass();
-                                       }
-                                       if (first != null)
-                                               return first;
-                                       throw new IllegalStateException("Cannot find objectClass in " + value);
-                               } else {
-                                       NamingEnumeration<?> en = attr.getAll();
-                                       StringJoiner values = new StringJoiner("\n");
-                                       while (en.hasMore()) {
-                                               String v = en.next().toString();
-                                               values.add(v);
-                                       }
-                                       return values.toString();
-                               }
-//                             else
-//                                     return value;
-                       } catch (NamingException e) {
-                               throw new IllegalStateException("Cannot get value for attribute " + key, e);
-                       }
-               }
-
-               @Override
-               public Object put(String key, Object value) {
-                       Objects.requireNonNull(value, "Value for key " + key + " is null");
-                       try {
-                               if (key == null) {
-                                       // FIXME remove this "feature", a key should be specified
-                                       // TODO persist to other sources (like PKCS12)
-                                       char[] password = DirectoryDigestUtils.bytesToChars(value);
-                                       byte[] hashedPassword = sha1hash(password);
-                                       return put(LdapAttrs.userPassword.name(), hashedPassword);
-                               }
-                               if (key.startsWith("X-")) {
-                                       return put(LdapAttrs.authPassword.name(), value);
-                               }
-
-                               // start editing
-                               getDirectory().checkEdit();
-                               if (!isEditing())
-                                       startEditing();
-
-                               // object classes special case.
-                               if (key.equals(LdapAttrs.objectClasses.name())) {
-                                       Attribute attribute = new BasicAttribute(LdapAttrs.objectClass.name());
-                                       String[] objectClasses = value.toString().split("\n");
-                                       for (String objectClass : objectClasses) {
-                                               if (objectClass.trim().equals(""))
-                                                       continue;
-                                               attribute.add(objectClass);
-                                       }
-                                       Attribute previousAttribute = getModifiedAttributes().put(attribute);
-                                       if (previousAttribute != null)
-                                               return previousAttribute.get();
-                                       else
-                                               return null;
-                               }
-
-                               if (!(value instanceof String || value instanceof byte[]))
-                                       throw new IllegalArgumentException("Value must be String or byte[]");
-
-                               if (includeFilter && !attrFilter.contains(key))
-                                       throw new IllegalArgumentException("Key " + key + " not included");
-                               else if (!includeFilter && attrFilter.contains(key))
-                                       throw new IllegalArgumentException("Key " + key + " excluded");
-
-                               Attribute attribute = getModifiedAttributes().get(key.toString());
-                               // if (attribute == null) // block unit tests
-                               attribute = new BasicAttribute(key.toString());
-                               if (value instanceof String && !isAsciiPrintable(((String) value)))
-                                       attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
-                               else
-                                       attribute.add(value);
-                               Attribute previousAttribute = getModifiedAttributes().put(attribute);
-                               if (previousAttribute != null)
-                                       return previousAttribute.get();
-                               else
-                                       return null;
-                       } catch (NamingException e) {
-                               throw new IllegalStateException("Cannot get value for attribute " + key, e);
-                       }
-               }
-
-               @Override
-               public Object remove(Object key) {
-                       getDirectory().checkEdit();
-                       if (!isEditing())
-                               startEditing();
-
-                       if (includeFilter && !attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " not included");
-                       else if (!includeFilter && attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " excluded");
-
-                       try {
-                               Attribute attr = getModifiedAttributes().remove(key.toString());
-                               if (attr != null)
-                                       return attr.get();
-                               else
-                                       return null;
-                       } catch (NamingException e) {
-                               throw new IllegalStateException("Cannot remove attribute " + key, e);
-                       }
-               }
-
-       }
-
-}