X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.security.core%2Fsrc%2Forg%2Fargeo%2Fosgi%2Fuseradmin%2FLdifUser.java;h=d5ddba50c17060e7d29db08aa2cb38dc5bdabfb7;hb=0b8aa4c76cb7a1d19abf93a4c1ae0c973abdab5b;hp=cd5401a55794273ffd350c9e341d025d4bd6425c;hpb=25071ab6bcb2df1fa4057c2c04137f2d606772e7;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java index cd5401a55..d5ddba50c 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java @@ -1,12 +1,18 @@ 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.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,6 +21,10 @@ import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.ldap.LdapName; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; + +/** Directory user implementation */ class LdifUser implements DirectoryUser { private final AbstractUserDirectory userAdmin; @@ -22,7 +32,6 @@ class LdifUser implements DirectoryUser { private final boolean frozen; private Attributes publishedAttributes; - private Attributes modifiedAttributes = null; private final AttributeDictionary properties; private final AttributeDictionary credentials; @@ -63,6 +72,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; @@ -75,6 +91,41 @@ class LdifUser implements DirectoryUser { return false; } + /** Hash and clear the password */ + private byte[] hash(char[] password) { + byte[] hashedPassword = ("{SHA}" + Base64 + .encodeBase64String(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; @@ -82,11 +133,21 @@ class LdifUser implements DirectoryUser { @Override public synchronized Attributes getAttributes() { - return isEditing() ? modifiedAttributes : publishedAttributes; + return isEditing() ? getModifiedAttributes() : publishedAttributes; + } + + /** Should only be called from working copy thread. */ + private synchronized Attributes getModifiedAttributes() { + assert getWc() != null; + return getWc().getAttributes(getDn()); } protected synchronized boolean isEditing() { - return userAdmin.isEditing() && modifiedAttributes != null; + return getWc() != null && getModifiedAttributes() != null; + } + + private synchronized UserDirectoryWorkingCopy getWc() { + return userAdmin.getWorkingCopy(); } protected synchronized void startEditing() { @@ -94,15 +155,13 @@ class LdifUser implements DirectoryUser { throw new UserDirectoryException("Cannot edit frozen view"); if (getUserAdmin().isReadOnly()) throw new UserDirectoryException("User directory is read-only"); - assert modifiedAttributes == null; - modifiedAttributes = (Attributes) publishedAttributes.clone(); + assert getModifiedAttributes() == null; + getWc().startEditing(this); + // modifiedAttributes = (Attributes) publishedAttributes.clone(); } - protected synchronized void stopEditing(boolean apply) { - assert modifiedAttributes != null; - if (apply) - publishedAttributes = modifiedAttributes; - modifiedAttributes = null; + public synchronized void publishAttributes(Attributes modifiedAttributes) { + publishedAttributes = modifiedAttributes; } public DirectoryUser getPublished() { @@ -185,12 +244,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); } }; @@ -202,7 +256,32 @@ 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 objectClasses = new HashSet(); + 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); @@ -211,6 +290,13 @@ class LdifUser implements DirectoryUser { @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(); @@ -226,10 +312,21 @@ class LdifUser implements DirectoryUser { throw new IllegalArgumentException("Key " + key + " excluded"); try { - Attribute attribute = modifiedAttributes.get(key.toString()); + Attribute attribute = getModifiedAttributes().get( + key.toString()); attribute = new BasicAttribute(key.toString()); - attribute.add(value); - Attribute previousAttribute = modifiedAttributes.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 @@ -253,7 +350,7 @@ class LdifUser implements DirectoryUser { throw new IllegalArgumentException("Key " + key + " excluded"); try { - Attribute attr = modifiedAttributes.remove(key.toString()); + Attribute attr = getModifiedAttributes().remove(key.toString()); if (attr != null) return attr.get(); else @@ -265,4 +362,21 @@ class LdifUser implements DirectoryUser { } } + 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; + } + }