From 50911fdcc6df5cd35e71a0a4ecddf03f98f742a2 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Thu, 10 Sep 2015 21:38:53 +0000 Subject: [PATCH] - Introduce PKI utils - Kernel login - Remove optimizations and improve LDIF/LDAP user admin git-svn-id: https://svn.argeo.org/commons/trunk@8381 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../src/org/argeo/cms/KernelHeader.java | 3 +- .../cms/internal/auth/ImpliedByPrincipal.java | 3 +- .../cms/internal/auth/KernelLoginModule.java | 91 +++++++++++++++++ .../internal/auth/UserAdminLoginModule.java | 12 ++- .../org/argeo/cms/internal/kernel/Kernel.java | 54 ++++++++++- .../cms/internal/kernel/KernelConstants.java | 1 + .../cms/internal/kernel/KernelUtils.java | 13 +++ .../internal/kernel/NodeAuthorization.java | 71 ++++++++++++++ .../cms/internal/kernel/NodeUserAdmin.java | 20 ++-- .../org/argeo/cms/internal/kernel/jaas.cfg | 6 ++ ....java => BundleContextCallbackHander.java} | 4 +- .../osgi/useradmin/AbstractLdapUserAdmin.java | 48 +++++++-- .../org/argeo/osgi/useradmin/LdapNames.java | 14 +++ .../argeo/osgi/useradmin/LdapUserAdmin.java | 66 ++++++++++--- .../osgi/useradmin/LdifAuthorization.java | 88 ++++++++++------- .../org/argeo/osgi/useradmin/LdifGroup.java | 79 +++++++++------ .../org/argeo/osgi/useradmin/LdifUser.java | 2 +- .../argeo/osgi/useradmin/LdifUserAdmin.java | 90 +++++++++++------ .../org/argeo/security/crypto/PkiUtils.java | 97 +++++++++++++++++++ 19 files changed, 623 insertions(+), 139 deletions(-) create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java rename org.argeo.security.core/src/org/argeo/osgi/auth/{BuncleContextCallbackHander.java => BundleContextCallbackHander.java} (84%) create mode 100644 org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapNames.java create mode 100644 org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java diff --git a/org.argeo.cms/src/org/argeo/cms/KernelHeader.java b/org.argeo.cms/src/org/argeo/cms/KernelHeader.java index 649fc9c04..f0e738a60 100644 --- a/org.argeo.cms/src/org/argeo/cms/KernelHeader.java +++ b/org.argeo.cms/src/org/argeo/cms/KernelHeader.java @@ -9,7 +9,8 @@ public interface KernelHeader { final static String LOGIN_CONTEXT_SINGLE_USER = "SINGLE_USER"; // RESERVED ROLES - final static String ROLES_BASEDN = "ou=roles,ou=node"; + public final static String ROLE_KERNEL = "OU=node"; + public final static String ROLES_BASEDN = "ou=roles,ou=node"; public final static String ROLE_ADMIN = "cn=admin," + ROLES_BASEDN; public final static String ROLE_GROUP_ADMIN = "cn=groupAdmin," + ROLES_BASEDN; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java index 829c17e35..6f83a9a28 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java @@ -85,6 +85,7 @@ public final class ImpliedByPrincipal implements Group { @Override public String toString() { - return name.toString() + " implied by " + causes; + // return name.toString() + " implied by " + causes; + return name.toString(); } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java new file mode 100644 index 000000000..ee36d3534 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java @@ -0,0 +1,91 @@ +package org.argeo.cms.internal.auth; + +import java.security.Principal; +import java.security.cert.CertPath; +import java.util.Map; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; +import javax.security.auth.x500.X500Principal; +import javax.security.auth.x500.X500PrivateCredential; + +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.principal.AdminPrincipal; +import org.argeo.cms.KernelHeader; + +public class KernelLoginModule implements LoginModule { + private Subject subject; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) { + this.subject = subject; + } + + @Override + public boolean login() throws LoginException { + // TODO check permission at code level + return true; + } + + @Override + public boolean commit() throws LoginException { + // Check that kernel has been logged in w/ certificate + // Name + Set names = subject.getPrincipals(X500Principal.class); + if (names.isEmpty() || names.size() > 1) + throw new LoginException("Kernel must have been named"); + X500Principal name = names.iterator().next(); + if (!KernelHeader.ROLE_KERNEL.equals(name.getName())) + throw new LoginException("Kernel must be named named " + + KernelHeader.ROLE_KERNEL); + // Private certificate + Set privateCerts = subject + .getPrivateCredentials(X500PrivateCredential.class); + X500PrivateCredential privateCert = null; + for (X500PrivateCredential pCert : privateCerts) { + if (pCert.getCertificate().getSubjectX500Principal().equals(name)) { + privateCert = pCert; + } + } + if (privateCert == null) + throw new LoginException("Kernel must have a private certificate"); + // Certificate path + Set certPaths = subject.getPublicCredentials(CertPath.class); + CertPath certPath = null; + for (CertPath cPath : certPaths) { + if (cPath.getCertificates().get(0) + .equals(privateCert.getCertificate())) { + certPath = cPath; + } + } + if (certPath == null) + throw new LoginException("Kernel must have a certificate path"); + + Set principals = subject.getPrincipals(); + // Add admin roles + + // Add data access roles + principals.add(new AdminPrincipal(SecurityConstants.ADMIN_ID)); + + return true; + } + + @Override + public boolean abort() throws LoginException { + return true; + } + + @Override + public boolean logout() throws LoginException { + // clear everything + subject.getPrincipals().clear(); + subject.getPublicCredentials().clear(); + subject.getPrivateCredentials().clear(); + return true; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/UserAdminLoginModule.java index f59851521..16a7d7265 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/UserAdminLoginModule.java @@ -40,18 +40,19 @@ public class UserAdminLoginModule implements LoginModule { private CallbackHandler callbackHandler; private boolean isAnonymous = false; - private final static LdapName ROLE_ADMIN_NAME, ROLE_USER_NAME, - ROLE_ANONYMOUS_NAME; + private final static LdapName ROLE_KERNEL_NAME, ROLE_ADMIN_NAME, + ROLE_ANONYMOUS_NAME, ROLE_USER_NAME; private final static List RESERVED_ROLES; private final static X500Principal ROLE_ANONYMOUS_PRINCIPAL; static { try { + ROLE_KERNEL_NAME = new LdapName(KernelHeader.ROLE_KERNEL); ROLE_ADMIN_NAME = new LdapName(KernelHeader.ROLE_ADMIN); ROLE_USER_NAME = new LdapName(KernelHeader.ROLE_USER); ROLE_ANONYMOUS_NAME = new LdapName(KernelHeader.ROLE_ANONYMOUS); RESERVED_ROLES = Collections.unmodifiableList(Arrays - .asList(new LdapName[] { ROLE_ANONYMOUS_NAME, - ROLE_USER_NAME, ROLE_ADMIN_NAME, + .asList(new LdapName[] { ROLE_KERNEL_NAME, ROLE_ADMIN_NAME, + ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, new LdapName(KernelHeader.ROLE_GROUP_ADMIN), new LdapName(KernelHeader.ROLE_USER_ADMIN) })); ROLE_ANONYMOUS_PRINCIPAL = new X500Principal( @@ -218,7 +219,8 @@ public class UserAdminLoginModule implements LoginModule { private void checkImpliedPrincipalName(LdapName roleName) { if (ROLE_USER_NAME.equals(roleName) - || ROLE_ANONYMOUS_NAME.equals(roleName)) + || ROLE_ANONYMOUS_NAME.equals(roleName) + || ROLE_KERNEL_NAME.equals(roleName)) throw new CmsException(roleName + " cannot be listed as role"); } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java index 67f0c3737..086975039 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java @@ -1,16 +1,27 @@ package org.argeo.cms.internal.kernel; +import java.io.File; +import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.URL; +import java.security.KeyStore; import java.security.PrivilegedAction; +import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import javax.jcr.Repository; import javax.jcr.RepositoryFactory; import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; +import javax.security.auth.x500.X500Principal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -21,6 +32,7 @@ import org.argeo.cms.KernelHeader; import org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory; import org.argeo.jcr.ArgeoJcrConstants; import org.argeo.security.core.InternalAuthentication; +import org.argeo.security.crypto.PkiUtils; import org.eclipse.equinox.http.servlet.ExtendedHttpService; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceEvent; @@ -41,6 +53,7 @@ import org.springframework.security.core.context.SecurityContextHolder; * */ final class Kernel implements ServiceListener { + private final static Log log = LogFactory.getLog(Kernel.class); private final BundleContext bundleContext = Activator.getBundleContext(); @@ -59,9 +72,27 @@ final class Kernel implements ServiceListener { KernelConstants.JAAS_CONFIG); System.setProperty("java.security.auth.login.config", url.toExternalForm()); + createKeyStoreIfNeeded(); + + CallbackHandler cbHandler = new CallbackHandler() { + + @Override + public void handle(Callback[] callbacks) throws IOException, + UnsupportedCallbackException { + // alias + ((NameCallback) callbacks[1]).setName(KernelHeader.ROLE_KERNEL); + // store pwd + ((PasswordCallback) callbacks[2]).setPassword("changeit" + .toCharArray()); + // key pwd + ((PasswordCallback) callbacks[3]).setPassword("changeit" + .toCharArray()); + } + }; try { LoginContext kernelLc = new LoginContext( - KernelHeader.LOGIN_CONTEXT_SYSTEM, kernelSubject); + KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject, + cbHandler); kernelLc.login(); } catch (LoginException e) { throw new CmsException("Cannot log in kernel", e); @@ -151,7 +182,7 @@ final class Kernel implements ServiceListener { try { LoginContext kernelLc = new LoginContext( - KernelHeader.LOGIN_CONTEXT_SYSTEM, kernelSubject); + KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject); kernelLc.logout(); } catch (LoginException e) { throw new CmsException("Cannot log in kernel", e); @@ -206,6 +237,25 @@ final class Kernel implements ServiceListener { return httpService; } + private void createKeyStoreIfNeeded() { + char[] ksPwd = "changeit".toCharArray(); + char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length); + File keyStoreFile = KernelUtils.getOsgiConfigurationFile("node.p12"); + if (!keyStoreFile.exists()) { + try { + KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd); + X509Certificate cert = PkiUtils.generateSelfSignedCertificate( + keyStore, new X500Principal(KernelHeader.ROLE_KERNEL), + keyPwd); + PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore); + + } catch (Exception e) { + throw new CmsException("Cannot create key store " + + keyStoreFile, e); + } + } + } + final private static void directorsCut(long initDuration) { // final long ms = 128l + (long) (Math.random() * 128d); long ms = initDuration / 100; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java index a9a3e7e9a..63fe750be 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java @@ -25,6 +25,7 @@ public interface KernelConstants { // Security final static String DEFAULT_SECURITY_KEY = "argeo"; final static String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg"; + final static String LOGIN_CONTEXT_KERNEL = "KERNEL"; // DAV final static String WEBDAV_CONFIG = "/org/argeo/cms/internal/kernel/webdav-config.xml"; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java index 0a9e6c53e..912d9fa99 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java @@ -2,6 +2,7 @@ package org.argeo.cms.internal.kernel; import java.io.File; import java.io.IOException; +import java.net.URI; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; @@ -25,6 +26,7 @@ import org.springframework.security.core.userdetails.UserDetails; /** Package utilities */ class KernelUtils implements KernelConstants { private final static String OSGI_INSTANCE_AREA = "osgi.instance.area"; + private final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; static Dictionary asDictionary(Properties props) { Hashtable hashtable = new Hashtable(); @@ -51,6 +53,17 @@ class KernelUtils implements KernelConstants { .getAbsoluteFile(); } + static File getOsgiConfigurationFile(String relativePath) { + try { + return new File(new URI(Activator.getBundleContext().getProperty( + OSGI_CONFIGURATION_AREA) + + relativePath)).getCanonicalFile(); + } catch (Exception e) { + throw new CmsException("Cannot get configuration file for " + + relativePath, e); + } + } + static String getFrameworkProp(String key, String def) { String value = Activator.getBundleContext().getProperty(key); if (value == null) diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java new file mode 100644 index 000000000..284606935 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java @@ -0,0 +1,71 @@ +package org.argeo.cms.internal.kernel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.osgi.service.useradmin.Authorization; + +class NodeAuthorization implements Authorization { + private final String name; + private final String displayName; + private final List systemRoles; + private final List roles; + + public NodeAuthorization(String name, String displayName, + Collection systemRoles, String[] roles) { + this.name = name; + this.displayName = displayName; + this.systemRoles = Collections.unmodifiableList(new ArrayList( + systemRoles)); + this.roles = Collections.unmodifiableList(Arrays.asList(roles)); + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean hasRole(String name) { + if (systemRoles.contains(name)) + return true; + if (roles.contains(name)) + return true; + return false; + } + + @Override + public String[] getRoles() { + int size = systemRoles.size() + roles.size(); + List res = new ArrayList(size); + res.addAll(systemRoles); + res.addAll(roles); + return res.toArray(new String[size]); + } + + @Override + public int hashCode() { + if (name == null) + return super.hashCode(); + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Authorization)) + return false; + Authorization that = (Authorization) obj; + if (name == null) + return that.getName() == null; + return name.equals(that.getName()); + } + + @Override + public String toString() { + return displayName; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java index 5459a2d13..19e52937e 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java @@ -74,15 +74,17 @@ public class NodeUserAdmin implements UserAdmin, UserAdminAggregator { @Override public Authorization getAuthorization(User user) { UserAdmin userAdmin = findUserAdmin(user.getName()); - // FIXME clarify assumptions - return userAdmin.getAuthorization(user); - // String[] roles = auth.getRoles(); - // // Gather system roles - // Set systemRoles = new HashSet(); - // for(String businessRole:roles){ - // - // } - // return null; + Authorization rawAuthorization = userAdmin.getAuthorization(user); + // gather system roles + Set systemRoles = new HashSet(); + for (String role : rawAuthorization.getRoles()) { + Authorization auth = nodeRoles.getAuthorization((User) userAdmin + .getRole(role)); + systemRoles.addAll(Arrays.asList(auth.getRoles())); + } + return new NodeAuthorization(rawAuthorization.getName(), + rawAuthorization.toString(), systemRoles, + rawAuthorization.getRoles()); } // diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg index 783d1f71f..8cd11ba44 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg @@ -20,6 +20,12 @@ SYSTEM { org.argeo.security.core.SystemLoginModule requisite; }; +KERNEL { + com.sun.security.auth.module.UnixLoginModule requisite; + com.sun.security.auth.module.KeyStoreLoginModule requisite keyStoreURL="${osgi.configuration.area}/node.p12" keyStoreType=PKCS12 keyStoreProvider=BC; + org.argeo.cms.internal.auth.KernelLoginModule requisite; +}; + OLD_SYSTEM { org.argeo.cms.internal.auth.SystemLoginModule requisite; org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite; diff --git a/org.argeo.security.core/src/org/argeo/osgi/auth/BuncleContextCallbackHander.java b/org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallbackHander.java similarity index 84% rename from org.argeo.security.core/src/org/argeo/osgi/auth/BuncleContextCallbackHander.java rename to org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallbackHander.java index 08cf37c6d..60510b5bb 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/auth/BuncleContextCallbackHander.java +++ b/org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallbackHander.java @@ -8,10 +8,10 @@ import javax.security.auth.callback.UnsupportedCallbackException; import org.osgi.framework.BundleContext; -public class BuncleContextCallbackHander implements CallbackHandler { +public class BundleContextCallbackHander implements CallbackHandler { private final BundleContext bundleContext; - public BuncleContextCallbackHander(BundleContext bundleContext) { + public BundleContextCallbackHander(BundleContext bundleContext) { this.bundleContext = bundleContext; } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractLdapUserAdmin.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractLdapUserAdmin.java index 254e5423c..8dcd6c216 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractLdapUserAdmin.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractLdapUserAdmin.java @@ -1,9 +1,13 @@ package org.argeo.osgi.useradmin; import java.net.URI; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; import org.osgi.service.useradmin.UserAdmin; public abstract class AbstractLdapUserAdmin implements UserAdmin { @@ -11,6 +15,8 @@ public abstract class AbstractLdapUserAdmin implements UserAdmin { private URI uri; private UserAdmin externalRoles; + private List indexedUserProperties = Arrays.asList(new String[] { + "uid", "mail", "cn" }); public AbstractLdapUserAdmin() { } @@ -20,8 +26,38 @@ public abstract class AbstractLdapUserAdmin implements UserAdmin { this.isReadOnly = isReadOnly; } - private List indexedUserProperties = Arrays.asList(new String[] { - "uid", "mail", "cn" }); + public void init() { + + } + + public void destroy() { + + } + + /** Returns the {@link Group}s this user is a direct member of. */ + protected abstract List getDirectGroups(User user); + + List getAllRoles(User user) { + List allRoles = new ArrayList(); + if (user != null) { + collectRoles(user, allRoles); + allRoles.add(user); + } else + collectAnonymousRoles(allRoles); + return allRoles; + } + + private void collectRoles(User user, List allRoles) { + for (Group group : getDirectGroups(user)) { + // TODO check for loops + allRoles.add(group); + collectRoles(group, allRoles); + } + } + + private void collectAnonymousRoles(List allRoles) { + // TODO gather anonymous roles + } protected URI getUri() { return uri; @@ -47,14 +83,6 @@ public abstract class AbstractLdapUserAdmin implements UserAdmin { return isReadOnly; } - public void init() { - - } - - public void destroy() { - - } - UserAdmin getExternalRoles() { return externalRoles; } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapNames.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapNames.java new file mode 100644 index 000000000..0b9c3ad7a --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapNames.java @@ -0,0 +1,14 @@ +package org.argeo.osgi.useradmin; + +/** + * Standard LDAP attributes and object classes leverages in ths implementation + * of user admin. + */ +public interface LdapNames { + public final static String LDAP_PREFIX = "ldap:"; + + // Attributes + public final static String LDAP_CN = LDAP_PREFIX + "cn"; + public final static String LDAP_UID = LDAP_PREFIX + "uid"; + public final static String LDAP_DISPLAY_NAME = LDAP_PREFIX + "displayName"; +} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java index dcb639d96..dabae718c 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.LogFactory; import org.argeo.ArgeoException; import org.osgi.framework.InvalidSyntaxException; import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Group; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; @@ -89,7 +90,7 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { Attributes attrs = initialLdapContext.getAttributes(name); LdifUser res; if (attrs.get("objectClass").contains("groupOfNames")) - res = new LdifGroup(new LdapName(name), attrs); + res = new LdifGroup(this, new LdapName(name), attrs); else if (attrs.get("objectClass").contains("inetOrgPerson")) res = new LdifUser(new LdapName(name), attrs); else @@ -105,6 +106,8 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { public Role[] getRoles(String filter) throws InvalidSyntaxException { try { String searchFilter = filter; + if (searchFilter == null) + searchFilter = "(|(objectClass=inetOrgPerson)(objectClass=groupOfNames))"; SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); @@ -116,15 +119,16 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { while (results.hasMoreElements()) { SearchResult searchResult = results.next(); Attributes attrs = searchResult.getAttributes(); - String name = searchResult.getName(); LdifUser role; if (attrs.get("objectClass").contains("groupOfNames")) - role = new LdifGroup(new LdapName(name), attrs); + role = new LdifGroup(this, toDn(searchBase, searchResult), + attrs); else if (attrs.get("objectClass").contains("inetOrgPerson")) - role = new LdifUser(new LdapName(name), attrs); + role = new LdifUser(toDn(searchBase, searchResult), attrs); else throw new ArgeoUserAdminException( - "Unsupported LDAP type for " + name); + "Unsupported LDAP type for " + + searchResult.getName()); res.add(role); } return res.toArray(new Role[res.size()]); @@ -179,8 +183,8 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { @Override public Authorization getAuthorization(User user) { LdifUser u = (LdifUser) user; - populateDirectMemberOf(u); - return new LdifAuthorization(u); + // populateDirectMemberOf(u); + return new LdifAuthorization(u, getAllRoles(u)); } private LdapName toDn(String baseDn, Binding binding) @@ -189,8 +193,38 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { + baseDn : binding.getName()); } - void populateDirectMemberOf(LdifUser user) { + // void populateDirectMemberOf(LdifUser user) { + // + // try { + // String searchFilter = "(&(objectClass=groupOfNames)(member=" + // + user.getName() + "))"; + // + // SearchControls searchControls = new SearchControls(); + // searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + // + // String searchBase = "ou=node"; + // NamingEnumeration results = initialLdapContext + // .search(searchBase, searchFilter, searchControls); + // + // // TODO synchro + // //user.directMemberOf.clear(); + // while (results.hasMoreElements()) { + // SearchResult searchResult = (SearchResult) results + // .nextElement(); + // LdifGroup group = new LdifGroup(toDn(searchBase, searchResult), + // searchResult.getAttributes()); + // populateDirectMemberOf(group); + // //user.directMemberOf.add(group); + // } + // } catch (Exception e) { + // throw new ArgeoException("Cannot populate direct members of " + // + user, e); + // } + // } + @Override + protected List getDirectGroups(User user) { + List directGroups = new ArrayList(); try { String searchFilter = "(&(objectClass=groupOfNames)(member=" + user.getName() + "))"; @@ -198,24 +232,26 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); - String searchBase = "ou=node"; + String searchBase = getGroupsSearchBase(); NamingEnumeration results = initialLdapContext .search(searchBase, searchFilter, searchControls); - // TODO synchro - user.directMemberOf.clear(); while (results.hasMoreElements()) { SearchResult searchResult = (SearchResult) results .nextElement(); - LdifGroup group = new LdifGroup(toDn(searchBase, searchResult), - searchResult.getAttributes()); - populateDirectMemberOf(group); - user.directMemberOf.add(group); + LdifGroup group = new LdifGroup(this, toDn(searchBase, + searchResult), searchResult.getAttributes()); + directGroups.add(group); } + return directGroups; } catch (Exception e) { throw new ArgeoException("Cannot populate direct members of " + user, e); } } + protected String getGroupsSearchBase() { + // TODO configure group search base + return baseDn; + } } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifAuthorization.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifAuthorization.java index 8f167c368..3600866af 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifAuthorization.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifAuthorization.java @@ -1,64 +1,80 @@ package org.argeo.osgi.useradmin; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Dictionary; import java.util.List; import org.osgi.service.useradmin.Authorization; import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; -public class LdifAuthorization implements Authorization { - private final LdifUser user; +public class LdifAuthorization implements Authorization, LdapNames { + private final String name; + private final String displayName; + private final List allRoles; - public LdifAuthorization(LdifUser user) { - this.user = user; + public LdifAuthorization(User user, List allRoles) { + if (user == null) { + this.name = null; + this.displayName = "anonymous"; + } else { + this.name = user.getName(); + Dictionary props = user.getProperties(); + Object displayName = props.get(LDAP_DISPLAY_NAME); + if (displayName == null) + displayName = props.get(LDAP_CN); + if (displayName == null) + displayName = props.get(LDAP_UID); + if (displayName == null) + displayName = user.getName(); + if (displayName == null) + throw new ArgeoUserAdminException( + "Cannot set display name for " + user); + this.displayName = displayName.toString(); + } + // roles + String[] roles = new String[allRoles.size()]; + for (int i = 0; i < allRoles.size(); i++) { + roles[i] = allRoles.get(i).getName(); + } + this.allRoles = Collections.unmodifiableList(Arrays.asList(roles)); } @Override public String getName() { - if (user == null) - return null; - return user.getName(); + return name; } @Override public boolean hasRole(String name) { - for (Role role : getAllRoles()) { - if (role.getName().equals(name)) - return true; - } - return false; + return allRoles.contains(name); } @Override public String[] getRoles() { - List allRoles = getAllRoles(); - if (user != null) - allRoles.add(0, user); - String[] res = new String[allRoles.size()]; - for (int i = 0; i < allRoles.size(); i++) - res[i] = allRoles.get(i).getName(); - return res; + return allRoles.toArray(new String[allRoles.size()]); } - List getAllRoles() { - List allRoles = new ArrayList(); - if (user != null) - collectRoles(user, allRoles); - else - collectAnonymousRoles(allRoles); - return allRoles; + @Override + public int hashCode() { + if (name == null) + return super.hashCode(); + return name.hashCode(); } - private void collectRoles(LdifUser user, List allRoles) { - for (LdifGroup group : user.directMemberOf) { - // TODO check for loops - allRoles.add(group); - collectRoles(group, allRoles); - } + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Authorization)) + return false; + Authorization that = (Authorization) obj; + if (name == null) + return that.getName() == null; + return name.equals(that.getName()); } - private void collectAnonymousRoles(List allRoles) { - // TODO gather anonymous roles + @Override + public String toString() { + return displayName; } - } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifGroup.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifGroup.java index fbc678c52..a19052425 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifGroup.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifGroup.java @@ -10,15 +10,18 @@ import javax.naming.ldap.LdapName; import org.osgi.service.useradmin.Group; import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdmin; public class LdifGroup extends LdifUser implements Group { // optimisation - List directMembers = null; + // List directMembers = null; + private final UserAdmin userAdmin; private String memberAttrName = "member"; - public LdifGroup(LdapName dn, Attributes attributes) { + public LdifGroup(UserAdmin userAdmin, LdapName dn, Attributes attributes) { super(dn, attributes); + this.userAdmin = userAdmin; } @Override @@ -29,9 +32,9 @@ public class LdifGroup extends LdifUser implements Group { return false; } else getAttributes().put(memberAttrName, role.getName()); - directMembers.add(role); - if (role instanceof LdifUser) - ((LdifUser) role).directMemberOf.add(this); + // directMembers.add(role); + // if (role instanceof LdifUser) + // ((LdifUser) role).directMemberOf.add(this); return true; } @@ -47,9 +50,9 @@ public class LdifGroup extends LdifUser implements Group { if (!member.contains(role.getName())) return false; member.remove(role.getName()); - directMembers.remove(role); - if (role instanceof LdifUser) - ((LdifUser) role).directMemberOf.remove(this); + // directMembers.remove(role); + // if (role instanceof LdifUser) + // ((LdifUser) role).directMemberOf.remove(this); return true; } else return false; @@ -57,10 +60,29 @@ public class LdifGroup extends LdifUser implements Group { @Override public Role[] getMembers() { - if (directMembers != null) - return directMembers.toArray(new Role[directMembers.size()]); - else - throw new ArgeoUserAdminException("Members have not been loaded."); + List directMembers = new ArrayList(); + for (LdapName ldapName : getMemberNames()) { + Role role = userAdmin.getRole(ldapName.toString()); + if (role == null && userAdmin instanceof AbstractLdapUserAdmin) { + AbstractLdapUserAdmin ua = (AbstractLdapUserAdmin) userAdmin; + if (ua.getExternalRoles() != null) + role = ua.getExternalRoles().getRole(ldapName.toString()); + } + if (role == null) + throw new ArgeoUserAdminException("No role found for " + + ldapName); + + // role.directMemberOf.add(group); + // if (!directMemberOf.containsKey(role.getDn())) + // directMemberOf.put(role.getDn(), new ArrayList()); + // directMemberOf.get(role.getDn()).add(group); + directMembers.add(role); + } + return directMembers.toArray(new Role[directMembers.size()]); + // if (directMembers != null) + // return directMembers.toArray(new Role[directMembers.size()]); + // else + // throw new ArgeoUserAdminException("Members have not been loaded."); // Attribute memberAttribute = getAttributes().get(memberAttrName); // if (memberAttribute == null) @@ -78,21 +100,21 @@ public class LdifGroup extends LdifUser implements Group { // } } -// void loadMembers(LdifUserAdmin userAdmin) { -// directMembers = new ArrayList(); -// for (LdapName ldapName : getMemberNames()) { -// LdifUser role; -// if (userAdmin.groups.containsKey(ldapName)) -// role = userAdmin.groups.get(ldapName); -// else if (userAdmin.users.containsKey(ldapName)) -// role = userAdmin.users.get(ldapName); -// else -// throw new ArgeoUserAdminException("No role found for " -// + ldapName); -// role.directMemberOf.add(this); -// directMembers.add(role); -// } -// } + // void loadMembers(LdifUserAdmin userAdmin) { + // directMembers = new ArrayList(); + // for (LdapName ldapName : getMemberNames()) { + // LdifUser role; + // if (userAdmin.groups.containsKey(ldapName)) + // role = userAdmin.groups.get(ldapName); + // else if (userAdmin.users.containsKey(ldapName)) + // role = userAdmin.users.get(ldapName); + // else + // throw new ArgeoUserAdminException("No role found for " + // + ldapName); + // role.directMemberOf.add(this); + // directMembers.add(role); + // } + // } List getMemberNames() { Attribute memberAttribute = getAttributes().get(memberAttrName); @@ -124,6 +146,5 @@ public class LdifGroup extends LdifUser implements Group { public String getMemberAttrName() { return memberAttrName; } - - + } 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 9f378f151..85d18c082 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 @@ -12,7 +12,7 @@ import org.osgi.service.useradmin.User; class LdifUser implements User { // optimisation - List directMemberOf = new ArrayList(); + //List directMemberOf = new ArrayList(); private final LdapName dn; private Attributes attributes; diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java index c96f2de66..c5ca49300 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java @@ -37,6 +37,9 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { private Map> userIndexes = new LinkedHashMap>(); + // private Map> directMemberOf = new + // TreeMap>(); + public LdifUserAdmin(String uri) { this(uri, true); } @@ -106,15 +109,15 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { users.put(key, new LdifUser(key, attributes)); break objectClasses; } else if (objectClass.equals("groupOfNames")) { - groups.put(key, new LdifGroup(key, attributes)); + groups.put(key, new LdifGroup(this, key, attributes)); break objectClasses; } } } // optimise - for (LdifGroup group : groups.values()) - loadMembers(group); +// for (LdifGroup group : groups.values()) +// loadMembers(group); // indexes for (String attr : getIndexedUserProperties()) @@ -168,7 +171,8 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { @Override public Authorization getAuthorization(User user) { - return new LdifAuthorization((LdifUser) user); + return new LdifAuthorization((LdifUser) user, + getAllRoles((LdifUser) user)); } @Override @@ -188,7 +192,7 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { newRole = new LdifUser(dn, attrs); users.put(dn, newRole); } else if (type == Role.GROUP) { - newRole = new LdifGroup(dn, attrs); + newRole = new LdifGroup(this, dn, attrs); groups.put(dn, (LdifGroup) newRole); } else throw new ArgeoUserAdminException("Unsupported type " + type); @@ -211,17 +215,18 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { throw new ArgeoUserAdminException("There is no role " + name); if (role == null) return false; - for (LdifGroup group : role.directMemberOf) { - group.directMembers.remove(role); + for (LdifGroup group : getDirectGroups(role)) { +// group.directMembers.remove(role); group.getAttributes().get(group.getMemberAttrName()) .remove(dn.toString()); } if (role instanceof LdifGroup) { LdifGroup group = (LdifGroup) role; - for (Role user : group.directMembers) { - if (user instanceof LdifUser) - ((LdifUser) user).directMemberOf.remove(group); - } + // for (Role user : group.directMembers) { + // if (user instanceof LdifUser) + // directMemberOf.get(((LdifUser) user).getDn()).remove( + // group); + // } } return true; } catch (InvalidNameException e) { @@ -280,25 +285,54 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { // throw new UnsupportedOperationException(); } - protected void loadMembers(LdifGroup group) { - group.directMembers = new ArrayList(); - for (LdapName ldapName : group.getMemberNames()) { - LdifUser role = null; - if (groups.containsKey(ldapName)) - role = groups.get(ldapName); - else if (users.containsKey(ldapName)) - role = users.get(ldapName); - else { - if (getExternalRoles() != null) - role = (LdifUser) getExternalRoles().getRole( - ldapName.toString()); - if (role == null) - throw new ArgeoUserAdminException("No role found for " - + ldapName); +// protected void loadMembers(LdifGroup group) { +// group.directMembers = new ArrayList(); +// for (LdapName ldapName : group.getMemberNames()) { +// LdifUser role = null; +// if (groups.containsKey(ldapName)) +// role = groups.get(ldapName); +// else if (users.containsKey(ldapName)) +// role = users.get(ldapName); +// else { +// if (getExternalRoles() != null) +// role = (LdifUser) getExternalRoles().getRole( +// ldapName.toString()); +// if (role == null) +// throw new ArgeoUserAdminException("No role found for " +// + ldapName); +// } +// // role.directMemberOf.add(group); +// // if (!directMemberOf.containsKey(role.getDn())) +// // directMemberOf.put(role.getDn(), new ArrayList()); +// // directMemberOf.get(role.getDn()).add(group); +// group.directMembers.add(role); +// } +// } + + @Override + protected List getDirectGroups(User user) { + LdapName dn; + if (user instanceof LdifUser) + dn = ((LdifUser) user).getDn(); + else + try { + dn = new LdapName(user.getName()); + } catch (InvalidNameException e) { + throw new ArgeoUserAdminException("Badly formatted user name " + + user.getName(), e); } - role.directMemberOf.add(group); - group.directMembers.add(role); + + List directGroups = new ArrayList(); + for (LdapName name : groups.keySet()) { + LdifGroup group = groups.get(name); + if (group.getMemberNames().contains(dn)) + directGroups.add(group); } + return directGroups; + // if (directMemberOf.containsKey(dn)) + // return Collections.unmodifiableList(directMemberOf.get(dn)); + // else + // return Collections.EMPTY_LIST; } } diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java b/org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java new file mode 100644 index 000000000..ed6640f36 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java @@ -0,0 +1,97 @@ +package org.argeo.security.crypto; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +import org.argeo.ArgeoException; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +/** + * Utilities around private keys and certificate, mostly wrapping BouncyCastle + * implementations. + */ +public class PkiUtils { + private final static String SECURITY_PROVIDER; + static { + Security.addProvider(new BouncyCastleProvider()); + SECURITY_PROVIDER = "BC"; + } + + public static X509Certificate generateSelfSignedCertificate( + KeyStore keyStore, X500Principal x500Principal, char[] keyPassword) { + try { + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", + SECURITY_PROVIDER); + kpGen.initialize(1024, new SecureRandom()); + KeyPair pair = kpGen.generateKeyPair(); + Date notBefore = new Date(System.currentTimeMillis() - 10000); + Date notAfter = new Date( + System.currentTimeMillis() + 24L * 3600 * 1000); + BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); + X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder( + x500Principal, serial, notBefore, notAfter, x500Principal, + pair.getPublic()); + ContentSigner sigGen = new JcaContentSignerBuilder( + "SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER) + .build(pair.getPrivate()); + X509Certificate cert = new JcaX509CertificateConverter() + .setProvider(SECURITY_PROVIDER).getCertificate( + certGen.build(sigGen)); + cert.checkValidity(new Date()); + cert.verify(cert.getPublicKey()); + + keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), + keyPassword, new Certificate[] { cert }); + return cert; + } catch (Exception e) { + throw new ArgeoException("Cannot generate self-signed certificate", + e); + } + } + + public static KeyStore getKeyStore(File keyStoreFile, + char[] keyStorePassword) { + try { + KeyStore store = KeyStore.getInstance("PKCS12", SECURITY_PROVIDER); + if (keyStoreFile.exists()) { + try (FileInputStream fis = new FileInputStream(keyStoreFile)) { + store.load(fis, keyStorePassword); + } + } else { + store.load(null); + } + return store; + } catch (Exception e) { + throw new ArgeoException("Cannot load keystore " + keyStoreFile, e); + } + } + + public static void saveKeyStore(File keyStoreFile, char[] keyStorePassword, + KeyStore keyStore) { + try { + try (FileOutputStream fis = new FileOutputStream(keyStoreFile)) { + keyStore.store(fis, keyStorePassword); + } + } catch (Exception e) { + throw new ArgeoException("Cannot save keystore " + keyStoreFile, e); + } + } + +} -- 2.30.2