org.eclipse.equinox.cm,\
org.eclipse.rap.rwt.osgi
-
argeo.osgi.start.3.node=\
org.argeo.cms
argeo.node.repo.type=h2
-#argeo.node.useradmin.uris=ldap://uid=admin,ou=system:secret@localhost:10389/dc=example,dc=com
-#argeo.node.useradmin.uris=ldap://uid=admin,ou=system:secret@localhost:10389\
-#/dc=example,dc=com?userBase=ou=users&groupBase=ou=groups
+#argeo.node.useradmin.uris=ldap://localhost:10389/dc=example,dc=com
#argeo.node.useradmin.uris="\
#ldap://uid=admin,ou=system:secret\
#&userObjectClass=inetOrgPerson \
#dc=example,dc=org.ldif"
-#argeo.node.useradmin.uris="dc=example,dc=com.ldif dc=example,dc=org.ldif"
-
-#sun.security.krb5.debug=true
-
# HTTP
org.osgi.service.http.port=7070
#org.eclipse.equinox.http.jetty.log.stderr.threshold=info
+++ /dev/null
-package org.argeo.cms.auth;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.User;
-
-/**
- * A special user type used during authentication in order to provide the
- * credentials required for scoping the user admin.
- */
-class AuthenticatingUser implements User {
- private final String name;
- private final Dictionary<String, Object> credentials;
-
- public AuthenticatingUser(LdapName name) {
- this.name = name.toString();
- this.credentials = new Hashtable<>();
- }
-
- public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
- this.name = name;
- this.credentials = credentials;
- }
-
- public AuthenticatingUser(String name, char[] password) {
- this.name = name;
- credentials = new Hashtable<>();
- credentials.put(CmsAuthUtils.SHARED_STATE_NAME, name);
- byte[] pwd = charsToBytes(password);
- credentials.put(CmsAuthUtils.SHARED_STATE_PWD, pwd);
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public int getType() {
- return User.USER;
- }
-
- @SuppressWarnings("rawtypes")
- @Override
- public Dictionary getProperties() {
- throw new UnsupportedOperationException();
- }
-
- @SuppressWarnings("rawtypes")
- @Override
- public Dictionary getCredentials() {
- return credentials;
- }
-
- @Override
- public boolean hasCredential(String key, Object value) {
- throw new UnsupportedOperationException();
- }
-
-
- static byte[] charsToBytes(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;
- }
-
- static char[] bytesToChars(byte[] bytes) {
- ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
- CharBuffer charBuffer = Charset.forName("UTF-8").decode(byteBuffer);
- char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
- Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
- Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
- return chars;
- }
-
-
-}
import org.argeo.node.security.AnonymousPrincipal;
import org.argeo.node.security.DataAdminPrincipal;
import org.argeo.node.security.NodeSecurityUtils;
+import org.argeo.osgi.useradmin.AuthenticatingUser;
import org.osgi.service.http.HttpContext;
import org.osgi.service.useradmin.Authorization;
class CmsAuthUtils {
- /** Shared HTTP request */
- final static String SHARED_STATE_HTTP_REQUEST = "org.argeo.cms.auth.http.request";
- /** From org.osgi.service.http.HttpContext */
- // final static String SHARED_STATE_AUTHORIZATION =
- // "org.osgi.service.useradmin.authorization";
- /** From com.sun.security.auth.module.*LoginModule */
- final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
- /** From com.sun.security.auth.module.*LoginModule */
- final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
+ // Standard
+ final static String SHARED_STATE_NAME = AuthenticatingUser.SHARED_STATE_NAME;
+ final static String SHARED_STATE_PWD = AuthenticatingUser.SHARED_STATE_PWD;
+ final static String HEADER_AUTHORIZATION = "Authorization";
+ final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+ // Argeo specific
+ final static String SHARED_STATE_HTTP_REQUEST = "org.argeo.cms.auth.http.request";
final static String SHARED_STATE_SPNEGO_TOKEN = "org.argeo.cms.auth.spnegoToken";
final static String SHARED_STATE_SPNEGO_OUT_TOKEN = "org.argeo.cms.auth.spnegoOutToken";
- final static String HEADER_AUTHORIZATION = "Authorization";
- final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
static void addAuthorization(Subject subject, Authorization authorization, Locale locale,
HttpServletRequest request) {
assert subject != null;
import org.apache.commons.logging.LogFactory;
import org.argeo.cms.CmsException;
import org.argeo.naming.LdapAttrs;
+import org.argeo.osgi.useradmin.AuthenticatingUser;
import org.argeo.osgi.useradmin.IpaUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
private CallbackHandler callbackHandler;
private Map<String, Object> sharedState = null;
- private List<String> indexedUserProperties = Arrays.asList(
- new String[] { LdapAttrs.DN, LdapAttrs.mail.name(), LdapAttrs.uid.name(), LdapAttrs.authPassword.name() });
+ private List<String> indexedUserProperties = Arrays
+ .asList(new String[] { LdapAttrs.mail.name(), LdapAttrs.uid.name(), LdapAttrs.authPassword.name() });
// private state
private BundleContext bc;
private User authenticatedUser = null;
private Locale locale;
+ private Authorization bindAuthorization = null;
+
@SuppressWarnings("unchecked")
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
throw new CredentialNotFoundException("No credentials provided");
}
- // User user = userAdmin.getUser(null, username);
User user = searchForUser(userAdmin, username);
if (user == null)
return true;// expect Kerberos
- // throw new FailedLoginException("Invalid credentials");
- if (!user.hasCredential(null, password))
+
+ // try bind first
+ try {
+ AuthenticatingUser authenticatingUser = new AuthenticatingUser(user.getName(), password);
+ bindAuthorization = userAdmin.getAuthorization(authenticatingUser);
+ // TODO check tokens as well
+ if (bindAuthorization != null)
+ return true;
+ } catch (Exception e) {
+ // silent
+ if(log.isTraceEnabled())
+ log.trace("Bind failed", e);
+ }
+
+ // works only if a connection password is provided
+ if (!user.hasCredential(null, password)) {
return false;
- // throw new FailedLoginException("Invalid credentials");
+ }
authenticatedUser = user;
return true;
}
Authorization authorization;
if (callbackHandler == null) {// anonymous
authorization = userAdmin.getAuthorization(null);
- } else {
+ } else if (bindAuthorization != null) {// bind
+ authorization = bindAuthorization;
+ } else {// Kerberos
User authenticatingUser;
Set<KerberosPrincipal> kerberosPrincipals = subject.getPrincipals(KerberosPrincipal.class);
if (kerberosPrincipals.isEmpty()) {
Set<User> collectedUsers = new HashSet<>();
// try dn
User user = null;
- try {
- user = (User) userAdmin.getRole(providedUsername);
- if (user != null)
- collectedUsers.add(user);
- } catch (Exception e) {
- // silent
- }
// try all indexes
for (String attr : indexedUserProperties) {
user = userAdmin.getUser(attr, providedUsername);
if (user != null)
collectedUsers.add(user);
}
- if (collectedUsers.size() == 1)
- return collectedUsers.iterator().next();
- else if (collectedUsers.size() > 1)
+ if (collectedUsers.size() == 1) {
+ user = collectedUsers.iterator().next();
+ return user;
+ } else if (collectedUsers.size() > 1) {
log.warn(collectedUsers.size() + " users for provided username" + providedUsername);
+ }
+ // try DN as a last resort
+ try {
+ user = (User) userAdmin.getRole(providedUsername);
+ if (user != null)
+ return user;
+ } catch (Exception e) {
+ // silent
+ }
return null;
} catch (Exception e) {
if (log.isTraceEnabled())
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.naming.LdapAttrs;
-import org.argeo.naming.LdapObjs;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
AbstractUserDirectory scopedUserAdmin = scope(user);
try {
DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
+ if (directoryUser == null)
+ throw new UserDirectoryException("No scoped user found for " + user);
LdifAuthorization authorization = new LdifAuthorization(directoryUser,
scopedUserAdmin.getAllRoles(directoryUser));
return authorization;
private UserAdmin findUserAdmin(String name) {
try {
- return findUserAdmin(new LdapName(name));
+ UserAdmin userAdmin = findUserAdmin(new LdapName(name));
+ return userAdmin;
} catch (InvalidNameException e) {
throw new UserDirectoryException("Badly formatted name " + name, e);
}
--- /dev/null
+package org.argeo.osgi.useradmin;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.ldap.LdapName;
+
+import org.osgi.service.useradmin.User;
+
+/**
+ * A special user type used during authentication in order to provide the
+ * credentials required for scoping the user admin.
+ */
+public class AuthenticatingUser implements User {
+ /** From com.sun.security.auth.module.*LoginModule */
+ public final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
+ /** From com.sun.security.auth.module.*LoginModule */
+ public final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
+
+ private final String name;
+ private final Dictionary<String, Object> credentials;
+
+ public AuthenticatingUser(LdapName name) {
+ this.name = name.toString();
+ this.credentials = new Hashtable<>();
+ }
+
+ public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
+ this.name = name;
+ this.credentials = credentials;
+ }
+
+ public AuthenticatingUser(String name, char[] password) {
+ this.name = name;
+ credentials = new Hashtable<>();
+ credentials.put(SHARED_STATE_NAME, name);
+ byte[] pwd = charsToBytes(password);
+ credentials.put(SHARED_STATE_PWD, pwd);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int getType() {
+ return User.USER;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Dictionary getProperties() {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Dictionary getCredentials() {
+ return credentials;
+ }
+
+ @Override
+ public boolean hasCredential(String key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ static byte[] charsToBytes(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;
+ }
+
+ static char[] bytesToChars(byte[] bytes) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ CharBuffer charBuffer = Charset.forName("UTF-8").decode(byteBuffer);
+ char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
+ Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+ Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+ return chars;
+ }
+
+}
package org.argeo.osgi.useradmin;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
+import java.util.Arrays;
+/** Utilities around digests, mostly those related to passwords. */
class DigestUtils {
static byte[] sha1(byte[] bytes) {
try {
}
}
+ static char[] bytesToChars(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 = 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
+ // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
+ return res;
+ }
+
+ static byte[] charsToBytes(char[] chars) {
+ CharBuffer charBuffer = CharBuffer.wrap(chars);
+ 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
+ return bytes;
+ }
+
private DigestUtils() {
}
@Override
protected AbstractUserDirectory scope(User user) {
Dictionary<String, Object> credentials = user.getCredentials();
- // FIXME use arrays
String username = (String) credentials.get(SHARED_STATE_USERNAME);
if (username == null)
username = user.getName();
- // byte[] pwd = (byte[]) credentials.get(SHARED_STATE_PASSWORD);
- // char[] password = DigestUtils.bytesToChars(pwd);
Dictionary<String, Object> properties = cloneProperties();
properties.put(Context.SECURITY_PRINCIPAL, username.toString());
- // properties.put(Context.SECURITY_CREDENTIALS, password);
- properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+ Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+ byte[] pwd = (byte[]) pwdCred;
+ if (pwd != null) {
+ char[] password = DigestUtils.bytesToChars(pwd);
+ properties.put(Context.SECURITY_CREDENTIALS, new String(password));
+ } else {
+ properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+ }
return new LdapUserAdmin(properties);
}
package org.argeo.osgi.useradmin;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
// TODO check other sources (like PKCS12)
// String pwd = new String((char[]) value);
// authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
- char[] password = toChars(value);
+ char[] password = DigestUtils.bytesToChars(value);
AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
if (authPassword != null) {
if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
/** Hash and clear the password */
private byte[] hash(char[] password) {
- byte[] hashedPassword = ("{SHA}" + Base64.getEncoder().encodeToString(DigestUtils.sha1(toBytes(password))))
- .getBytes(StandardCharsets.UTF_8);
+ byte[] hashedPassword = ("{SHA}"
+ + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
+ .getBytes(StandardCharsets.UTF_8);
// Arrays.fill(password, '\u0000');
return hashedPassword;
}
- private byte[] toBytes(char[] chars) {
- CharBuffer charBuffer = CharBuffer.wrap(chars);
- 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
- 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 = 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
- Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
- return res;
- }
-
+ // private byte[] toBytes(char[] chars) {
+ // CharBuffer charBuffer = CharBuffer.wrap(chars);
+ // 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
+ // 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 = 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
+ // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
+ // return res;
+ // }
+ //
@Override
public LdapName getDn() {
return dn;
public Object put(String key, Object value) {
if (key == null) {
// TODO persist to other sources (like PKCS12)
- char[] password = toChars(value);
+ char[] password = DigestUtils.bytesToChars(value);
byte[] hashedPassword = hash(password);
return put(LdapAttrs.userPassword.name(), hashedPassword);
}