Introduce weak authentication
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 26 Aug 2017 13:46:45 +0000 (15:46 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 26 Aug 2017 13:46:45 +0000 (15:46 +0200)
org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java
org.argeo.enterprise/src/org/argeo/naming/NamingUtils.java [new file with mode: 0644]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java
org.argeo.node.api/src/org/argeo/node/NodeConstants.java

index 9d41cea6923a5666f4bfb89553b8cb96f2e9fa7d..d3103627c294259f8d4218e3534f2a8d98ce89c1 100644 (file)
@@ -20,6 +20,7 @@ import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.cms.CmsException;
+import org.argeo.naming.LdapAttrs;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
@@ -175,6 +176,15 @@ public class HttpSessionLoginModule implements LoginModule {
                                }
                        }
                }
+
+               // auth token
+//             String mail = request.getParameter(LdapAttrs.mail.name());
+//             String authPassword = request.getParameter(LdapAttrs.authPassword.name());
+//             if (authPassword != null) {
+//                     sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
+//                     if (mail != null)
+//                             sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
+//             }
        }
 
        private X509Certificate[] extractClientCertificate(HttpServletRequest req) {
diff --git a/org.argeo.enterprise/src/org/argeo/naming/NamingUtils.java b/org.argeo.enterprise/src/org/argeo/naming/NamingUtils.java
new file mode 100644 (file)
index 0000000..fc50502
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.naming;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class NamingUtils {
+       private final static DateTimeFormatter ldapDateTimeFormatter = DateTimeFormatter
+                       .ofPattern("uuuuMMddHHmmss[,S][.S]X");
+
+       public static OffsetDateTime ldapDateToInstant(String ldapDate) {
+               return OffsetDateTime.parse(ldapDate, ldapDateTimeFormatter);
+       }
+
+       public static String getQueryValue(Map<String, List<String>> query, String key) {
+               if (!query.containsKey(key))
+                       return null;
+               List<String> val = query.get(key);
+               if (val.size() == 1)
+                       return val.get(0);
+               else
+                       throw new IllegalArgumentException("There are " + val.size() + " value(s) for " + key);
+       }
+
+       public static Map<String, List<String>> queryToMap(URI uri) {
+               return queryToMap(uri.getQuery());
+       }
+
+       private static Map<String, List<String>> queryToMap(String queryPart) {
+               try {
+                       final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
+                       if (queryPart == null)
+                               return query_pairs;
+                       final String[] pairs = queryPart.split("&");
+                       for (String pair : pairs) {
+                               final int idx = pair.indexOf("=");
+                               final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
+                                               : pair;
+                               if (!query_pairs.containsKey(key)) {
+                                       query_pairs.put(key, new LinkedList<String>());
+                               }
+                               final String value = idx > 0 && pair.length() > idx + 1
+                                               ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name()) : null;
+                               query_pairs.get(key).add(value);
+                       }
+                       return query_pairs;
+               } catch (UnsupportedEncodingException e) {
+                       throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
+               }
+       }
+
+       private NamingUtils() {
+
+       }
+}
index c20260056ddbbd0951d35cba7f3b8db1c459a415..081d9e1faa76888705c314355c6a86c4e03b0f47 100644 (file)
@@ -60,7 +60,8 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory
        // LdapAttrs.cn.name() });
 
        private String memberAttributeId = "member";
-       private List<String> credentialAttributeIds = Arrays.asList(new String[] { LdapAttrs.userPassword.name() });
+       private List<String> credentialAttributeIds = Arrays
+                       .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
 
        // JTA
        private TransactionManager transactionManager;
index d26ed148f0f861aa22ed0661f56339c4d63da381..7cf416526ffdea417975c0cc9c3f704d564c8e2e 100644 (file)
@@ -1,9 +1,12 @@
 package org.argeo.osgi.useradmin;
 
-import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Base64;
@@ -13,7 +16,9 @@ import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.StringTokenizer;
 
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
@@ -23,6 +28,7 @@ import javax.naming.directory.BasicAttribute;
 import javax.naming.ldap.LdapName;
 
 import org.argeo.naming.LdapAttrs;
