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;
}
}
}
+
+ // 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) {
--- /dev/null
+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() {
+
+ }
+}
// 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;
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;
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;
import javax.naming.ldap.LdapName;
import org.argeo.naming.LdapAttrs;
+import org.argeo.naming.NamingUtils;
/** Directory user implementation */
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[]))
/** 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
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
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;
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);
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;
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. */
} 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);
}
- 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"
// 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