Improve and simplify OSGi Boot
[lgpl/argeo-commons.git] / org.argeo.security.core / src / org / argeo / osgi / useradmin / LdifUser.java
index 304dda9b1002a926664df181a50b51e0f93f3185..866c48c6b4c8d64d0abfe879e9482c8fd74770a9 100644 (file)
@@ -1,12 +1,19 @@
 package org.argeo.osgi.useradmin;
 
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
 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.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
@@ -15,8 +22,7 @@ import javax.naming.directory.Attributes;
 import javax.naming.directory.BasicAttribute;
 import javax.naming.ldap.LdapName;
 
-import org.argeo.osgi.useradmin.AbstractUserDirectory.WorkingCopy;
-
+/** Directory user implementation */
 class LdifUser implements DirectoryUser {
        private final AbstractUserDirectory userAdmin;
 
@@ -32,8 +38,7 @@ class LdifUser implements DirectoryUser {
                this(userAdmin, dn, attributes, false);
        }
 
-       private LdifUser(AbstractUserDirectory userAdmin, LdapName dn,
-                       Attributes attributes, boolean frozen) {
+       private LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes, boolean frozen) {
                this.userAdmin = userAdmin;
                this.dn = dn;
                this.publishedAttributes = attributes;
@@ -64,6 +69,13 @@ class LdifUser implements DirectoryUser {
 
        @Override
        public boolean hasCredential(String key, Object value) {
+               if (key == null) {
+                       // TODO check other sources (like PKCS12)
+                       char[] password = toChars(value);
+                       byte[] hashedPassword = hash(password);
+                       return hasCredential(LdifName.userPassword.name(), hashedPassword);
+               }
+
                Object storedValue = getCredentials().get(key);
                if (storedValue == null || value == null)
                        return false;
@@ -76,6 +88,37 @@ class LdifUser implements DirectoryUser {
                return false;
        }
 
+       /** Hash and clear the password */
+       private byte[] hash(char[] password) {
+               byte[] hashedPassword = ("{SHA}" + Base64.getEncoder().encodeToString(DigestUtils.sha1(toBytes(password))))
+                               .getBytes();
+               Arrays.fill(password, '\u0000');
+               return hashedPassword;
+       }
+
+       private byte[] toBytes(char[] chars) {
+               CharBuffer charBuffer = CharBuffer.wrap(chars);
+               ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
+               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
+               Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+               Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+               return bytes;
+       }
+
+       private char[] toChars(Object obj) {
+               if (obj instanceof char[])
+                       return (char[]) obj;
+               if (!(obj instanceof byte[]))
+                       throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
+               ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
+               CharBuffer toBuffer = Charset.forName("UTF-8").decode(fromBuffer);
+               char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
+               Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
+               Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
+               Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
+               return res;
+       }
+
        @Override
        public LdapName getDn() {
                return dn;
@@ -92,11 +135,11 @@ class LdifUser implements DirectoryUser {
                return getWc().getAttributes(getDn());
        }
 
-       private synchronized boolean isEditing() {
+       protected synchronized boolean isEditing() {
                return getWc() != null && getModifiedAttributes() != null;
        }
 
-       private synchronized WorkingCopy getWc() {
+       private synchronized UserDirectoryWorkingCopy getWc() {
                return userAdmin.getWorkingCopy();
        }
 
@@ -114,13 +157,6 @@ class LdifUser implements DirectoryUser {
                publishedAttributes = modifiedAttributes;
        }
 
-       // protected synchronized void stopEditing(boolean apply) {
-       // assert getModifiedAttributes() != null;
-       // if (apply)
-       // publishedAttributes = getModifiedAttributes();
-       // // modifiedAttributes = null;
-       // }
-
        public DirectoryUser getPublished() {
                return new LdifUser(userAdmin, dn, publishedAttributes, true);
        }
@@ -168,8 +204,7 @@ class LdifUser implements DirectoryUser {
                                                effectiveKeys.add(id);
                                }
                        } catch (NamingException e) {
-                               throw new UserDirectoryException(
-                                               "Cannot initialise attribute dictionary", e);
+                               throw new UserDirectoryException("Cannot initialise attribute dictionary", e);
                        }
                }
 
@@ -201,12 +236,7 @@ class LdifUser implements DirectoryUser {
                                @Override
                                public Object nextElement() {
                                        String key = it.next();
-                                       try {
-                                               return getAttributes().get(key).get();
-                                       } catch (NamingException e) {
-                                               throw new UserDirectoryException(
-                                                               "Cannot get value for key " + key, e);
-                                       }
+                                       return get(key);
                                }
 
                        };
@@ -218,43 +248,75 @@ class LdifUser implements DirectoryUser {
                                Attribute attr = getAttributes().get(key.toString());
                                if (attr == null)
                                        return null;
-                               return attr.get();
+                               Object value = attr.get();
+                               if (value instanceof byte[]) {
+                                       if (key.equals(LdifName.userPassword.name()))
+                                               // TODO other cases (certificates, images)
+                                               return value;
+                                       value = new String((byte[]) value, Charset.forName("UTF-8"));
+                               }
+                               if (attr.size() == 1)
+                                       return value;
+                               if (!attr.getID().equals(LdifName.objectClass.name()))
+                                       return value;
+                               // special case for object class
+                               NamingEnumeration<?> en = attr.getAll();
+                               Set<String> objectClasses = new HashSet<String>();
+                               while (en.hasMore()) {
+                                       String objectClass = en.next().toString();
+                                       objectClasses.add(objectClass);
+                               }
+
+                               if (objectClasses.contains(userAdmin.getUserObjectClass()))
+                                       return userAdmin.getUserObjectClass();
+                               else if (objectClasses.contains(userAdmin.getGroupObjectClass()))
+                                       return userAdmin.getGroupObjectClass();
+                               else
+                                       return value;
                        } catch (NamingException e) {
-                               throw new UserDirectoryException(
-                                               "Cannot get value for attribute " + key, e);
+                               throw new UserDirectoryException("Cannot get value for attribute " + key, e);
                        }
                }
 
                @Override
                public Object put(String key, Object value) {
+                       if (key == null) {
+                               // TODO persist to other sources (like PKCS12)
+                               char[] password = toChars(value);
+                               byte[] hashedPassword = hash(password);
+                               return put(LdifName.userPassword.name(), hashedPassword);
+                       }
+
                        userAdmin.checkEdit();
                        if (!isEditing())
                                startEditing();
 
                        if (!(value instanceof String || value instanceof byte[]))
-                               throw new IllegalArgumentException(
-                                               "Value must be String or byte[]");
+                               throw new IllegalArgumentException("Value must be String or byte[]");
 
                        if (includeFilter && !attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key
-                                               + " not included");
+                               throw new IllegalArgumentException("Key " + key + " not included");
                        else if (!includeFilter && attrFilter.contains(key))
                                throw new IllegalArgumentException("Key " + key + " excluded");
 
                        try {
-                               Attribute attribute = getModifiedAttributes().get(
-                                               key.toString());
+                               Attribute attribute = getModifiedAttributes().get(key.toString());
                                attribute = new BasicAttribute(key.toString());
-                               attribute.add(value);
-                               Attribute previousAttribute = getModifiedAttributes().put(
-                                               attribute);
+                               if (value instanceof String && !isAsciiPrintable(((String) value)))
+                                       try {
+                                               attribute.add(((String) value).getBytes("UTF-8"));
+                                       } catch (UnsupportedEncodingException e) {
+                                               throw new UserDirectoryException("Cannot encode " + value, e);
+                                       }
+                               else
+                                       attribute.add(value);
+                               Attribute previousAttribute = getModifiedAttributes().put(attribute);
                                if (previousAttribute != null)
                                        return previousAttribute.get();
                                else
                                        return null;
                        } catch (NamingException e) {
-                               throw new UserDirectoryException(
-                                               "Cannot get value for attribute " + key, e);
+                               throw new UserDirectoryException("Cannot get value for attribute " + key, e);
                        }
                }
 
@@ -265,8 +327,7 @@ class LdifUser implements DirectoryUser {
                                startEditing();
 
                        if (includeFilter && !attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key
-                                               + " not included");
+                               throw new IllegalArgumentException("Key " + key + " not included");
                        else if (!includeFilter && attrFilter.contains(key))
                                throw new IllegalArgumentException("Key " + key + " excluded");
 
@@ -277,10 +338,26 @@ class LdifUser implements DirectoryUser {
                                else
                                        return null;
                        } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot remove attribute "
-                                               + key, e);
+                               throw new UserDirectoryException("Cannot remove attribute " + key, e);
+                       }
+               }
+       }
+
+       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;
        }
 
 }