From 25071ab6bcb2df1fa4057c2c04137f2d606772e7 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sat, 12 Sep 2015 11:25:51 +0000 Subject: [PATCH] LDIF user admin persistence based on transactions. git-svn-id: https://svn.argeo.org/commons/trunk@8388 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- org.argeo.cms/bnd.bnd | 1 + .../src/org/argeo/cms/KernelHeader.java | 2 + .../cms/internal/auth/KernelLoginModule.java | 2 +- .../org/argeo/cms/internal/kernel/Kernel.java | 183 +++++++------ .../cms/internal/kernel/NodeSecurity.java | 254 +++++++++++++----- .../cms/internal/kernel/NodeUserAdmin.java | 58 +++- .../org/argeo/cms/internal/kernel/jaas.cfg | 2 +- .../transaction/SimpleTransaction.java | 121 +++++++++ .../transaction/SimpleTransactionManager.java | 188 +++++++++++++ .../cms/internal/transaction/UuidXid.java | 132 +++++++++ .../osgi/useradmin/AbstractLdapUserAdmin.java | 94 ------- .../osgi/useradmin/AbstractUserDirectory.java | 162 +++++++++++ .../osgi/useradmin/AttributeDictionary.java | 137 ---------- .../argeo/osgi/useradmin/DirectoryGroup.java | 11 + .../argeo/osgi/useradmin/DirectoryUser.java | 12 + .../org/argeo/osgi/useradmin/EditorRole.java | 9 + .../org/argeo/osgi/useradmin/LdapNames.java | 4 +- .../argeo/osgi/useradmin/LdapUserAdmin.java | 21 +- .../osgi/useradmin/LdifAuthorization.java | 2 +- .../org/argeo/osgi/useradmin/LdifGroup.java | 93 ++----- .../org/argeo/osgi/useradmin/LdifUser.java | 205 ++++++++++++-- .../argeo/osgi/useradmin/LdifUserAdmin.java | 185 +++++++++---- .../org/argeo/osgi/useradmin/LdifWriter.java | 2 +- .../osgi/useradmin/UserAdminWorkingCopy.java | 14 + ...ption.java => UserDirectoryException.java} | 6 +- .../useradmin/UserDirectoryTransaction.java | 26 ++ .../useradmin/cm/LdapUserAdminFactory.java | 8 +- .../org/argeo/security/crypto/PkiUtils.java | 4 +- 28 files changed, 1376 insertions(+), 562 deletions(-) create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransaction.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/transaction/UuidXid.java delete mode 100644 org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractLdapUserAdmin.java create mode 100644 org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java delete mode 100644 org.argeo.security.core/src/org/argeo/osgi/useradmin/AttributeDictionary.java create mode 100644 org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryGroup.java create mode 100644 org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryUser.java create mode 100644 org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java create mode 100644 org.argeo.security.core/src/org/argeo/osgi/useradmin/UserAdminWorkingCopy.java rename org.argeo.security.core/src/org/argeo/osgi/useradmin/{ArgeoUserAdminException.java => UserDirectoryException.java} (62%) create mode 100644 org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryTransaction.java diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index 1034b39fa..6eecc6b85 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -21,5 +21,6 @@ org.apache.commons.vfs2.*;resolution:=optional,\ org.apache.jackrabbit.*;resolution:=optional,\ org.springframework.ldap.*;resolution:=optional,\ org.springframework.security.ldap.*;resolution:=optional,\ +org.springframework.security.provisioning;resolution:=optional,\ org.joda.time.*;resolution:=optional,\ * diff --git a/org.argeo.cms/src/org/argeo/cms/KernelHeader.java b/org.argeo.cms/src/org/argeo/cms/KernelHeader.java index f0e738a60..8f817df79 100644 --- a/org.argeo.cms/src/org/argeo/cms/KernelHeader.java +++ b/org.argeo.cms/src/org/argeo/cms/KernelHeader.java @@ -2,6 +2,8 @@ package org.argeo.cms; /** Public properties of the CMS Kernel */ public interface KernelHeader { + final static String SECURITY_PROVIDER = "BC";// Bouncy Castle + // LOGIN CONTEXTS final static String LOGIN_CONTEXT_USER = "USER"; final static String LOGIN_CONTEXT_ANONYMOUS = "ANONYMOUS"; 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 index ee36d3534..f96bc8880 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java @@ -27,7 +27,7 @@ public class KernelLoginModule implements LoginModule { @Override public boolean login() throws LoginException { - // TODO check permission at code level + // TODO check permission at code level ? return true; } 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 086975039..6246a1b1b 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,38 +1,26 @@ 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 javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.UserTransaction; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.util.TransientFileFactory; import org.argeo.ArgeoException; import org.argeo.cms.CmsException; -import org.argeo.cms.KernelHeader; +import org.argeo.cms.internal.transaction.SimpleTransactionManager; 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; @@ -53,62 +41,34 @@ 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(); + private final NodeSecurity nodeSecurity; ThreadGroup threadGroup = new ThreadGroup(Kernel.class.getSimpleName()); JackrabbitNode node; - OsgiJackrabbitRepositoryFactory repositoryFactory; - NodeSecurity nodeSecurity; - NodeHttp nodeHttp; - private KernelThread kernelThread; - private final Subject kernelSubject = new Subject(); + private SimpleTransactionManager transactionManager; + private OsgiJackrabbitRepositoryFactory repositoryFactory; + private NodeHttp nodeHttp; + private KernelThread kernelThread; public Kernel() { - URL url = getClass().getClassLoader().getResource( - 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( - KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject, - cbHandler); - kernelLc.login(); - } catch (LoginException e) { - throw new CmsException("Cannot log in kernel", e); - } + nodeSecurity = new NodeSecurity(bundleContext); } final void init() { - Subject.doAs(kernelSubject, new PrivilegedAction() { + Subject.doAs(nodeSecurity.getKernelSubject(), + new PrivilegedAction() { - @Override - public Void run() { - doInit(); - return null; - } + @Override + public Void run() { + doInit(); + return null; + } - }); + }); } private void doInit() { @@ -122,6 +82,17 @@ final class Kernel implements ServiceListener { SecurityContextHolder.getContext().setAuthentication(initAuth); try { + // Transaction + transactionManager = new SimpleTransactionManager(); + bundleContext.registerService(TransactionManager.class, + transactionManager, null); + bundleContext.registerService(UserTransaction.class, + transactionManager, null); + bundleContext.registerService( + TransactionSynchronizationRegistry.class, + transactionManager.getTransactionSynchronizationRegistry(), + null); + // Jackrabbit node node = new JackrabbitNode(bundleContext); @@ -129,7 +100,10 @@ final class Kernel implements ServiceListener { repositoryFactory = new OsgiJackrabbitRepositoryFactory(); // Authentication - nodeSecurity = new NodeSecurity(bundleContext, node); + nodeSecurity.getUserAdmin().setSyncRegistry( + transactionManager.getTransactionSynchronizationRegistry()); + nodeSecurity.getUserAdmin().setTransactionManager( + transactionManager); // Equinox dependency ExtendedHttpService httpService = waitForHttpService(); @@ -170,8 +144,8 @@ final class Kernel implements ServiceListener { if (nodeHttp != null) nodeHttp.destroy(); - if (nodeSecurity != null) - nodeSecurity.destroy(); + // if (nodeSecurity != null) + // nodeSecurity.destroy(); if (node != null) node.destroy(); @@ -180,14 +154,10 @@ final class Kernel implements ServiceListener { // Clean hanging threads from Jackrabbit TransientFileFactory.shutdown(); - try { - LoginContext kernelLc = new LoginContext( - KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject); - kernelLc.logout(); - } catch (LoginException e) { - throw new CmsException("Cannot log in kernel", e); - } + // Clean hanging Gogo shell thread + new GogoShellKiller().start(); + nodeSecurity.destroy(); long duration = System.currentTimeMillis() - begin; log.info("## ARGEO CMS DOWN in " + (duration / 1000) + "." + (duration % 1000) + "s ##"); @@ -237,25 +207,6 @@ 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; @@ -276,4 +227,62 @@ final class Kernel implements ServiceListener { + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %"); } + + /** Workaround for blocking Gogo shell by system shutdown. */ + private class GogoShellKiller extends Thread { + + public GogoShellKiller() { + super("Gogo shell killer"); + setDaemon(true); + } + + @Override + public void run() { + ThreadGroup rootTg = getRootThreadGroup(null); + Thread gogoShellThread = findGogoShellThread(rootTg); + if (gogoShellThread == null) + return; + while (getNonDaemonCount(rootTg) > 2) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // silent + } + } + gogoShellThread = findGogoShellThread(rootTg); + if (gogoShellThread == null) + return; + System.exit(0); + } + } + + private static ThreadGroup getRootThreadGroup(ThreadGroup tg) { + if (tg == null) + tg = Thread.currentThread().getThreadGroup(); + if (tg.getParent() == null) + return tg; + else + return getRootThreadGroup(tg.getParent()); + } + + private static int getNonDaemonCount(ThreadGroup rootThreadGroup) { + Thread[] threads = new Thread[rootThreadGroup.activeCount()]; + rootThreadGroup.enumerate(threads); + int nonDameonCount = 0; + for (Thread t : threads) + if (!t.isDaemon()) + nonDameonCount++; + return nonDameonCount; + } + + private static Thread findGogoShellThread(ThreadGroup rootThreadGroup) { + Thread[] threads = new Thread[rootThreadGroup.activeCount()]; + rootThreadGroup.enumerate(threads, true); + for (Thread thread : threads) { + if (thread.getName().equals("Gogo shell")) + return thread; + } + return null; + } + } \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java index 13ecac4b0..f2cffb3ad 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java @@ -2,79 +2,119 @@ package org.argeo.cms.internal.kernel; import java.io.File; import java.io.IOException; +import java.net.URL; +import java.nio.file.ProviderNotFoundException; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; -import javax.jcr.RepositoryException; +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.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsException; import org.argeo.cms.KernelHeader; -import org.argeo.cms.internal.useradmin.SimpleJcrSecurityModel; -import org.argeo.cms.internal.useradmin.jackrabbit.JackrabbitUserAdminService; -import org.argeo.osgi.useradmin.AbstractLdapUserAdmin; +import org.argeo.osgi.useradmin.AbstractUserDirectory; import org.argeo.osgi.useradmin.LdapUserAdmin; import org.argeo.osgi.useradmin.LdifUserAdmin; -import org.argeo.security.OsAuthenticationToken; -import org.argeo.security.UserAdminService; -import org.argeo.security.core.InternalAuthentication; -import org.argeo.security.core.InternalAuthenticationProvider; -import org.argeo.security.core.OsAuthenticationProvider; +import org.argeo.security.crypto.PkiUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; -import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.UserAdmin; -import org.springframework.security.authentication.AnonymousAuthenticationProvider; -import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.provisioning.UserDetailsManager; /** Authentication and user management. */ class NodeSecurity implements AuthenticationManager { - private final static Log log = LogFactory.getLog(NodeSecurity.class); + private final static Log log; + static { + log = LogFactory.getLog(NodeSecurity.class); + // Make Bouncy Castle the default provider + Provider provider = new BouncyCastleProvider(); + int position = Security.insertProviderAt(provider, 1); + if (position == -1) + log.error("Provider " + provider.getName() + + " already installed and could not be set as default"); + Provider defaultProvider = Security.getProviders()[0]; + if (!defaultProvider.getName().equals(KernelHeader.SECURITY_PROVIDER)) + log.error("Provider name is " + defaultProvider.getName() + + " but it should be " + KernelHeader.SECURITY_PROVIDER); + } private final BundleContext bundleContext; - - private final OsAuthenticationProvider osAuth; - private final InternalAuthenticationProvider internalAuth; - private final AnonymousAuthenticationProvider anonymousAuth; - private final JackrabbitUserAdminService userAdminService; private final NodeUserAdmin userAdmin; + private final Subject kernelSubject; + + // private final OsAuthenticationProvider osAuth; + // private final InternalAuthenticationProvider internalAuth; + // private final AnonymousAuthenticationProvider anonymousAuth; + // private final JackrabbitUserAdminService userAdminService; private ServiceRegistration authenticationManagerReg; - private ServiceRegistration userAdminServiceReg; - private ServiceRegistration userDetailsManagerReg; + // private ServiceRegistration userAdminServiceReg; + // private ServiceRegistration userDetailsManagerReg; private ServiceRegistration userAdminReg; - public NodeSecurity(BundleContext bundleContext, JackrabbitNode node) - throws RepositoryException { + public NodeSecurity(BundleContext bundleContext) { + // Configure JAAS first + URL url = getClass().getClassLoader().getResource( + KernelConstants.JAAS_CONFIG); + System.setProperty("java.security.auth.login.config", + url.toExternalForm()); + this.bundleContext = bundleContext; + this.kernelSubject = logKernel(); - osAuth = new OsAuthenticationProvider(); - internalAuth = new InternalAuthenticationProvider( - Activator.getSystemKey()); - anonymousAuth = new AnonymousAuthenticationProvider( - Activator.getSystemKey()); + // osAuth = new OsAuthenticationProvider(); + // internalAuth = new InternalAuthenticationProvider( + // Activator.getSystemKey()); + // anonymousAuth = new AnonymousAuthenticationProvider( + // Activator.getSystemKey()); // user admin - userAdminService = new JackrabbitUserAdminService(); - userAdminService.setRepository(node); - userAdminService.setSecurityModel(new SimpleJcrSecurityModel()); - userAdminService.init(); + // userAdminService = new JackrabbitUserAdminService(); + // userAdminService.setRepository(node); + // userAdminService.setSecurityModel(new SimpleJcrSecurityModel()); + // userAdminService.init(); userAdmin = new NodeUserAdmin(); - String baseDn = "dc=example,dc=com"; + File osgiInstanceDir = KernelUtils.getOsgiInstanceDir(); + File homeDir = new File(osgiInstanceDir, "node"); + homeDir.mkdirs(); + String userAdminUri = KernelUtils .getFrameworkProp(KernelConstants.USERADMIN_URI); - if (userAdminUri == null) - userAdminUri = getClass().getResource(baseDn + ".ldif").toString(); + String baseDn = "dc=example,dc=com"; + if (userAdminUri == null) { + File businessRolesFile = new File(homeDir, baseDn + ".ldif"); + // userAdminUri = getClass().getResource(baseDn + + // ".ldif").toString(); + if (!businessRolesFile.exists()) + try { + FileUtils.copyInputStreamToFile(getClass() + .getResourceAsStream(baseDn + ".ldif"), + businessRolesFile); + } catch (IOException e) { + throw new CmsException("Cannot copy demo resource", e); + } + userAdminUri = businessRolesFile.toURI().toString(); + } - AbstractLdapUserAdmin businessRoles; + AbstractUserDirectory businessRoles; if (userAdminUri.startsWith("ldap")) businessRoles = new LdapUserAdmin(userAdminUri); else { @@ -83,19 +123,18 @@ class NodeSecurity implements AuthenticationManager { businessRoles.init(); userAdmin.addUserAdmin(baseDn, businessRoles); - File osgiInstanceDir = KernelUtils.getOsgiInstanceDir(); - File homeDir = new File(osgiInstanceDir, "node"); - String baseNodeRoleDn = KernelHeader.ROLES_BASEDN; File nodeRolesFile = new File(homeDir, baseNodeRoleDn + ".ldif"); - try { - FileUtils.copyInputStreamToFile( - getClass().getResourceAsStream("demo.ldif"), nodeRolesFile); - } catch (IOException e) { - throw new CmsException("Cannot copy demo resource", e); - } + if (!nodeRolesFile.exists()) + try { + FileUtils.copyInputStreamToFile( + getClass().getResourceAsStream("demo.ldif"), + nodeRolesFile); + } catch (IOException e) { + throw new CmsException("Cannot copy demo resource", e); + } LdifUserAdmin nodeRoles = new LdifUserAdmin(nodeRolesFile.toURI() - .toString()); + .toString(), false); nodeRoles.setExternalRoles(userAdmin); nodeRoles.init(); // nodeRoles.createRole(KernelHeader.ROLE_ADMIN, Role.GROUP); @@ -103,47 +142,118 @@ class NodeSecurity implements AuthenticationManager { } + private Subject logKernel() { + final Subject kernelSubject = new Subject(); + 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( + KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject, + cbHandler); + kernelLc.login(); + } catch (LoginException e) { + throw new CmsException("Cannot log in kernel", e); + } + return kernelSubject; + } + public void publish() { authenticationManagerReg = bundleContext.registerService( AuthenticationManager.class, this, null); - userAdminServiceReg = bundleContext.registerService( - UserAdminService.class, userAdminService, null); - userDetailsManagerReg = bundleContext.registerService( - UserDetailsManager.class, userAdminService, null); + // userAdminServiceReg = bundleContext.registerService( + // UserAdminService.class, userAdminService, null); + // userDetailsManagerReg = bundleContext.registerService( + // UserDetailsManager.class, userAdminService, null); userAdminReg = bundleContext.registerService(UserAdmin.class, userAdmin, null); } void destroy() { - try { - userAdminService.destroy(); - } catch (RepositoryException e) { - log.error("Error while destroying Jackrabbit useradmin"); - } - userDetailsManagerReg.unregister(); - userAdminServiceReg.unregister(); + // try { + // userAdminService.destroy(); + // } catch (RepositoryException e) { + // log.error("Error while destroying Jackrabbit useradmin"); + // } + // userDetailsManagerReg.unregister(); + // userAdminServiceReg.unregister(); authenticationManagerReg.unregister(); // userAdmin.destroy(); userAdminReg.unregister(); + + // Logout kernel + try { + LoginContext kernelLc = new LoginContext( + KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject); + kernelLc.logout(); + } catch (LoginException e) { + throw new CmsException("Cannot log in kernel", e); + } + + Security.removeProvider(KernelHeader.SECURITY_PROVIDER); + } + + public NodeUserAdmin getUserAdmin() { + return userAdmin; + } + + public Subject getKernelSubject() { + return kernelSubject; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { -// throw new UnsupportedOperationException( -// "Authentication manager is deprectaed and should not be used."); - Authentication auth = null; - if (authentication instanceof InternalAuthentication) - auth = internalAuth.authenticate(authentication); - else if (authentication instanceof AnonymousAuthenticationToken) - auth = anonymousAuth.authenticate(authentication); - else if (authentication instanceof UsernamePasswordAuthenticationToken) - auth = userAdminService.authenticate(authentication); - else if (authentication instanceof OsAuthenticationToken) - auth = osAuth.authenticate(authentication); - if (auth == null) - throw new CmsException("Could not authenticate " + authentication); - return auth; + log.error("Authentication manager is deprectaed and should not be used."); + // Authentication auth = null; + // if (authentication instanceof InternalAuthentication) + // auth = internalAuth.authenticate(authentication); + // else if (authentication instanceof AnonymousAuthenticationToken) + // auth = anonymousAuth.authenticate(authentication); + // else if (authentication instanceof + // UsernamePasswordAuthenticationToken) + // auth = userAdminService.authenticate(authentication); + // else if (authentication instanceof OsAuthenticationToken) + // auth = osAuth.authenticate(authentication); + // if (auth == null) + // throw new CmsException("Could not authenticate " + authentication); + throw new ProviderNotFoundException( + "Authentication manager is deprectaed and should not be used."); } + + private void createKeyStoreIfNeeded() { + char[] ksPwd = "changeit".toCharArray(); + char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length); + File keyStoreFile = new File(KernelUtils.getOsgiInstanceDir(), + "node.p12"); + if (!keyStoreFile.exists()) { + try { + keyStoreFile.getParentFile().mkdirs(); + KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd); + 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); + } + } + } + } 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 19e52937e..9dd516137 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 @@ -10,10 +10,14 @@ import java.util.Set; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; import org.argeo.cms.KernelHeader; -import org.argeo.osgi.useradmin.ArgeoUserAdminException; +import org.argeo.osgi.useradmin.AbstractUserDirectory; import org.argeo.osgi.useradmin.UserAdminAggregator; +import org.argeo.osgi.useradmin.UserDirectoryException; import org.osgi.framework.InvalidSyntaxException; import org.osgi.service.useradmin.Authorization; import org.osgi.service.useradmin.Role; @@ -26,7 +30,7 @@ public class NodeUserAdmin implements UserAdmin, UserAdminAggregator { try { ROLES_BASE = new LdapName(KernelHeader.ROLES_BASEDN); } catch (InvalidNameException e) { - throw new ArgeoUserAdminException("Cannot initialize " + throw new UserDirectoryException("Cannot initialize " + NodeUserAdmin.class, e); } } @@ -34,6 +38,9 @@ public class NodeUserAdmin implements UserAdmin, UserAdminAggregator { private UserAdmin nodeRoles = null; private Map userAdmins = new HashMap(); + private TransactionSynchronizationRegistry syncRegistry; + private TransactionManager transactionManager; + @Override public Role createRole(String name, int type) { return findUserAdmin(name).createRole(name, type); @@ -92,18 +99,21 @@ public class NodeUserAdmin implements UserAdmin, UserAdminAggregator { // @Override public synchronized void addUserAdmin(String baseDn, UserAdmin userAdmin) { + if (userAdmin instanceof AbstractUserDirectory) + ((AbstractUserDirectory) userAdmin).setSyncRegistry(syncRegistry); + if (baseDn.equals(KernelHeader.ROLES_BASEDN)) { nodeRoles = userAdmin; return; } if (userAdmins.containsKey(baseDn)) - throw new ArgeoUserAdminException( + throw new UserDirectoryException( "There is already a user admin for " + baseDn); try { userAdmins.put(new LdapName(baseDn), userAdmin); } catch (InvalidNameException e) { - throw new ArgeoUserAdminException("Badly formatted base DN " + throw new UserDirectoryException("Badly formatted base DN " + baseDn, e); } } @@ -111,25 +121,27 @@ public class NodeUserAdmin implements UserAdmin, UserAdminAggregator { @Override public synchronized void removeUserAdmin(String baseDn) { if (baseDn.equals(KernelHeader.ROLES_BASEDN)) - throw new ArgeoUserAdminException("Node roles cannot be removed."); + throw new UserDirectoryException("Node roles cannot be removed."); LdapName base; try { base = new LdapName(baseDn); } catch (InvalidNameException e) { - throw new ArgeoUserAdminException("Badly formatted base DN " + throw new UserDirectoryException("Badly formatted base DN " + baseDn, e); } if (!userAdmins.containsKey(base)) - throw new ArgeoUserAdminException("There is no user admin for " + throw new UserDirectoryException("There is no user admin for " + base); - userAdmins.remove(base); + UserAdmin userAdmin = userAdmins.remove(base); + if (userAdmin instanceof AbstractUserDirectory) + ((AbstractUserDirectory) userAdmin).setSyncRegistry(null); } private UserAdmin findUserAdmin(String name) { try { return findUserAdmin(new LdapName(name)); } catch (InvalidNameException e) { - throw new ArgeoUserAdminException("Badly formatted name " + name, e); + throw new UserDirectoryException("Badly formatted name " + name, e); } } @@ -142,11 +154,35 @@ public class NodeUserAdmin implements UserAdmin, UserAdminAggregator { res.add(userAdmins.get(baseDn)); } if (res.size() == 0) - throw new ArgeoUserAdminException("Cannot find user admin for " + throw new UserDirectoryException("Cannot find user admin for " + name); if (res.size() > 1) - throw new ArgeoUserAdminException("Multiple user admin found for " + throw new UserDirectoryException("Multiple user admin found for " + name); return res.get(0); } + + public void setTransactionManager(TransactionManager transactionManager) { + this.transactionManager = transactionManager; + if (nodeRoles instanceof AbstractUserDirectory) + ((AbstractUserDirectory) nodeRoles) + .setTransactionManager(transactionManager); + for (UserAdmin userAdmin : userAdmins.values()) { + if (userAdmin instanceof AbstractUserDirectory) + ((AbstractUserDirectory) userAdmin) + .setTransactionManager(transactionManager); + } + } + + public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry) { + this.syncRegistry = syncRegistry; + if (nodeRoles instanceof AbstractUserDirectory) + ((AbstractUserDirectory) nodeRoles).setSyncRegistry(syncRegistry); + for (UserAdmin userAdmin : userAdmins.values()) { + if (userAdmin instanceof AbstractUserDirectory) + ((AbstractUserDirectory) userAdmin) + .setSyncRegistry(syncRegistry); + } + } + } 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 8cd11ba44..f97cf9113 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 @@ -22,7 +22,7 @@ SYSTEM { 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; + com.sun.security.auth.module.KeyStoreLoginModule requisite keyStoreURL="${osgi.instance.area}/node.p12" keyStoreType=PKCS12 keyStoreProvider=BC; org.argeo.cms.internal.auth.KernelLoginModule requisite; }; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransaction.java b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransaction.java new file mode 100644 index 000000000..a07645f15 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransaction.java @@ -0,0 +1,121 @@ +package org.argeo.cms.internal.transaction; + +import java.util.ArrayList; +import java.util.List; + +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +class SimpleTransaction implements Transaction, Status { + private final static Log log = LogFactory.getLog(SimpleTransaction.class); + + private final Xid xid; + private int status = Status.STATUS_ACTIVE; + private final List xaResources = new ArrayList(); + + public SimpleTransaction() { + xid = new UuidXid(); + } + + @Override + public synchronized void commit() throws RollbackException, + HeuristicMixedException, HeuristicRollbackException, + SecurityException, IllegalStateException, SystemException { + status = STATUS_PREPARING; + for (XAResource xaRes : xaResources) { + if (status == STATUS_MARKED_ROLLBACK) + break; + try { + xaRes.prepare(xid); + } catch (XAException e) { + status = STATUS_MARKED_ROLLBACK; + log.error("Cannot prepare " + xaRes + " for " + xid, e); + } + } + if (status == STATUS_MARKED_ROLLBACK) { + rollback(); + throw new RollbackException(); + } + status = STATUS_PREPARED; + + status = STATUS_COMMITTING; + for (XAResource xaRes : xaResources) { + if (status == STATUS_MARKED_ROLLBACK) + break; + try { + xaRes.commit(xid, false); + } catch (XAException e) { + status = STATUS_MARKED_ROLLBACK; + log.error("Cannot prepare " + xaRes + " for " + xid, e); + } + } + if (status == STATUS_MARKED_ROLLBACK) { + rollback(); + throw new RollbackException(); + } + status = STATUS_COMMITTED; + } + + @Override + public synchronized boolean delistResource(XAResource xaRes, int flag) + throws IllegalStateException, SystemException { + return xaResources.remove(xaRes); + } + + @Override + public synchronized boolean enlistResource(XAResource xaRes) + throws RollbackException, IllegalStateException, SystemException { + return xaResources.add(xaRes); + } + + @Override + public synchronized int getStatus() throws SystemException { + return status; + } + + @Override + public void registerSynchronization(Synchronization sync) + throws RollbackException, IllegalStateException, SystemException { + throw new UnsupportedOperationException(); + } + + @Override + public synchronized void rollback() throws IllegalStateException, + SystemException { + status = STATUS_ROLLING_BACK; + for (XAResource xaRes : xaResources) { + try { + xaRes.rollback(xid); + } catch (XAException e) { + log.error("Cannot rollback " + xaRes + " for " + xid, e); + } + } + status = STATUS_ROLLEDBACK; + } + + @Override + public void setRollbackOnly() throws IllegalStateException, SystemException { + status = STATUS_MARKED_ROLLBACK; + } + + @Override + public int hashCode() { + return xid.hashCode(); + } + + public Xid getXid() { + return xid; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java new file mode 100644 index 000000000..6261a0265 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java @@ -0,0 +1,188 @@ +package org.argeo.cms.internal.transaction; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.InvalidTransactionException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.UserTransaction; +import javax.transaction.xa.Xid; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsException; + +public class SimpleTransactionManager implements TransactionManager, + UserTransaction { + private final static Log log = LogFactory + .getLog(SimpleTransactionManager.class); + + private ThreadLocal current = new ThreadLocal(); + + private Map knownTransactions = Collections + .synchronizedMap(new HashMap()); + private SyncRegistry syncRegistry = new SyncRegistry(); + + @Override + public void begin() throws NotSupportedException, SystemException { + if (getCurrent() != null) + throw new NotSupportedException( + "Nested transactions are not supported"); + SimpleTransaction transaction = new SimpleTransaction(); + knownTransactions.put(transaction.getXid(), transaction); + current.set(transaction); + if (log.isDebugEnabled()) + log.debug("Started transaction " + transaction.getXid()); + } + + @Override + public void commit() throws RollbackException, HeuristicMixedException, + HeuristicRollbackException, SecurityException, + IllegalStateException, SystemException { + if (getCurrent() == null) + throw new IllegalStateException( + "No transaction registered with the current thread."); + getCurrent().commit(); + } + + @Override + public int getStatus() throws SystemException { + if (getCurrent() == null) + return Status.STATUS_NO_TRANSACTION; + return getTransaction().getStatus(); + } + + @Override + public Transaction getTransaction() throws SystemException { + return getCurrent(); + } + + protected SimpleTransaction getCurrent() throws SystemException { + SimpleTransaction transaction = current.get(); + if (transaction == null) + return null; + int status = transaction.getStatus(); + if (Status.STATUS_COMMITTED == status + || Status.STATUS_ROLLEDBACK == status) { + current.remove(); + knownTransactions.remove(transaction.getXid()); + if (log.isDebugEnabled()) + log.debug("Completed transaction " + + transaction.getXid() + + " [" + + (status == Status.STATUS_ROLLEDBACK ? "FAILED" : "OK") + + "]"); + return null; + } + return transaction; + } + + @Override + public void resume(Transaction tobj) throws InvalidTransactionException, + IllegalStateException, SystemException { + if (getCurrent() != null) + throw new IllegalStateException("Transaction " + current.get() + + " already registered"); + current.set((SimpleTransaction) tobj); + } + + @Override + public void rollback() throws IllegalStateException, SecurityException, + SystemException { + if (getCurrent() == null) + throw new IllegalStateException( + "No transaction registered with the current thread."); + getCurrent().rollback(); + } + + @Override + public void setRollbackOnly() throws IllegalStateException, SystemException { + if (getCurrent() == null) + throw new IllegalStateException( + "No transaction registered with the current thread."); + getCurrent().setRollbackOnly(); + } + + @Override + public void setTransactionTimeout(int seconds) throws SystemException { + throw new UnsupportedOperationException(); + } + + @Override + public Transaction suspend() throws SystemException { + Transaction transaction = getCurrent(); + current.remove(); + return transaction; + } + + public TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() { + return syncRegistry; + } + + private class SyncRegistry implements TransactionSynchronizationRegistry { + @Override + public Object getTransactionKey() { + try { + SimpleTransaction transaction = getCurrent(); + if (transaction == null) + return null; + return getCurrent().getXid(); + } catch (SystemException e) { + throw new CmsException("Cannot get transaction key", e); + } + } + + @Override + public void putResource(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getResource(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void registerInterposedSynchronization(Synchronization sync) { + throw new UnsupportedOperationException(); + } + + @Override + public int getTransactionStatus() { + try { + return getStatus(); + } catch (SystemException e) { + throw new CmsException("Cannot get status", e); + } + } + + @Override + public boolean getRollbackOnly() { + try { + return getStatus() == Status.STATUS_MARKED_ROLLBACK; + } catch (SystemException e) { + throw new CmsException("Cannot get status", e); + } + } + + @Override + public void setRollbackOnly() { + try { + getCurrent().setRollbackOnly(); + } catch (Exception e) { + throw new CmsException("Cannot set rollback only", e); + } + } + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/transaction/UuidXid.java b/org.argeo.cms/src/org/argeo/cms/internal/transaction/UuidXid.java new file mode 100644 index 000000000..18975ead8 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/transaction/UuidXid.java @@ -0,0 +1,132 @@ +package org.argeo.cms.internal.transaction; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.UUID; + +import javax.transaction.xa.Xid; + +/** + * Implementation of {@link Xid} based on {@link UUID}, using max significant + * bits as global transaction id, and least significant bits as branch + * qualifier. + */ +public class UuidXid implements Xid, Serializable { + private static final long serialVersionUID = -5380531989917886819L; + public final static int FORMAT = (int) serialVersionUID; + + private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE; + + private final int format; + private final byte[] globalTransactionId; + private final byte[] branchQualifier; + private final String uuid; + private final int hashCode; + + public UuidXid() { + this(UUID.randomUUID()); + } + + public UuidXid(UUID uuid) { + this.format = FORMAT; + this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits()); + this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits()); + this.uuid = uuid.toString(); + this.hashCode = uuid.hashCode(); + } + + public UuidXid(Xid xid) { + this(xid.getFormatId(), xid.getGlobalTransactionId(), xid + .getBranchQualifier()); + } + + private UuidXid(int format, byte[] globalTransactionId, + byte[] branchQualifier) { + this.format = format; + this.globalTransactionId = globalTransactionId; + this.branchQualifier = branchQualifier; + this.uuid = bytesToUUID(globalTransactionId, branchQualifier) + .toString(); + this.hashCode = uuid.hashCode(); + } + + @Override + public int getFormatId() { + return format; + } + + @Override + public byte[] getGlobalTransactionId() { + return Arrays.copyOf(globalTransactionId, globalTransactionId.length); + } + + @Override + public byte[] getBranchQualifier() { + return Arrays.copyOf(branchQualifier, branchQualifier.length); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj instanceof UuidXid) { + UuidXid that = (UuidXid) obj; + return Arrays.equals(globalTransactionId, that.globalTransactionId) + && Arrays.equals(branchQualifier, that.branchQualifier); + } + if (obj instanceof Xid) { + Xid that = (Xid) obj; + return Arrays.equals(globalTransactionId, + that.getGlobalTransactionId()) + && Arrays + .equals(branchQualifier, that.getBranchQualifier()); + } + return uuid.equals(obj.toString()); + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return new UuidXid(format, globalTransactionId, branchQualifier); + } + + @Override + public String toString() { + return uuid; + } + + public UUID asUuid() { + return bytesToUUID(globalTransactionId, branchQualifier); + } + + public static byte[] uuidToBytes(long bits) { + ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG); + buffer.putLong(0, bits); + return buffer.array(); + } + + public static UUID bytesToUUID(byte[] most, byte[] least) { + if (most.length < BYTES_PER_LONG) + most = Arrays.copyOf(most, BYTES_PER_LONG); + if (least.length < BYTES_PER_LONG) + least = Arrays.copyOf(least, BYTES_PER_LONG); + ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG); + buffer.put(most, 0, BYTES_PER_LONG); + buffer.put(least, 0, BYTES_PER_LONG); + buffer.flip(); + return new UUID(buffer.getLong(), buffer.getLong()); + } + + // public static void main(String[] args) { + // UUID uuid = UUID.randomUUID(); + // System.out.println(uuid); + // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()), + // uuidToBytes(uuid.getLeastSignificantBits())); + // System.out.println(uuid); + // } +} 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 deleted file mode 100644 index 8dcd6c216..000000000 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractLdapUserAdmin.java +++ /dev/null @@ -1,94 +0,0 @@ -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 { - private boolean isReadOnly; - private URI uri; - - private UserAdmin externalRoles; - private List indexedUserProperties = Arrays.asList(new String[] { - "uid", "mail", "cn" }); - - public AbstractLdapUserAdmin() { - } - - public AbstractLdapUserAdmin(URI uri, boolean isReadOnly) { - this.uri = uri; - this.isReadOnly = isReadOnly; - } - - 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; - } - - protected void setUri(URI uri) { - this.uri = uri; - } - - protected List getIndexedUserProperties() { - return indexedUserProperties; - } - - protected void setIndexedUserProperties(List indexedUserProperties) { - this.indexedUserProperties = indexedUserProperties; - } - - protected void setReadOnly(boolean isReadOnly) { - this.isReadOnly = isReadOnly; - } - - public boolean isReadOnly() { - return isReadOnly; - } - - UserAdmin getExternalRoles() { - return externalRoles; - } - - public void setExternalRoles(UserAdmin externalRoles) { - this.externalRoles = externalRoles; - } - -} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java new file mode 100644 index 000000000..4f20dc379 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java @@ -0,0 +1,162 @@ +package org.argeo.osgi.useradmin; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.transaction.RollbackException; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +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 AbstractUserDirectory implements UserAdmin { + private boolean isReadOnly; + private URI uri; + + private UserAdmin externalRoles; + private List indexedUserProperties = Arrays.asList(new String[] { + "uid", "mail", "cn" }); + + private String memberAttributeId = "member"; + private List credentialAttributeIds = Arrays + .asList(new String[] { "userpassword" }); + + private TransactionSynchronizationRegistry syncRegistry; + private Object editingTransactionKey = null; + private TransactionManager transactionManager; + private Transaction editingTransaction; + + public AbstractUserDirectory() { + } + + public AbstractUserDirectory(URI uri, boolean isReadOnly) { + this.uri = uri; + this.isReadOnly = isReadOnly; + } + + /** Returns the {@link Group}s this user is a direct member of. */ + protected abstract List getDirectGroups(User user); + + public void init() { + + } + + public void destroy() { + + } + + boolean isEditing() { + if (editingTransactionKey == null) + return false; + Object currentTrKey = syncRegistry.getTransactionKey(); + if (currentTrKey == null) + return false; + return editingTransactionKey.equals(currentTrKey); + } + + void checkEdit() { + Object currentTrKey = syncRegistry.getTransactionKey(); + if (currentTrKey == null) + throw new UserDirectoryException( + "A transaction needs to be active in order to edit"); + if (editingTransactionKey == null) { + editingTransactionKey = currentTrKey; + XAResource xaRes = getXAResource(); + if (xaRes != null) + try { + transactionManager.getTransaction().enlistResource(xaRes); + } catch (Exception e) { + throw new UserDirectoryException("Cannot enlist " + this, e); + } + } else { + if (!editingTransactionKey.equals(currentTrKey)) + throw new UserDirectoryException("Transaction " + + editingTransactionKey + " already editing"); + } + } + + 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 + } + + public XAResource getXAResource() { + return null; + } + + String getMemberAttributeId() { + return memberAttributeId; + } + + List getCredentialAttributeIds() { + return credentialAttributeIds; + } + + protected URI getUri() { + return uri; + } + + protected void setUri(URI uri) { + this.uri = uri; + } + + protected List getIndexedUserProperties() { + return indexedUserProperties; + } + + protected void setIndexedUserProperties(List indexedUserProperties) { + this.indexedUserProperties = indexedUserProperties; + } + + protected void setReadOnly(boolean isReadOnly) { + this.isReadOnly = isReadOnly; + } + + public boolean isReadOnly() { + return isReadOnly; + } + + UserAdmin getExternalRoles() { + return externalRoles; + } + + public void setExternalRoles(UserAdmin externalRoles) { + this.externalRoles = externalRoles; + } + + public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry) { + this.syncRegistry = syncRegistry; + } + + public void setTransactionManager(TransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/AttributeDictionary.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/AttributeDictionary.java deleted file mode 100644 index 99e5edb62..000000000 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/AttributeDictionary.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; - -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; - -class AttributeDictionary extends Dictionary { - private final Attributes attributes; - private final List effectiveKeys = new ArrayList(); - private final List attrFilter; - private final Boolean includeFilter; - - public AttributeDictionary(Attributes attributes, List attrFilter, - Boolean includeFilter) { - this.attributes = attributes; - this.attrFilter = attrFilter; - this.includeFilter = includeFilter; - try { - NamingEnumeration ids = attributes.getIDs(); - while (ids.hasMore()) { - String id = ids.next(); - if (includeFilter && attrFilter.contains(id)) - effectiveKeys.add(id); - else if (!includeFilter && !attrFilter.contains(id)) - effectiveKeys.add(id); - } - } catch (NamingException e) { - throw new ArgeoUserAdminException( - "Cannot initialise attribute dictionary", e); - } - } - - @Override - public int size() { - return effectiveKeys.size(); - } - - @Override - public boolean isEmpty() { - return effectiveKeys.size() == 0; - } - - @Override - public Enumeration keys() { - return Collections.enumeration(effectiveKeys); - } - - @Override - public Enumeration elements() { - final Iterator it = effectiveKeys.iterator(); - return new Enumeration() { - - @Override - public boolean hasMoreElements() { - return it.hasNext(); - } - - @Override - public Object nextElement() { - String key = it.next(); - try { - return attributes.get(key).get(); - } catch (NamingException e) { - throw new ArgeoUserAdminException( - "Cannot get value for key " + key, e); - } - } - - }; - } - - @Override - public Object get(Object key) { - try { - Attribute attr = attributes.get(key.toString()); - if (attr == null) - return null; - return attr.get(); - } catch (NamingException e) { - throw new ArgeoUserAdminException("Cannot get value for attribute " - + key, e); - } - } - - @Override - public Object put(String key, Object value) { - if (!(value instanceof String || value instanceof byte[])) - throw new IllegalArgumentException("Value must be String or byte[]"); - - if (includeFilter && !attrFilter.contains(key)) - throw new IllegalArgumentException("Key " + key + " not included"); - else if (!includeFilter && attrFilter.contains(key)) - throw new IllegalArgumentException("Key " + key + " excluded"); - - try { - Attribute attribute = attributes.get(key.toString()); - attribute = new BasicAttribute(key.toString()); - attribute.add(value); - Attribute previousAttribute = attributes.put(attribute); - if (previousAttribute != null) - return previousAttribute.get(); - else - return null; - } catch (NamingException e) { - throw new ArgeoUserAdminException("Cannot get value for attribute " - + key, e); - } - } - - @Override - public Object remove(Object key) { - if (includeFilter && !attrFilter.contains(key)) - throw new IllegalArgumentException("Key " + key + " not included"); - else if (!includeFilter && attrFilter.contains(key)) - throw new IllegalArgumentException("Key " + key + " excluded"); - - try { - Attribute attr = attributes.remove(key.toString()); - if (attr != null) - return attr.get(); - else - return null; - } catch (NamingException e) { - throw new ArgeoUserAdminException("Cannot remove attribute " + key, - e); - } - } -} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryGroup.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryGroup.java new file mode 100644 index 000000000..bb64c26e1 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryGroup.java @@ -0,0 +1,11 @@ +package org.argeo.osgi.useradmin; + +import java.util.List; + +import javax.naming.ldap.LdapName; + +import org.osgi.service.useradmin.Group; + +public interface DirectoryGroup extends Group, DirectoryUser { + List getMemberNames(); +} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryUser.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryUser.java new file mode 100644 index 000000000..05107ab56 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryUser.java @@ -0,0 +1,12 @@ +package org.argeo.osgi.useradmin; + +import javax.naming.directory.Attributes; +import javax.naming.ldap.LdapName; + +import org.osgi.service.useradmin.User; + +interface DirectoryUser extends User { + LdapName getDn(); + + Attributes getAttributes(); +} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java new file mode 100644 index 000000000..d99fc3390 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java @@ -0,0 +1,9 @@ +package org.argeo.osgi.useradmin; + +import org.osgi.service.useradmin.Role; + +public interface EditorRole extends Role { + public static final int EDITOR = -1; + + +} 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 index 0b9c3ad7a..62f4b2b37 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapNames.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapNames.java @@ -6,9 +6,11 @@ package org.argeo.osgi.useradmin; */ 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_SN = LDAP_PREFIX + "sn"; 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 dabae718c..9bb8fbc7d 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 @@ -26,7 +26,7 @@ import org.osgi.service.useradmin.Group; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; -public class LdapUserAdmin extends AbstractLdapUserAdmin { +public class LdapUserAdmin extends AbstractUserDirectory { private final static Log log = LogFactory.getLog(LdapUserAdmin.class); private String baseDn = "dc=example,dc=com"; @@ -59,7 +59,7 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { log.debug(initialLdapContext.getAttributes( "uid=root,ou=users,dc=example,dc=com").get("cn")); } catch (Exception e) { - throw new ArgeoUserAdminException("Cannot connect to LDAP", e); + throw new UserDirectoryException("Cannot connect to LDAP", e); } } @@ -92,13 +92,13 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { if (attrs.get("objectClass").contains("groupOfNames")) res = new LdifGroup(this, new LdapName(name), attrs); else if (attrs.get("objectClass").contains("inetOrgPerson")) - res = new LdifUser(new LdapName(name), attrs); + res = new LdifUser(this, new LdapName(name), attrs); else - throw new ArgeoUserAdminException("Unsupported LDAP type for " + throw new UserDirectoryException("Unsupported LDAP type for " + name); return res; } catch (NamingException e) { - throw new ArgeoUserAdminException("Cannot get role for " + name, e); + throw new UserDirectoryException("Cannot get role for " + name, e); } } @@ -124,16 +124,17 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { role = new LdifGroup(this, toDn(searchBase, searchResult), attrs); else if (attrs.get("objectClass").contains("inetOrgPerson")) - role = new LdifUser(toDn(searchBase, searchResult), attrs); + role = new LdifUser(this, toDn(searchBase, searchResult), + attrs); else - throw new ArgeoUserAdminException( + throw new UserDirectoryException( "Unsupported LDAP type for " + searchResult.getName()); res.add(role); } return res.toArray(new Role[res.size()]); } catch (Exception e) { - throw new ArgeoUserAdminException("Cannot get roles for filter " + throw new UserDirectoryException("Cannot get roles for filter " + filter, e); } } @@ -172,10 +173,10 @@ public class LdapUserAdmin extends AbstractLdapUserAdmin { } if (searchResult == null) return null; - return new LdifUser(toDn(searchBase, searchResult), + return new LdifUser(this, toDn(searchBase, searchResult), searchResult.getAttributes()); } catch (Exception e) { - throw new ArgeoUserAdminException("Cannot get user with " + key + throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e); } } 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 3600866af..845fefd13 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 @@ -29,7 +29,7 @@ public class LdifAuthorization implements Authorization, LdapNames { if (displayName == null) displayName = user.getName(); if (displayName == null) - throw new ArgeoUserAdminException( + throw new UserDirectoryException( "Cannot set display name for " + user); this.displayName = displayName.toString(); } 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 a19052425..3e9d44750 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 @@ -8,33 +8,25 @@ import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; 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; +public class LdifGroup extends LdifUser implements DirectoryGroup { + private final String memberAttributeId; - private final UserAdmin userAdmin; - private String memberAttrName = "member"; - - public LdifGroup(UserAdmin userAdmin, LdapName dn, Attributes attributes) { - super(dn, attributes); - this.userAdmin = userAdmin; + public LdifGroup(AbstractUserDirectory userAdmin, LdapName dn, + Attributes attributes) { + super(userAdmin, dn, attributes); + memberAttributeId = userAdmin.getMemberAttributeId(); } @Override public boolean addMember(Role role) { - Attribute member = getAttributes().get(memberAttrName); + Attribute member = getAttributes().get(memberAttributeId); if (member != null) { if (member.contains(role.getName())) return false; } else - getAttributes().put(memberAttrName, role.getName()); - // directMembers.add(role); - // if (role instanceof LdifUser) - // ((LdifUser) role).directMemberOf.add(this); + getAttributes().put(memberAttributeId, role.getName()); return true; } @@ -45,14 +37,11 @@ public class LdifGroup extends LdifUser implements Group { @Override public boolean removeMember(Role role) { - Attribute member = getAttributes().get(memberAttrName); + Attribute member = getAttributes().get(memberAttributeId); if (member != null) { if (!member.contains(role.getName())) return false; member.remove(role.getName()); - // directMembers.remove(role); - // if (role instanceof LdifUser) - // ((LdifUser) role).directMemberOf.remove(this); return true; } else return false; @@ -62,62 +51,23 @@ public class LdifGroup extends LdifUser implements Group { public Role[] getMembers() { 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()); + Role role = getUserAdmin().getRole(ldapName.toString()); + if (role == null) { + if (getUserAdmin().getExternalRoles() != null) + role = getUserAdmin().getExternalRoles().getRole( + ldapName.toString()); } if (role == null) - throw new ArgeoUserAdminException("No role found for " + throw new UserDirectoryException("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) - // return new Role[0]; - // try { - // List roles = new ArrayList(); - // NamingEnumeration values = memberAttribute.getAll(); - // while (values.hasMore()) { - // LdapName dn = new LdapName(values.next().toString()); - // roles.add(new LdifUser(dn, null)); - // } - // return roles.toArray(new Role[roles.size()]); - // } catch (Exception e) { - // throw new ArgeoUserAdminException("Cannot get members", e); - // } } - // 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); + @Override + public List getMemberNames() { + Attribute memberAttribute = getAttributes().get(memberAttributeId); if (memberAttribute == null) return new ArrayList(); try { @@ -129,7 +79,7 @@ public class LdifGroup extends LdifUser implements Group { } return roles; } catch (Exception e) { - throw new ArgeoUserAdminException("Cannot get members", e); + throw new UserDirectoryException("Cannot get members", e); } } @@ -142,9 +92,4 @@ public class LdifGroup extends LdifUser implements Group { public int getType() { return 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 85d18c082..cd5401a55 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 @@ -2,34 +2,43 @@ package org.argeo.osgi.useradmin; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Iterator; import java.util.List; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; import javax.naming.ldap.LdapName; -import org.osgi.service.useradmin.User; - -class LdifUser implements User { - // optimisation - //List directMemberOf = new ArrayList(); +class LdifUser implements DirectoryUser { + private final AbstractUserDirectory userAdmin; private final LdapName dn; - private Attributes attributes; + + private final boolean frozen; + private Attributes publishedAttributes; + private Attributes modifiedAttributes = null; private final AttributeDictionary properties; private final AttributeDictionary credentials; - private List credentialAttributes = Arrays - .asList(new String[] { "userpassword" }); + LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) { + this(userAdmin, dn, attributes, false); + } - LdifUser(LdapName dn, Attributes attributes) { + private LdifUser(AbstractUserDirectory userAdmin, LdapName dn, + Attributes attributes, boolean frozen) { + this.userAdmin = userAdmin; this.dn = dn; - this.attributes = attributes; - properties = new AttributeDictionary(attributes, credentialAttributes, - false); - credentials = new AttributeDictionary(attributes, credentialAttributes, - true); + this.publishedAttributes = attributes; + properties = new AttributeDictionary(false); + credentials = new AttributeDictionary(true); + this.frozen = frozen; } @Override @@ -66,12 +75,38 @@ class LdifUser implements User { return false; } - protected LdapName getDn() { + @Override + public LdapName getDn() { return dn; } - protected Attributes getAttributes() { - return attributes; + @Override + public synchronized Attributes getAttributes() { + return isEditing() ? modifiedAttributes : publishedAttributes; + } + + protected synchronized boolean isEditing() { + return userAdmin.isEditing() && modifiedAttributes != null; + } + + protected synchronized void startEditing() { + if (frozen) + 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(); + } + + protected synchronized void stopEditing(boolean apply) { + assert modifiedAttributes != null; + if (apply) + publishedAttributes = modifiedAttributes; + modifiedAttributes = null; + } + + public DirectoryUser getPublished() { + return new LdifUser(userAdmin, dn, publishedAttributes, true); } @Override @@ -94,4 +129,140 @@ class LdifUser implements User { public String toString() { return dn.toString(); } + + protected AbstractUserDirectory getUserAdmin() { + return userAdmin; + } + + private class AttributeDictionary extends Dictionary { + private final List effectiveKeys = new ArrayList(); + private final List attrFilter; + private final Boolean includeFilter; + + public AttributeDictionary(Boolean includeFilter) { + this.attrFilter = userAdmin.getCredentialAttributeIds(); + this.includeFilter = includeFilter; + try { + NamingEnumeration ids = getAttributes().getIDs(); + while (ids.hasMore()) { + String id = ids.next(); + if (includeFilter && attrFilter.contains(id)) + effectiveKeys.add(id); + else if (!includeFilter && !attrFilter.contains(id)) + effectiveKeys.add(id); + } + } catch (NamingException e) { + throw new UserDirectoryException( + "Cannot initialise attribute dictionary", e); + } + } + + @Override + public int size() { + return effectiveKeys.size(); + } + + @Override + public boolean isEmpty() { + return effectiveKeys.size() == 0; + } + + @Override + public Enumeration keys() { + return Collections.enumeration(effectiveKeys); + } + + @Override + public Enumeration elements() { + final Iterator it = effectiveKeys.iterator(); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return it.hasNext(); + } + + @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); + } + } + + }; + } + + @Override + public Object get(Object key) { + try { + Attribute attr = getAttributes().get(key.toString()); + if (attr == null) + return null; + return attr.get(); + } catch (NamingException e) { + throw new UserDirectoryException( + "Cannot get value for attribute " + key, e); + } + } + + @Override + public Object put(String key, Object value) { + userAdmin.checkEdit(); + if (!isEditing()) + startEditing(); + + if (!(value instanceof String || value instanceof byte[])) + throw new IllegalArgumentException( + "Value must be String or byte[]"); + + if (includeFilter && !attrFilter.contains(key)) + throw new IllegalArgumentException("Key " + key + + " not included"); + else if (!includeFilter && attrFilter.contains(key)) + throw new IllegalArgumentException("Key " + key + " excluded"); + + try { + Attribute attribute = modifiedAttributes.get(key.toString()); + attribute = new BasicAttribute(key.toString()); + attribute.add(value); + Attribute previousAttribute = modifiedAttributes.put(attribute); + if (previousAttribute != null) + return previousAttribute.get(); + else + return null; + } catch (NamingException e) { + throw new UserDirectoryException( + "Cannot get value for attribute " + key, e); + } + } + + @Override + public Object remove(Object key) { + userAdmin.checkEdit(); + if (!isEditing()) + startEditing(); + + if (includeFilter && !attrFilter.contains(key)) + throw new IllegalArgumentException("Key " + key + + " not included"); + else if (!includeFilter && attrFilter.contains(key)) + throw new IllegalArgumentException("Key " + key + " excluded"); + + try { + Attribute attr = modifiedAttributes.remove(key.toString()); + if (attr != null) + return attr.get(); + else + return null; + } catch (NamingException e) { + throw new UserDirectoryException("Cannot remove attribute " + + key, e); + } + } + } + } 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 c5ca49300..098243638 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 @@ -21,6 +21,9 @@ import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; import org.apache.commons.io.IOUtils; import org.osgi.framework.Filter; @@ -31,7 +34,7 @@ import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; /** User admin implementation using LDIF file(s) as backend. */ -public class LdifUserAdmin extends AbstractLdapUserAdmin { +public class LdifUserAdmin extends AbstractUserDirectory { SortedMap users = new TreeMap(); SortedMap groups = new TreeMap(); @@ -39,9 +42,10 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { // private Map> directMemberOf = new // TreeMap>(); + private XaRes xaRes = new XaRes(); public LdifUserAdmin(String uri) { - this(uri, true); + this(uri, readOnlyDefault(uri)); } public LdifUserAdmin(String uri, boolean isReadOnly) { @@ -49,12 +53,21 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { try { setUri(new URI(uri)); } catch (URISyntaxException e) { - throw new ArgeoUserAdminException("Invalid URI " + uri, e); + throw new UserDirectoryException("Invalid URI " + uri, e); } - if (!isReadOnly && !getUri().getScheme().equals("file:")) + if (!isReadOnly && !getUri().getScheme().equals("file")) throw new UnsupportedOperationException(getUri().getScheme() - + "not supported read-write."); + + " not supported read-write."); + + } + + public LdifUserAdmin(URI uri, boolean isReadOnly) { + setReadOnly(isReadOnly); + setUri(uri); + if (!isReadOnly && !getUri().getScheme().equals("file")) + throw new UnsupportedOperationException(getUri().getScheme() + + " not supported read-write."); } @@ -64,21 +77,35 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { setUri(null); } + private static boolean readOnlyDefault(String uriStr) { + URI uri; + try { + uri = new URI(uriStr); + } catch (Exception e) { + throw new UserDirectoryException("Invalid URI " + uriStr, e); + } + if (uri.getScheme().equals("file")) { + File file = new File(uri); + return !file.canWrite(); + } + return true; + } + public void init() { try { load(getUri().toURL().openStream()); } catch (Exception e) { - throw new ArgeoUserAdminException("Cannot open URL " + getUri(), e); + throw new UserDirectoryException("Cannot open URL " + getUri(), e); } } public void save() { if (getUri() == null || isReadOnly()) - throw new ArgeoUserAdminException("Cannot save LDIF user admin"); + throw new UserDirectoryException("Cannot save LDIF user admin"); try (FileOutputStream out = new FileOutputStream(new File(getUri()))) { save(out); } catch (IOException e) { - throw new ArgeoUserAdminException("Cannot save user admin to " + throw new UserDirectoryException("Cannot save user admin to " + getUri(), e); } } @@ -106,7 +133,7 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { objectClasses: while (objectClasses.hasMore()) { String objectClass = objectClasses.next().toString(); if (objectClass.equals("inetOrgPerson")) { - users.put(key, new LdifUser(key, attributes)); + users.put(key, new LdifUser(this, key, attributes)); break objectClasses; } else if (objectClass.equals("groupOfNames")) { groups.put(key, new LdifGroup(this, key, attributes)); @@ -116,8 +143,8 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { } // optimise -// for (LdifGroup group : groups.values()) -// loadMembers(group); + // for (LdifGroup group : groups.values()) + // loadMembers(group); // indexes for (String attr : getIndexedUserProperties()) @@ -131,7 +158,7 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { LdifUser otherUser = userIndexes.get(attr).put( value.toString(), user); if (otherUser != null) - throw new ArgeoUserAdminException("User " + user + throw new UserDirectoryException("User " + user + " and user " + otherUser + " both have property " + attr + " set to " + value); @@ -139,7 +166,7 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { } } } catch (Exception e) { - throw new ArgeoUserAdminException( + throw new UserDirectoryException( "Cannot load user admin service from LDIF", e); } } @@ -180,7 +207,7 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { try { LdapName dn = new LdapName(name); if (users.containsKey(dn) || groups.containsKey(dn)) - throw new ArgeoUserAdminException("Already a role " + name); + throw new UserDirectoryException("Already a role " + name); BasicAttributes attrs = new BasicAttributes(); attrs.put("dn", dn.toString()); @@ -189,16 +216,16 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { attrs.put(nameRdn.getType(), nameRdn.getValue()); LdifUser newRole; if (type == Role.USER) { - newRole = new LdifUser(dn, attrs); + newRole = new LdifUser(this, dn, attrs); users.put(dn, newRole); } else if (type == Role.GROUP) { newRole = new LdifGroup(this, dn, attrs); groups.put(dn, (LdifGroup) newRole); } else - throw new ArgeoUserAdminException("Unsupported type " + type); + throw new UserDirectoryException("Unsupported type " + type); return newRole; } catch (InvalidNameException e) { - throw new ArgeoUserAdminException("Cannot create role " + name, e); + throw new UserDirectoryException("Cannot create role " + name, e); } } @@ -212,12 +239,12 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { else if (groups.containsKey(dn)) role = groups.remove(dn); else - throw new ArgeoUserAdminException("There is no role " + name); + throw new UserDirectoryException("There is no role " + name); if (role == null) return false; for (LdifGroup group : getDirectGroups(role)) { -// group.directMembers.remove(role); - group.getAttributes().get(group.getMemberAttrName()) + // group.directMembers.remove(role); + group.getAttributes().get(getMemberAttributeId()) .remove(dn.toString()); } if (role instanceof LdifGroup) { @@ -230,7 +257,7 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { } return true; } catch (InvalidNameException e) { - throw new ArgeoUserAdminException("Cannot create role " + name, e); + throw new UserDirectoryException("Cannot create role " + name, e); } } @@ -285,29 +312,29 @@ 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); -// } -// // 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); -// } -// } + // 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) { @@ -318,7 +345,7 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { try { dn = new LdapName(user.getName()); } catch (InvalidNameException e) { - throw new ArgeoUserAdminException("Badly formatted user name " + throw new UserDirectoryException("Badly formatted user name " + user.getName(), e); } @@ -335,4 +362,72 @@ public class LdifUserAdmin extends AbstractLdapUserAdmin { // return Collections.EMPTY_LIST; } + @Override + public XAResource getXAResource() { + return xaRes; + } + + private class XaRes implements XAResource { + + @Override + public void commit(Xid xid, boolean onePhase) throws XAException { + save(); + } + + @Override + public void end(Xid xid, int flags) throws XAException { + // TODO Auto-generated method stub + + } + + @Override + public void forget(Xid xid) throws XAException { + // TODO Auto-generated method stub + + } + + @Override + public int getTransactionTimeout() throws XAException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean isSameRM(XAResource xares) throws XAException { + // TODO Auto-generated method stub + return false; + } + + @Override + public int prepare(Xid xid) throws XAException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Xid[] recover(int flag) throws XAException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void rollback(Xid xid) throws XAException { + // TODO Auto-generated method stub + + } + + @Override + public boolean setTransactionTimeout(int seconds) throws XAException { + // TODO Auto-generated method stub + return false; + } + + @Override + public void start(Xid xid, int flags) throws XAException { + // TODO Auto-generated method stub + + } + + } + } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifWriter.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifWriter.java index 25793c923..7aad5c488 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifWriter.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifWriter.java @@ -39,7 +39,7 @@ public class LdifWriter { writer.append('\n'); writer.flush(); } catch (NamingException e) { - throw new ArgeoUserAdminException("Cannot write LDIF", e); + throw new UserDirectoryException("Cannot write LDIF", e); } } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserAdminWorkingCopy.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserAdminWorkingCopy.java new file mode 100644 index 000000000..7103d7ac6 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserAdminWorkingCopy.java @@ -0,0 +1,14 @@ +package org.argeo.osgi.useradmin; + +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdmin; + +public interface UserAdminWorkingCopy extends UserAdmin { + public void commit(); + + public void rollback(); + + public Boolean isEditable(Role role); + + public T getPublished(T role); +} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/ArgeoUserAdminException.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryException.java similarity index 62% rename from org.argeo.security.core/src/org/argeo/osgi/useradmin/ArgeoUserAdminException.java rename to org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryException.java index 70724a743..613d0fdf0 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/ArgeoUserAdminException.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryException.java @@ -6,14 +6,14 @@ import org.osgi.service.useradmin.UserAdmin; * Exceptions related to Argeo's implementation of OSGi {@link UserAdmin} * service. */ -public class ArgeoUserAdminException extends RuntimeException { +public class UserDirectoryException extends RuntimeException { private static final long serialVersionUID = 1419352360062048603L; - public ArgeoUserAdminException(String message) { + public UserDirectoryException(String message) { super(message); } - public ArgeoUserAdminException(String message, Throwable e) { + public UserDirectoryException(String message, Throwable e) { super(message, e); } } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryTransaction.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryTransaction.java new file mode 100644 index 000000000..24e3cbeab --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryTransaction.java @@ -0,0 +1,26 @@ +package org.argeo.osgi.useradmin; + +import javax.transaction.UserTransaction; + +import org.osgi.service.useradmin.UserAdmin; + +class UserDirectoryTransaction { + static ThreadLocal current = new ThreadLocal(); + + private UserAdmin userAdmin; + + private UserTransaction userTransaction; + + public UserDirectoryTransaction(UserAdmin userAdmin) { + this.userAdmin = userAdmin; + if (current.get() != null) + throw new UserDirectoryException("Transaction " + current.get() + + " already active."); + current.set(this); + } + + public void setUserTransaction(UserTransaction userTransaction) { + this.userTransaction = userTransaction; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/cm/LdapUserAdminFactory.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/cm/LdapUserAdminFactory.java index 64fdea0b0..42338b2af 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/cm/LdapUserAdminFactory.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/cm/LdapUserAdminFactory.java @@ -4,8 +4,8 @@ import java.util.Dictionary; import java.util.HashMap; import java.util.Map; -import org.argeo.osgi.useradmin.AbstractLdapUserAdmin; -import org.argeo.osgi.useradmin.ArgeoUserAdminException; +import org.argeo.osgi.useradmin.AbstractUserDirectory; +import org.argeo.osgi.useradmin.UserDirectoryException; import org.argeo.osgi.useradmin.LdapUserAdmin; import org.argeo.osgi.useradmin.LdifUserAdmin; import org.argeo.osgi.useradmin.UserAdminAggregator; @@ -31,7 +31,7 @@ public class LdapUserAdminFactory implements ManagedServiceFactory { Dictionary properties) throws ConfigurationException { String baseDn = properties.get("baseDn").toString(); String userAdminUri = properties.get("uri").toString(); - AbstractLdapUserAdmin userAdmin; + AbstractUserDirectory userAdmin; if (userAdminUri.startsWith("ldap")) userAdmin = new LdapUserAdmin(userAdminUri); else @@ -45,7 +45,7 @@ public class LdapUserAdminFactory implements ManagedServiceFactory { if (index.containsKey(pid)) userAdminAggregator.removeUserAdmin(index.get(pid)); else - throw new ArgeoUserAdminException("No user admin registered for " + throw new UserDirectoryException("No user admin registered for " + pid); index.remove(pid); } 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 index ed6640f36..f66d3f99c 100644 --- a/org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java +++ b/org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java @@ -8,7 +8,6 @@ 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; @@ -19,7 +18,6 @@ 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; @@ -30,7 +28,7 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; public class PkiUtils { private final static String SECURITY_PROVIDER; static { - Security.addProvider(new BouncyCastleProvider()); + // Security.addProvider(new BouncyCastleProvider()); SECURITY_PROVIDER = "BC"; } -- 2.30.2