+import org.argeo.naming.NamingUtils;
 
 /** Directory user implementation */
 class LdifUser implements DirectoryUser {
@@ -73,12 +79,47 @@ class LdifUser implements DirectoryUser {
        public boolean hasCredential(String key, Object value) {
                if (key == null) {
                        // TODO check other sources (like PKCS12)
+                       String pwd = new String((char[]) value);
                        char[] password = toChars(value);
                        byte[] hashedPassword = hash(password);
-                       return hasCredential(LdapAttrs.userPassword.name(), hashedPassword);
+                       if (hasCredential(LdapAttrs.userPassword.name(), hashedPassword))
+                               return true;
+                       if (hasCredential(LdapAttrs.authPassword.name(), pwd))
+                               return true;
+                       return false;
                }
 
                Object storedValue = getCredentials().get(key);
+
+               // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
+               if (LdapAttrs.authPassword.name().equals(key)) {
+                       StringTokenizer st = new StringTokenizer((String) storedValue, "$ ");
+                       // TODO make it more robust, deal with bad formatting
+                       String authScheme = st.nextToken();
+                       String authInfo = st.nextToken();
+                       String authValue = st.nextToken();
+                       if (authScheme.equals("X-Node-Token")) {
+                               try {
+                                       URI uri = new URI(authInfo);
+                                       Map<String, List<String>> query = NamingUtils.queryToMap(uri);
+                                       String expiryTimestamp = NamingUtils.getQueryValue(query, LdapAttrs.modifyTimestamp.name());
+                                       if (expiryTimestamp != null) {
+                                               OffsetDateTime expiryOdt = NamingUtils.ldapDateToInstant(expiryTimestamp);
+                                               if (expiryOdt.isBefore(OffsetDateTime.now()))
+                                                       return false;
+                                       } else {
+                                               throw new UnsupportedOperationException("An expiry timestamp "
+                                                               + LdapAttrs.modifyTimestamp.name() + " must be set in the URI query");
+                                       }
+                                       byte[] hash = Base64.getDecoder().decode(authValue);
+                                       byte[] hashedInput = DigestUtils.sha1((authInfo + value).getBytes(StandardCharsets.US_ASCII));
+                                       return Arrays.equals(hash, hashedInput);
+                               } catch (URISyntaxException e) {
+                                       throw new UserDirectoryException("Badly formatted " + authInfo, e);
+                               }
+                       }
+               }
+
                if (storedValue == null || value == null)
                        return false;
                if (!(value instanceof String || value instanceof byte[]))
@@ -93,14 +134,14 @@ class LdifUser implements DirectoryUser {
        /** 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');
+                               .getBytes(StandardCharsets.UTF_8);
+               // Arrays.fill(password, '\u0000');
                return hashedPassword;
        }
 
        private byte[] toBytes(char[] chars) {
                CharBuffer charBuffer = CharBuffer.wrap(chars);
-               ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
+               ByteBuffer byteBuffer = StandardCharsets.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
@@ -113,7 +154,7 @@ class LdifUser implements DirectoryUser {
                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);
+               CharBuffer toBuffer = StandardCharsets.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
@@ -255,7 +296,7 @@ class LdifUser implements DirectoryUser {
                                        if (key.equals(LdapAttrs.userPassword.name()))
                                                // TODO other cases (certificates, images)
                                                return value;
-                                       value = new String((byte[]) value, Charset.forName("UTF-8"));
+                                       value = new String((byte[]) value, StandardCharsets.UTF_8);
                                }
                                if (attr.size() == 1)
                                        return value;
@@ -305,11 +346,7 @@ class LdifUser implements DirectoryUser {
                                Attribute attribute = getModifiedAttributes().get(key.toString());
                                attribute = new BasicAttribute(key.toString());
                                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);
-                                       }
+                                       attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
                                else
                                        attribute.add(value);
                                Attribute previousAttribute = getModifiedAttributes().put(attribute);
index 093b443702474c0d50ab19e6dec8b540e4b7d4d8..83cbf795c7fc91c9c118298b4f8a3f3b53fa7f4b 100644 (file)
@@ -1,16 +1,12 @@
 package org.argeo.osgi.useradmin;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.URLDecoder;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.Hashtable;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -20,6 +16,7 @@ import javax.naming.NamingException;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.naming.DnsBrowser;
+import org.argeo.naming.NamingUtils;
 import org.osgi.framework.Constants;
 
 /** Properties used to configure user admins. */
@@ -150,7 +147,7 @@ public enum UserAdminConf {
                                } else if (scheme.equals("ipa")) {
                                } else
                                        throw new UserDirectoryException("Unsupported scheme " + scheme);
-                       Map<String, List<String>> query = splitQuery(u.getQuery());
+                       Map<String, List<String>> query = NamingUtils.queryToMap(u);
                        for (String key : query.keySet()) {
                                UserAdminConf ldapProp = UserAdminConf.valueOf(key);
                                List<String> values = query.get(key);
@@ -221,23 +218,26 @@ public enum UserAdminConf {
 
        }
 
-       private static Map<String, List<String>> splitQuery(String query) throws UnsupportedEncodingException {
-               final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
-               if (query == null)
-                       return query_pairs;
-               final String[] pairs = query.split("&");
-               for (String pair : pairs) {
-                       final int idx = pair.indexOf("=");
-                       final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
-                       if (!query_pairs.containsKey(key)) {
-                               query_pairs.put(key, new LinkedList<String>());
-                       }
-                       final String value = idx > 0 && pair.length() > idx + 1
-                                       ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
-                       query_pairs.get(key).add(value);
-               }
-               return query_pairs;
-       }
+       // private static Map<String, List<String>> splitQuery(String query) throws
+       // UnsupportedEncodingException {
+       // final Map<String, List<String>> query_pairs = new LinkedHashMap<String,
+       // List<String>>();
+       // if (query == null)
+       // return query_pairs;
+       // final String[] pairs = query.split("&");
+       // for (String pair : pairs) {
+       // final int idx = pair.indexOf("=");
+       // final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx),
+       // "UTF-8") : pair;
+       // if (!query_pairs.containsKey(key)) {
+       // query_pairs.put(key, new LinkedList<String>());
+       // }
+       // final String value = idx > 0 && pair.length() > idx + 1
+       // ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
+       // query_pairs.get(key).add(value);
+       // }
+       // return query_pairs;
+       // }
 
        public static void main(String[] args) {
                Dictionary<String, ?> props = uriAsProperties("ldap://" + "uid=admin,ou=system:secret@localhost:10389"
index 01fb5185ab106140455c8b3d03c4f2dab6cd7bf8..2b4c284f62034cfc51a3795907e0069a8a3df764 100644 (file)
@@ -62,6 +62,8 @@ public interface NodeConstants {
        // user U anonymous = everyone
        String ROLE_USER = "cn=user," + ROLES_BASEDN;
        String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN;
+       // Account lifecycle
+       String ROLE_REGISTERING = "cn=registering," + ROLES_BASEDN;
 
        /*
         * LOGIN CONTEXTS