Introduce Argeo 2 security model-
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 26 Aug 2015 12:59:10 +0000 (12:59 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 26 Aug 2015 12:59:10 +0000 (12:59 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@8346 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

32 files changed:
demo/log4j.properties
org.argeo.cms/src/org/argeo/cms/AbstractCmsEntryPoint.java
org.argeo.cms/src/org/argeo/cms/KernelHeader.java
org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/UserAdminLoginModule.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/demo.ldif [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg
org.argeo.cms/src/org/argeo/cms/internal/kernel/repository-h2.xml
org.argeo.cms/src/org/argeo/cms/util/CurrentUserUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/UserMenu.java
org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java
org.argeo.eclipse.ui.workbench/plugin.xml
org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifAuthorization.java
org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
org.argeo.security.jackrabbit/.classpath
org.argeo.security.jackrabbit/build.properties
org.argeo.security.jackrabbit/ext/test/log4j.properties [new file with mode: 0644]
org.argeo.security.jackrabbit/ext/test/org/argeo/security/jackrabbit/JackrabbitAuthTest.java [new file with mode: 0644]
org.argeo.security.jackrabbit/ext/test/org/argeo/security/jackrabbit/repository-memory-test.xml [new file with mode: 0644]
org.argeo.security.jackrabbit/ext/test/org/argeo/security/jackrabbit/test_jaas.config [new file with mode: 0644]
org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java
org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java [new file with mode: 0644]
org.argeo.security.ui.rap/plugin.xml
org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java
org.argeo.security.ui/plugin.xml
org.argeo.security.ui/src/org/argeo/security/ui/internal/CurrentUser.java
org.argeo.security.ui/src/org/argeo/security/ui/views/UserProfile.java
org.argeo.server.jcr/build.properties
org.argeo.server.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java

index 8b071c0365ad4a317c737a338abc6da750126e68..1238aeebe3232f9a4b5c293d931a2ded59949f73 100644 (file)
@@ -1,6 +1,7 @@
 log4j.rootLogger=WARN, development
 
 log4j.logger.org.argeo=DEBUG
+log4j.logger.org.apache.jackrabbit.core.RepositoryImpl=DEBUG
 #log4j.logger.argeo.stats=DEBUG
 
 ## Appenders
index 00db13443b0cb44d6845f288471c4d53014e45eb..951e177b2473f0d5b81b03b1e2532ed2837a2b85 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.cms;
 
+import java.security.PrivilegedAction;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
@@ -103,14 +104,20 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint
        }
 
        @Override
-       protected final void createContents(Composite parent) {
-               try {
-                       getShell().getDisplay().setData(CmsSession.KEY, this);
-
-                       createUi(parent);
-               } catch (Exception e) {
-                       throw new CmsException("Cannot create entrypoint contents", e);
-               }
+       protected final void createContents(final Composite parent) {
+               getShell().getDisplay().setData(CmsSession.KEY, this);
+               Subject.doAs(subject, new PrivilegedAction<Void>() {
+                       @Override
+                       public Void run() {
+                               try {
+                                       createUi(parent);
+                               } catch (Exception e) {
+                                       throw new CmsException("Cannot create entrypoint contents",
+                                                       e);
+                               }
+                               return null;
+                       }
+               });
        }
 
        /** Create UI */
@@ -140,7 +147,7 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint
        public void navigateTo(String state) {
                exception = null;
                String title = setState(state);
-               refresh();
+               doRefresh();
                if (browserNavigation != null)
                        browserNavigation.pushState(state, title);
        }
@@ -152,48 +159,66 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint
 
        @Override
        public void authChange() {
-               try {
-                       String currentPath = null;
-                       if (node != null)
-                               currentPath = node.getPath();
-                       JcrUtils.logoutQuietly(session);
+               Subject.doAs(subject, new PrivilegedAction<Void>() {
 
-                       session = repository.login(workspace);
-                       if (currentPath != null)
+                       @Override
+                       public Void run() {
                                try {
-                                       node = session.getNode(currentPath);
-                               } catch (Exception e) {
-                                       try {
-                                               // TODO find a less hacky way to log out
-                                               new ArgeoLoginContext(
-                                                               KernelHeader.LOGIN_CONTEXT_ANONYMOUS, subject)
-                                                               .logout();
-                                               new ArgeoLoginContext(
-                                                               KernelHeader.LOGIN_CONTEXT_ANONYMOUS, subject)
-                                                               .login();
-                                       } catch (LoginException eAnonymous) {
-                                               throw new ArgeoException("Cannot reset to anonymous",
-                                                               eAnonymous);
-                                       }
+                                       String currentPath = null;
+                                       if (node != null)
+                                               currentPath = node.getPath();
                                        JcrUtils.logoutQuietly(session);
+
                                        session = repository.login(workspace);
-                                       navigateTo("~");
-                                       throw e;
+                                       if (currentPath != null)
+                                               try {
+                                                       node = session.getNode(currentPath);
+                                               } catch (Exception e) {
+                                                       try {
+                                                               // TODO find a less hacky way to log out
+                                                               new ArgeoLoginContext(
+                                                                               KernelHeader.LOGIN_CONTEXT_ANONYMOUS,
+                                                                               subject).logout();
+                                                               new ArgeoLoginContext(
+                                                                               KernelHeader.LOGIN_CONTEXT_ANONYMOUS,
+                                                                               subject).login();
+                                                       } catch (LoginException eAnonymous) {
+                                                               throw new ArgeoException(
+                                                                               "Cannot reset to anonymous", eAnonymous);
+                                                       }
+                                                       JcrUtils.logoutQuietly(session);
+                                                       session = repository.login(workspace);
+                                                       navigateTo("~");
+                                                       throw e;
+                                               }
+
+                                       // refresh UI
+                                       doRefresh();
+                               } catch (RepositoryException e) {
+                                       throw new CmsException("Cannot perform auth change", e);
                                }
+                               return null;
+                       }
 
-                       // refresh UI
-                       refresh();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot perform auth change", e);
-               }
+               });
 
        }
 
        @Override
-       public void exception(Throwable e) {
-               this.exception = e;
+       public void exception(final Throwable e) {
+               AbstractCmsEntryPoint.this.exception = e;
                log.error("Unexpected exception in CMS", e);
-               refresh();
+               doRefresh();
+       }
+
+       protected void doRefresh() {
+               Subject.doAs(subject, new PrivilegedAction<Void>() {
+                       @Override
+                       public Void run() {
+                               refresh();
+                               return null;
+                       }
+               });
        }
 
        @Override
index c2dd2cae7dfe76cc57a107a5ecc46545846beb7b..620699abfac7a944cf08ab8f81ad83974bdfd0ac 100644 (file)
@@ -9,14 +9,17 @@ public interface KernelHeader {
        final static String LOGIN_CONTEXT_SINGLE_USER = "SINGLE_USER";
 
        // RESERVED ROLES
-       public final static String ROLE_ADMIN = "ROLE_ADMIN";
-       public final static String ROLE_GROUP_ADMIN = "ROLE_GROUP_ADMIN";
-       public final static String ROLE_USER_ADMIN = "ROLE_USER_ADMIN";
-       public final static String ROLE_USER = "ROLE_USER";
-       public final static String ROLE_ANONYMOUS = "ROLE_ANONYMOUS";
+       public final static String ROLE_ADMIN = "cn=admin,ou=system,ou=node";
+       public final static String ROLE_GROUP_ADMIN = "cn=groupAdmin,ou=system,ou=node";
+       public final static String ROLE_USER_ADMIN = "cn=userAdmin,ou=system,ou=node";
+       // Special system groups that cannot be edited:
+       // user U anonymous = everyone
+       public final static String ROLE_USER = "cn=user,ou=system,ou=node";
+       public final static String ROLE_ANONYMOUS = "cn=anonymous,ou=system,ou=node";
 
        // RESERVED USERNAMES
        public final static String USERNAME_ADMIN = "root";
        public final static String USERNAME_DEMO = "demo";
+       @Deprecated
        public final static String USERNAME_ANONYMOUS = "anonymous";
 }
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
new file mode 100644 (file)
index 0000000..417ea69
--- /dev/null
@@ -0,0 +1,90 @@
+package org.argeo.cms.internal.auth;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.cms.CmsException;
+import org.osgi.service.useradmin.Authorization;
+
+/**
+ * A {@link Principal} which has been implied by an {@link Authorization}. If it
+ * is empty it meeans this is an additional identity, otherwise it lists the
+ * users (typically the logged in user but possibly empty
+ * {@link ImpliedByPrincipal}s) which have implied it. When an additional
+ * identityx is removed, the related {@link ImpliedByPrincipal}s can thus be
+ * removed.
+ */
+public final class ImpliedByPrincipal implements Group {
+       private final LdapName name;
+       private Set<Principal> causes = new HashSet<Principal>();
+
+       public ImpliedByPrincipal(String name, Principal userPrincipal) {
+               try {
+                       this.name = new LdapName(name);
+               } catch (InvalidNameException e) {
+                       throw new CmsException("Badly formatted role name", e);
+               }
+               if (userPrincipal != null)
+                       causes.add(userPrincipal);
+       }
+
+       public ImpliedByPrincipal(LdapName name, Principal userPrincipal) {
+               this.name = name;
+               if (userPrincipal != null)
+                       causes.add(userPrincipal);
+       }
+
+       @Override
+       public String getName() {
+               return name.toString();
+       }
+
+       @Override
+       public boolean addMember(Principal user) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public boolean removeMember(Principal user) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public boolean isMember(Principal member) {
+               return causes.contains(member);
+       }
+
+       @Override
+       public Enumeration<? extends Principal> members() {
+               return Collections.enumeration(causes);
+       }
+
+       @Override
+       public int hashCode() {
+               return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               // if (this == obj)
+               // return true;
+               if (obj instanceof ImpliedByPrincipal) {
+                       ImpliedByPrincipal that = (ImpliedByPrincipal) obj;
+                       // TODO check members too?
+                       return name.equals(that.name);
+               }
+               return false;
+       }
+
+       @Override
+       public String toString() {
+               return name.toString() + ", implied by " + causes;
+       }
+}
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
new file mode 100644 (file)
index 0000000..dea6048
--- /dev/null
@@ -0,0 +1,199 @@
+package org.argeo.cms.internal.auth;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+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.login.CredentialNotFoundException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.KernelHeader;
+import org.argeo.cms.internal.kernel.Activator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+public class UserAdminLoginModule implements LoginModule {
+       private Subject subject;
+       private CallbackHandler callbackHandler;
+       private boolean isAnonymous = false;
+
+       private final static LdapName ROLE_USER_NAME, ROLE_ANONYMOUS_NAME;
+       private final static X500Principal ROLE_ANONYMOUS_PRINCIPAL;
+       static {
+               try {
+                       ROLE_USER_NAME = new LdapName(KernelHeader.ROLE_USER);
+                       ROLE_ANONYMOUS_NAME = new LdapName(KernelHeader.ROLE_ANONYMOUS);
+                       ROLE_ANONYMOUS_PRINCIPAL = new X500Principal(
+                                       ROLE_ANONYMOUS_NAME.toString());
+               } catch (InvalidNameException e) {
+                       throw new Error("Cannot initialize login module class", e);
+               }
+       }
+
+       private Authorization authorization;
+
+       @Override
+       public void initialize(Subject subject, CallbackHandler callbackHandler,
+                       Map<String, ?> sharedState, Map<String, ?> options) {
+               try {
+                       this.subject = subject;
+                       this.callbackHandler = callbackHandler;
+                       if (options.containsKey("anonymous"))
+                               isAnonymous = Boolean.parseBoolean(options.get("anonymous")
+                                               .toString());
+                       // String ldifFile = options.get("ldifFile").toString();
+                       // InputStream in = new URL(ldifFile).openStream();
+                       // userAdmin = new LdifUserAdmin(in);
+               } catch (Exception e) {
+                       throw new CmsException("Cannot initialize login module", e);
+               }
+       }
+
+       @Override
+       public boolean login() throws LoginException {
+               // TODO use a callback in order to get the bundle context
+               BundleContext bc = Activator.getBundleContext();
+               UserAdmin userAdmin = bc.getService(bc
+                               .getServiceReference(UserAdmin.class));
+               final User user;
+
+               if (!isAnonymous) {
+                       // ask for username and password
+                       NameCallback nameCallback = new NameCallback("User");
+                       PasswordCallback passwordCallback = new PasswordCallback(
+                                       "Password", false);
+                       // handle callbacks
+                       try {
+                               callbackHandler.handle(new Callback[] { nameCallback,
+                                               passwordCallback });
+                       } catch (Exception e) {
+                               throw new CmsException("Cannot handle callbacks", e);
+                       }
+
+                       // create credentials
+                       final String username = nameCallback.getName();
+                       if (username == null || username.trim().equals(""))
+                               throw new CredentialNotFoundException("No credentials provided");
+
+                       char[] password = {};
+                       if (passwordCallback.getPassword() != null)
+                               password = passwordCallback.getPassword();
+                       else
+                               throw new CredentialNotFoundException("No credentials provided");
+
+                       user = (User) userAdmin.getRole(username);
+                       if (user == null)
+                               return false;
+
+                       byte[] hashedPassword = ("{SHA}" + Base64
+                                       .encodeBase64String(DigestUtils.sha1(toBytes(password))))
+                                       .getBytes();
+                       if (!user.hasCredential("userpassword", hashedPassword))
+                               return false;
+               } else
+                       // anonymous
+                       user = null;
+               this.authorization = userAdmin.getAuthorization(user);
+               return true;
+       }
+
+       private byte[] toBytes(char[] chars) {
+               CharBuffer charBuffer = CharBuffer.wrap(chars);
+               ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
+               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(),
+                               byteBuffer.position(), byteBuffer.limit());
+               Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+               Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+               return bytes;
+       }
+
+       @Override
+       public boolean commit() throws LoginException {
+               if (authorization != null) {
+                       Set<Principal> principals = subject.getPrincipals();
+                       try {
+                               String authName = authorization.getName();
+
+                               // determine user'S principal
+                               final LdapName name;
+                               final Principal userPrincipal;
+                               if (authName == null) {
+                                       name = ROLE_ANONYMOUS_NAME;
+                                       userPrincipal = ROLE_ANONYMOUS_PRINCIPAL;
+                                       principals.add(userPrincipal);
+                               } else {
+                                       name = new LdapName(authName);
+                                       userPrincipal = new X500Principal(name.toString());
+                                       principals.add(userPrincipal);
+                                       principals.add(new ImpliedByPrincipal(ROLE_USER_NAME,
+                                                       userPrincipal));
+                               }
+
+                               // Add roles provided by authorization
+                               for (String role : authorization.getRoles()) {
+                                       LdapName roleName = new LdapName(role);
+                                       if (ROLE_USER_NAME.equals(roleName))
+                                               throw new CmsException(ROLE_USER_NAME
+                                                               + " cannot be listed as role");
+                                       if (ROLE_ANONYMOUS_NAME.equals(roleName))
+                                               throw new CmsException(ROLE_ANONYMOUS_NAME
+                                                               + " cannot be listed as role");
+                                       if (roleName.equals(name)) {
+                                               // skip
+                                       } else {
+                                               principals.add(new ImpliedByPrincipal(roleName
+                                                               .toString(), userPrincipal));
+                                       }
+                               }
+
+                               return true;
+                       } catch (InvalidNameException e) {
+                               throw new CmsException("Cannot commit", e);
+                       }
+               } else
+                       return false;
+       }
+
+       @Override
+       public boolean abort() throws LoginException {
+               cleanUp();
+               return true;
+       }
+
+       @Override
+       public boolean logout() throws LoginException {
+               // TODO better deal with successive logout
+               if (subject == null)
+                       return true;
+               // TODO make it less brutal
+               subject.getPrincipals().removeAll(
+                               subject.getPrincipals(X500Principal.class));
+               subject.getPrincipals().removeAll(
+                               subject.getPrincipals(ImpliedByPrincipal.class));
+               cleanUp();
+               return true;
+       }
+
+       private void cleanUp() {
+               subject = null;
+               authorization = null;
+       }
+}
index af4f0ff2a75cd868d7597f42f35b63d0d9a8c9ae..83f21202e45612855b87d23d29a226479469df3a 100644 (file)
@@ -1,6 +1,7 @@
 package org.argeo.cms.internal.kernel;
 
 import java.lang.management.ManagementFactory;
+import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -47,6 +48,11 @@ final class Kernel implements ServiceListener {
        private KernelThread kernelThread;
 
        void init() {
+               URL url = getClass().getClassLoader().getResource(
+                               KernelConstants.JAAS_CONFIG);
+               System.setProperty("java.security.auth.login.config",
+                               url.toExternalForm());
+
                ClassLoader currentContextCl = Thread.currentThread()
                                .getContextClassLoader();
                Thread.currentThread().setContextClassLoader(
index 7465583f9bdd45fd84b74c829d4bdb22f02b19cc..f273242c530543ca4d2617803f87516ed489c6a3 100644 (file)
@@ -16,6 +16,10 @@ public interface KernelConstants {
        final static String REPO_SEARCH_CACHE_SIZE = "argeo.node.repo.searchCacheSize";
        final static String REPO_MAX_VOLATILE_INDEX_SIZE = "argeo.node.repo.maxVolatileIndexSize";
 
+       // Node Security
+       /** URI to an LDIF file used as initialization or backend */
+       final static String USERADMIN_URI = "argeo.node.useradmin.uri";
+
        final static String[] DEFAULT_CNDS = { "/org/argeo/jcr/argeo.cnd",
                        "/org/argeo/cms/cms.cnd" };
 
index f279ba5eab329d02ed5010ee2a848475c2d3b47d..3b5d78d897ff24f6a9ff5b2765af13eb79c1d35c 100644 (file)
@@ -10,6 +10,7 @@ import org.argeo.cms.CmsException;
 import org.argeo.cms.internal.useradmin.JcrUserAdmin;
 import org.argeo.cms.internal.useradmin.SimpleJcrSecurityModel;
 import org.argeo.cms.internal.useradmin.jackrabbit.JackrabbitUserAdminService;
+import org.argeo.osgi.useradmin.LdifUserAdmin;
 import org.argeo.security.OsAuthenticationToken;
 import org.argeo.security.UserAdminService;
 import org.argeo.security.core.InternalAuthentication;
@@ -36,7 +37,7 @@ class NodeSecurity implements AuthenticationManager {
        private final InternalAuthenticationProvider internalAuth;
        private final AnonymousAuthenticationProvider anonymousAuth;
        private final JackrabbitUserAdminService userAdminService;
-       private final JcrUserAdmin userAdmin;
+       private final LdifUserAdmin userAdmin;
 
        private ServiceRegistration<AuthenticationManager> authenticationManagerReg;
        private ServiceRegistration<UserAdminService> userAdminServiceReg;
@@ -46,11 +47,6 @@ class NodeSecurity implements AuthenticationManager {
 
        public NodeSecurity(BundleContext bundleContext, JackrabbitNode node)
                        throws RepositoryException {
-               URL url = getClass().getClassLoader().getResource(
-                               KernelConstants.JAAS_CONFIG);
-               System.setProperty("java.security.auth.login.config",
-                               url.toExternalForm());
-
                this.bundleContext = bundleContext;
 
                osAuth = new OsAuthenticationProvider();
@@ -65,8 +61,11 @@ class NodeSecurity implements AuthenticationManager {
                userAdminService.setSecurityModel(new SimpleJcrSecurityModel());
                userAdminService.init();
 
-               userAdmin = new JcrUserAdmin(bundleContext, node);
-               userAdmin.setUserAdminService(userAdminService);
+               String userAdminUri = KernelUtils
+                               .getFrameworkProp(KernelConstants.USERADMIN_URI);
+               if (userAdminUri == null)
+                       userAdminUri = getClass().getResource("demo.ldif").toString();
+               userAdmin = new LdifUserAdmin(userAdminUri);
        }
 
        public void publish() {
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/demo.ldif b/org.argeo.cms/src/org/argeo/cms/internal/kernel/demo.ldif
new file mode 100644 (file)
index 0000000..980719f
--- /dev/null
@@ -0,0 +1,68 @@
+dn: dc=example,dc=org
+objectClass: domain
+objectClass: extensibleObject
+objectClass: top
+dc: example
+
+dn: ou=groups,dc=example,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: groups
+
+dn: ou=users,dc=example,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: users
+
+dn: uid=demo,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+cn: Demo User
+description: Demo user
+givenname: Demo
+mail: demo@localhost
+sn: User
+uid: demo
+userpassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
+
+dn: uid=root,ou=users,dc=example,dc=org
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: top
+cn: Super User
+description: Superuser
+givenname: Super
+mail: root@localhost
+sn: User
+uid: root
+userpassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
+
+dn: cn=admin,ou=system,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: uid=root,ou=users,dc=example,dc=org
+
+dn: cn=userAdmin,ou=system,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: userAdmin
+member: cn=admin,ou=system,ou=node
+member: uid=demo,ou=users,dc=example,dc=org
+
+dn: cn=groupAdmin,ou=system,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: groupAdmin
+member: cn=admin,ou=system,ou=node
+
+dn: cn=editor,ou=cms,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: cn=admin,ou=system,ou=node
+member: uid=demo,ou=users,dc=example,dc=org
+
index c8033b1bd7bf070ef605d17ee48005beb47f2288..0e76f376a3793a5a3175bbe591c946d6c9a7a616 100644 (file)
@@ -1,14 +1,28 @@
 USER {
+    org.argeo.cms.internal.auth.UserAdminLoginModule requisite;
+};
+
+OLD_USER {
     org.argeo.cms.internal.auth.EndUserLoginModule requisite;
     org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
 };
 
 ANONYMOUS {
+    org.argeo.cms.internal.auth.UserAdminLoginModule requisite anonymous=true;
+};
+
+OLD_ANONYMOUS {
     org.argeo.cms.internal.auth.AnonymousLoginModule requisite;
     org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
 };
 
 SYSTEM {
+   org.argeo.cms.internal.auth.SystemLoginModule requisite;
+   org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
+   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
+
+OLD_SYSTEM {
     org.argeo.cms.internal.auth.SystemLoginModule requisite;
     org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
 };
@@ -22,3 +36,8 @@ SINGLE_USER {
     org.argeo.cms.internal.auth.SingleUserLoginModule requisite;
     org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
 };
+
+Jackrabbit {
+   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
+
index 4c4875f1c62525474fc744e9df3b700324b845b9..981dff473dcac353fd39b8ee9cc770162a64b455 100644 (file)
@@ -76,7 +76,7 @@
                </SecurityManager>
                <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager">
                </AccessManager>
-               <LoginModule class="org.argeo.security.jackrabbit.ArgeoLoginModule">
-               </LoginModule>
+<!--           <LoginModule class="org.argeo.security.jackrabbit.ArgeoLoginModule"> -->
+<!--           </LoginModule> -->
        </Security>
 </Repository>
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/util/CurrentUserUtils.java b/org.argeo.cms/src/org/argeo/cms/util/CurrentUserUtils.java
new file mode 100644 (file)
index 0000000..a47cad0
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.util;
+
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.ArgeoException;
+import org.argeo.cms.CmsSession;
+
+/**
+ * Retrieves information about the current user. Not an API, can change without
+ * notice.
+ */
+class CurrentUserUtils {
+       public final static String getUsername() {
+               Subject subject = getSubject();
+               if (subject == null)
+                       return null;
+               Principal principal = subject.getPrincipals(X500Principal.class)
+                               .iterator().next();
+               return principal.getName();
+
+       }
+
+       public final static Set<String> roles() {
+               Set<String> roles = Collections.synchronizedSet(new HashSet<String>());
+               // roles.add("ROLE_USER");
+               Subject subject = getSubject();
+               X500Principal userPrincipal = subject
+                               .getPrincipals(X500Principal.class).iterator().next();
+               roles.add(userPrincipal.getName());
+               for (Principal group : subject.getPrincipals(Group.class)) {
+                       roles.add(group.getName());
+               }
+               return roles;
+       }
+
+       public final static Subject getSubject() {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               if (subject == null) {
+                       subject = CmsSession.current.get().getSubject();
+                       if (subject == null)
+                               throw new ArgeoException("Not authenticated.");
+               }
+               return subject;
+       }
+}
index ad47d1850b34fb3d767d5aaa60e804e629dcd454..1c35600012d9c47e33d031e376bb1d98d627310f 100644 (file)
@@ -32,8 +32,6 @@ import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Text;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
 
 /** The site-related user menu */
 public class UserMenu extends Shell implements CmsStyles, CallbackHandler {
@@ -44,13 +42,13 @@ public class UserMenu extends Shell implements CmsStyles, CallbackHandler {
                super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
                setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU);
 
-               Authentication authentication = SecurityContextHolder.getContext()
-                               .getAuthentication();
-               if (authentication == null)
-                       throw new CmsException("No authentication available");
+               // Authentication authentication = SecurityContextHolder.getContext()
+               // .getAuthentication();
+               // if (authentication == null)
+               // throw new CmsException("No authentication available");
 
-               String username = authentication.getName();
-               if (username.equals(KernelHeader.USERNAME_ANONYMOUS)) {
+               String username = CurrentUserUtils.getUsername();
+               if (username.equalsIgnoreCase(KernelHeader.ROLE_ANONYMOUS)) {
                        username = null;
                        anonymousUi();
                } else {
@@ -107,11 +105,11 @@ public class UserMenu extends Shell implements CmsStyles, CallbackHandler {
                });
        }
 
-       protected String getUsername() {
-               String username = SecurityContextHolder.getContext()
-                               .getAuthentication().getName();
-               return username;
-       }
+       // protected String getUsername() {
+       // // String username = SecurityContextHolder.getContext()
+       // // .getAuthentication().getName();
+       // return CurrentUserUtils.getUsername();
+       // }
 
        /** To be overridden */
        protected void specificUserUi(Composite parent) {
index 689318137fa06f8102a6e85973a59a0c17a048e9..0dbb4ac5adf0429e678a1947f2ca4a339e08056f 100644 (file)
@@ -12,7 +12,6 @@ import org.eclipse.swt.events.MouseListener;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Label;
-import org.springframework.security.core.context.SecurityContextHolder;
 
 /** Open the user menu when clicked */
 public class UserMenuLink extends MenuLink {
@@ -23,9 +22,10 @@ public class UserMenuLink extends MenuLink {
 
        @Override
        public Control createUi(Composite parent, Node context) {
-               String username = SecurityContextHolder.getContext()
-                               .getAuthentication().getName();
-               if (username.equals(KernelHeader.USERNAME_ANONYMOUS))
+               // String username = SecurityContextHolder.getContext()
+               // .getAuthentication().getName();
+               String username = CurrentUserUtils.getUsername();
+               if (username.equalsIgnoreCase(KernelHeader.ROLE_ANONYMOUS))
                        setLabel(CmsMsg.login.lead());
                else
                        setLabel(username);
index 5e281b29a3ed22b75e3a285594fa73e1a4a2bcfc..a4292c261751d60d1416e44cce5f49523bd08aa2 100644 (file)
        <!-- Reduce visibility of JCR Browser perspective to users that are in ROLE_ADMIN -->   
        <extension
        point="org.eclipse.ui.activities">
+       <!--
                <activity
                        description="Only for admins"
                        id="org.argeo.eclipse.ui.workbench.adminActivity"
                                </with>
                        </enabledWhen>
                </activity>
+               -->
         <activityPatternBinding
                        pattern="org.argeo.eclipse.ui.workbench/org.argeo.eclipse.ui.workbench.osgiPerspective"
                        isEqualityPattern="true"
-                       activityId="org.argeo.eclipse.ui.workbench.adminActivity">
+                       activityId="org.argeo.security.ui.adminActivity">
                        <!-- activityId="org.argeo.security.ui.adminActivity" -->
         </activityPatternBinding>
         <activityPatternBinding
                        pattern="org.argeo.eclipse.ui.workbench/org.argeo.eclipse.ui.workbench.jcrBrowserPerspective"
                        isEqualityPattern="true"
-                       activityId="org.argeo.eclipse.ui.workbench.adminActivity">
+                       activityId="org.argeo.security.ui.adminActivity">
                </activityPatternBinding>
        </extension>
 </plugin>
\ No newline at end of file
index 4ae2434b4ceb24381d0eac5ec4063710bfe80e53..8f167c3683c58f7e7537194f50b14881cd6aaae9 100644 (file)
@@ -15,6 +15,8 @@ public class LdifAuthorization implements Authorization {
 
        @Override
        public String getName() {
+               if (user == null)
+                       return null;
                return user.getName();
        }
 
@@ -30,16 +32,20 @@ public class LdifAuthorization implements Authorization {
        @Override
        public String[] getRoles() {
                List<Role> allRoles = getAllRoles();
-               String[] res = new String[allRoles.size() + 1];
-               res[0] = user.getName();
+               if (user != null)
+                       allRoles.add(0, user);
+               String[] res = new String[allRoles.size()];
                for (int i = 0; i < allRoles.size(); i++)
-                       res[i + 1] = allRoles.get(i).getName();
+                       res[i] = allRoles.get(i).getName();
                return res;
        }
 
        List<Role> getAllRoles() {
                List<Role> allRoles = new ArrayList<Role>();
-               collectRoles(user, allRoles);
+               if (user != null)
+                       collectRoles(user, allRoles);
+               else
+                       collectAnonymousRoles(allRoles);
                return allRoles;
        }
 
@@ -51,4 +57,8 @@ public class LdifAuthorization implements Authorization {
                }
        }
 
+       private void collectAnonymousRoles(List<Role> allRoles) {
+               // TODO gather anonymous roles
+       }
+
 }
index 8cc7cb39581f5a8f4d50c47919e828969369fb31..e2cf903fca2c17a959c3e065c9927d097e684fdf 100644 (file)
@@ -1,6 +1,8 @@
 package org.argeo.osgi.useradmin;
 
 import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -15,11 +17,44 @@ import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
 import org.osgi.service.useradmin.UserAdmin;
 
+/** User admin implementation using LDIF file(s) as backend. */
 public class LdifUserAdmin implements UserAdmin {
        SortedMap<LdapName, LdifUser> users = new TreeMap<LdapName, LdifUser>();
        SortedMap<LdapName, LdifGroup> groups = new TreeMap<LdapName, LdifGroup>();
 
+       private final boolean isReadOnly;
+       private final URI uri;
+
+       public LdifUserAdmin(String uri) {
+               this(uri, true);
+       }
+
+       public LdifUserAdmin(String uri, boolean isReadOnly) {
+               this.isReadOnly = isReadOnly;
+               try {
+                       this.uri = new URI(uri);
+               } catch (URISyntaxException e) {
+                       throw new ArgeoUserAdminException("Invalid URI " + uri, e);
+               }
+
+               if (!isReadOnly && !this.uri.getScheme().equals("file:"))
+                       throw new UnsupportedOperationException(this.uri.getScheme()
+                                       + "not supported read-write.");
+
+               try {
+                       load(this.uri.toURL().openStream());
+               } catch (Exception e) {
+                       throw new ArgeoUserAdminException("Cannot open URL " + this.uri, e);
+               }
+       }
+
        public LdifUserAdmin(InputStream in) {
+               load(in);
+               isReadOnly = true;
+               this.uri = null;
+       }
+
+       protected void load(InputStream in) {
                try {
                        LdifParser ldifParser = new LdifParser();
                        SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
@@ -45,10 +80,17 @@ public class LdifUserAdmin implements UserAdmin {
                        }
                } catch (Exception e) {
                        throw new ArgeoUserAdminException(
-                                       "Cannot initialise user admin service from LDIF", e);
+                                       "Cannot load user admin service from LDIF", e);
                }
        }
 
+       public void destroy() {
+               users.clear();
+               users = null;
+               groups.clear();
+               groups = null;
+       }
+
        @Override
        public Role getRole(String name) {
                LdapName key;
@@ -92,4 +134,8 @@ public class LdifUserAdmin implements UserAdmin {
                throw new UnsupportedOperationException();
        }
 
+       public boolean getIsReadOnly() {
+               return isReadOnly;
+       }
+
 }
index f9f1a39d85d66c7198c956bbecfd2cad76b330cd..f103f9cb11f178a5f948b560310eb9d960400ab1 100644 (file)
@@ -1,9 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="src" path="src" />
-       <classpathentry kind="con"
-               path="org.eclipse.pde.core.requiredPlugins" />
-       <classpathentry kind="con"
-               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7" />
-       <classpathentry kind="output" path="bin" />
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="src" path="ext/test"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+       <classpathentry kind="output" path="bin"/>
 </classpath>
index 30f715358d984db427238bb1984c0d19b6ca4004..8103225a3488347b77b7fe2a8e154d22595d0cde 100644 (file)
@@ -1 +1,24 @@
-source.. = src/
+source.. = src/,\
+           ext/test/
+
+additional.bundles = org.junit,\
+                     org.apache.jackrabbit.core,\
+                     javax.jcr,\
+                     org.apache.jackrabbit.api,\
+                     org.apache.jackrabbit.data,\
+                     org.apache.jackrabbit.jcr.commons,\
+                     org.apache.jackrabbit.spi,\
+                     org.apache.jackrabbit.spi.commons,\
+                     org.slf4j.api,\
+                     org.slf4j.commons.logging,\
+                     org.slf4j.log4j12,\
+                     org.apache.log4j,\
+                     org.apache.commons.collections,\
+                     EDU.oswego.cs.dl.util.concurrent,\
+                     org.apache.lucene,\
+                     org.apache.tika.core,\
+                     org.apache.tika.parsers,\
+                     org.apache.commons.dbcp,\
+                     org.apache.commons.pool,\
+                     org.argeo.server.jcr
+
diff --git a/org.argeo.security.jackrabbit/ext/test/log4j.properties b/org.argeo.security.jackrabbit/ext/test/log4j.properties
new file mode 100644 (file)
index 0000000..b4edd7c
--- /dev/null
@@ -0,0 +1,17 @@
+log4j.rootLogger=WARN, console
+
+## Levels
+log4j.logger.org.argeo=DEBUG
+log4j.logger.org.apache.jackrabbit=OFF
+log4j.logger.org.apache.jackrabbit.core.security=DEBUG
+log4j.logger.org.apache.jackrabbit.core.DefaultSecurityManager=DEBUG
+
+## Appenders
+# console is set to be a ConsoleAppender.
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+
+# console uses PatternLayout.
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+#log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c%n
+#log4j.appender.console.layout.ConversionPattern=%m%n
+log4j.appender.console.layout.ConversionPattern=%d{ABSOLUTE} %m (%F:%L) [%t] %p %n
diff --git a/org.argeo.security.jackrabbit/ext/test/org/argeo/security/jackrabbit/JackrabbitAuthTest.java b/org.argeo.security.jackrabbit/ext/test/org/argeo/security/jackrabbit/JackrabbitAuthTest.java
new file mode 100644 (file)
index 0000000..45a6567
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.security.jackrabbit;
+
+import java.net.URL;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.jackrabbit.unit.AbstractJackrabbitTestCase;
+
+public class JackrabbitAuthTest extends AbstractJackrabbitTestCase {
+       private final Log log = LogFactory.getLog(JackrabbitAuthTest.class);
+
+       public void testLogin() throws Exception {
+               // Subject subject = new Subject();
+               // LoginContext loginContext = new LoginContext("UNIX",subject);
+               // loginContext.login();
+
+               Repository repository = getRepository();
+               Session session = repository.login();
+               log.debug(session.getUserID());
+       }
+
+       @Override
+       protected Repository createRepository() throws Exception {
+               URL url = getClass().getResource("test_jaas.config");
+               System.setProperty("java.security.auth.login.config", url.toString());
+               return super.createRepository();
+       }
+
+       @Override
+       protected void clearRepository(Repository repository) throws Exception {
+               System.setProperty("java.security.auth.login.config", "");
+       }
+
+       @Override
+       protected String getRepositoryConfigResource() {
+               return "/org/argeo/security/jackrabbit/repository-memory-test.xml";
+       }
+
+}
diff --git a/org.argeo.security.jackrabbit/ext/test/org/argeo/security/jackrabbit/repository-memory-test.xml b/org.argeo.security.jackrabbit/ext/test/org/argeo/security/jackrabbit/repository-memory-test.xml
new file mode 100644 (file)
index 0000000..e285555
--- /dev/null
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright (C) 2007-2012 Argeo GmbH
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+       <!-- File system and datastore -->
+       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+       <!-- Workspace templates -->
+       <Workspaces rootPath="${rep.home}/workspaces"
+               defaultWorkspace="main" configRootPath="/workspaces" />
+       <Workspace name="${wsp.name}">
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+                       <param name="blobFSBlockSize" value="1" />
+               </PersistenceManager>
+               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+                       <param name="path" value="${rep.home}/repository/index" />
+                       <param name="directoryManagerClass"
+                               value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+                       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               </SearchIndex>
+       </Workspace>
+
+       <!-- Versioning -->
+       <Versioning rootPath="${rep.home}/version">
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+                       <param name="blobFSBlockSize" value="1" />
+               </PersistenceManager>
+       </Versioning>
+
+       <!-- Indexing -->
+       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+               <param name="path" value="${rep.home}/repository/index" />
+               <param name="directoryManagerClass"
+                       value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+       </SearchIndex>
+
+       <!-- Security -->
+       <Security appName="Jackrabbit">
+               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+                       workspaceName="security"/>
+               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager"/>
+       </Security>
+</Repository>
\ No newline at end of file
diff --git a/org.argeo.security.jackrabbit/ext/test/org/argeo/security/jackrabbit/test_jaas.config b/org.argeo.security.jackrabbit/ext/test/org/argeo/security/jackrabbit/test_jaas.config
new file mode 100644 (file)
index 0000000..3b4ee81
--- /dev/null
@@ -0,0 +1,3 @@
+Jackrabbit {
+   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
index c4f95458ba4ece82d1449000f69272cf52ea432d..0d9d980e0022382d89aaabfec79ba59ea70bb597 100644 (file)
@@ -21,6 +21,7 @@ import java.util.Set;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
 
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.core.DefaultSecurityManager;
@@ -28,8 +29,6 @@ import org.apache.jackrabbit.core.security.AMContext;
 import org.apache.jackrabbit.core.security.AccessManager;
 import org.apache.jackrabbit.core.security.SecurityConstants;
 import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
 
 /** Integrates Spring Security and Jackrabbit Security users and roles. */
 public class ArgeoSecurityManager extends DefaultSecurityManager {
@@ -57,12 +56,20 @@ public class ArgeoSecurityManager extends DefaultSecurityManager {
        @Override
        public String getUserID(Subject subject, String workspaceName)
                        throws RepositoryException {
-               Authentication authentication = SecurityContextHolder.getContext()
-                               .getAuthentication();
-               if (authentication != null)
-                       return authentication.getName();
-               else
+               Set<X500Principal> userPrincipal = subject
+                               .getPrincipals(X500Principal.class);
+               if (userPrincipal.isEmpty())
                        return super.getUserID(subject, workspaceName);
+               if (userPrincipal.size() > 1)
+                       throw new RuntimeException("Multiple user principals "
+                                       + userPrincipal);
+               return userPrincipal.iterator().next().getName();
+               // Authentication authentication = SecurityContextHolder.getContext()
+               // .getAuthentication();
+               // if (authentication != null)
+               // return authentication.getName();
+               // else
+               // return super.getUserID(subject, workspaceName);
        }
 
        @Override
diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java
new file mode 100644 (file)
index 0000000..466402d
--- /dev/null
@@ -0,0 +1,72 @@
+package org.argeo.security.jackrabbit;
+
+import java.security.Principal;
+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 org.apache.jackrabbit.core.security.AnonymousPrincipal;
+import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
+
+public class SystemJackrabbitLoginModule implements LoginModule {
+
+       private Subject subject;
+
+       @Override
+       public void initialize(Subject subject, CallbackHandler callbackHandler,
+                       Map<String, ?> sharedState, Map<String, ?> options) {
+               this.subject = subject;
+       }
+
+       @Override
+       public boolean login() throws LoginException {
+               return true;
+       }
+
+       @Override
+       public boolean commit() throws LoginException {
+               Set<Principal> principals = subject.getPrincipals();
+               if (principals.isEmpty()) {// system
+                       subject.getPrincipals().add(new AdminPrincipal("admin"));
+                       return true;
+               }
+               boolean isAdmin = false;
+               boolean isAnonymous = false;
+               // FIXME make it more generic
+               for (Principal principal : principals) {
+                       if (principal.getName().equalsIgnoreCase(
+                                       "cn=admin,ou=system,ou=node"))
+                               isAdmin = true;
+                       else if (principal.getName().equalsIgnoreCase(
+                                       "cn=anonymous,ou=system,ou=node"))
+                               isAnonymous = true;
+               }
+
+               if (isAnonymous && isAdmin)
+                       throw new LoginException("Cannot be admin and anonymous");
+
+               // Add special Jackrabbit roles
+               if (isAdmin)
+                       principals.add(new AdminPrincipal("admin"));
+               if (isAnonymous)// anonymous
+                       principals.add(new AnonymousPrincipal());
+               return true;
+       }
+
+       @Override
+       public boolean abort() throws LoginException {
+               return true;
+       }
+
+       @Override
+       public boolean logout() throws LoginException {
+               subject.getPrincipals().removeAll(
+                               subject.getPrincipals(AdminPrincipal.class));
+               return true;
+       }
+
+}
index 84df522b00c8156f75514bad6f51b343ee5cd575..68d88f2406b2acd3fcdc865fd1a95e0e315a7dc5 100644 (file)
                  <enabledWhen>
                    <with variable="roles">
                      <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_ANONYMOUS" />
+                       <equals value="cn=anonymous,ou=system,ou=node" />
                      </iterate>
                    </with>
                  </enabledWhen>
                        <not>
                    <with variable="roles">
                      <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_ANONYMOUS" />
+                       <equals value="cn=anonymous,ou=system,ou=node" />
                      </iterate>
                    </with>
                    </not>
index 561a52079296804f4d74bb5dc6d1daf6c2b788e2..fb885377fed8e64e762fe6b24247eeda4176e265 100644 (file)
@@ -22,6 +22,7 @@ import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.login.CredentialNotFoundException;
 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;
@@ -37,8 +38,6 @@ import org.eclipse.rap.rwt.application.EntryPoint;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.PlatformUI;
 import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
 
 /**
  * RAP entry point with login capabilities. Once the user has been
@@ -96,10 +95,10 @@ public class SecureEntryPoint implements EntryPoint {
                        throw new ArgeoException("Cannot initialize login context", e1);
                }
 
-               tryLogin: while (subject.getPrincipals(Authentication.class).size() == 0) {
+               tryLogin: while (subject.getPrincipals(X500Principal.class).size() == 0) {
                        try {
                                loginContext.login();
-                               if (subject.getPrincipals(Authentication.class).size() == 0)
+                               if (subject.getPrincipals(X500Principal.class).size() == 0)
                                        throw new ArgeoException("Login succeeded but no auth");// fatal
 
                                // add security context to session
@@ -131,7 +130,7 @@ public class SecureEntryPoint implements EntryPoint {
                        }
                }
 
-               final String username = subject.getPrincipals(Authentication.class)
+               final String username = subject.getPrincipals(X500Principal.class)
                                .iterator().next().getName();
                // Logout callback when the display is disposed
                display.disposeExec(new Runnable() {
@@ -218,7 +217,7 @@ public class SecureEntryPoint implements EntryPoint {
        private void fullLogout(LoginContext loginContext, String username) {
                try {
                        loginContext.logout();
-                       SecurityContextHolder.clearContext();
+                       // SecurityContextHolder.clearContext();
 
                        // HttpServletRequest httpRequest = RWT.getRequest();
                        // HttpSession httpSession = httpRequest.getSession();
index a1e1e9bdf18f7903bd1b0c258d40da25a7555d2b..6978b3bd657c4bd93eefe201aaabaa56da794011 100644 (file)
@@ -59,7 +59,7 @@
                  <enabledWhen>
                    <with variable="roles">
                      <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_USER" />
+                       <equals value="cn=user,ou=system,ou=node" />
                      </iterate>
                    </with>
                  </enabledWhen>
@@ -71,7 +71,7 @@
                  <enabledWhen>
                    <with variable="roles">
                      <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_ADMIN" />
+                       <equals value="cn=admin,ou=system,ou=node" />
                      </iterate>
                    </with>
                  </enabledWhen>
@@ -83,7 +83,7 @@
                  <enabledWhen>
                    <with variable="roles">
                      <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_USER_ADMIN" />
+                       <equals value="cn=userAdmin,ou=system,ou=node" />
                      </iterate>
                    </with>
                  </enabledWhen>
@@ -95,7 +95,7 @@
                  <enabledWhen>
                    <with variable="roles">
                      <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_GROUP_ADMIN" />
+                       <equals value="cn=groupAdmin,ou=system,ou=node" />
                      </iterate>
                    </with>
                  </enabledWhen>
                        <not>
                    <with variable="roles">
                      <iterate ifEmpty="false" operator="or">
-                       <equals value="ROLE_ADMIN" />
+                       <equals value="cn=admin,ou=system,ou=node" />
                      </iterate>
                    </with>
                        </not>
index b7287f66c97efe7e19ca3765c6a7e4540dbae558..f5a242d3a4a514420b3b560d60ba848921c0364a 100644 (file)
  */
 package org.argeo.security.ui.internal;
 
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.acl.Group;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.ArgeoException;
 
 /**
  * Retrieves information about the current user. Not an API, can change without
  * notice.
  */
 public class CurrentUser {
-       // public final static String getUsername() {
-       // Subject subject = getSubject();
-       // if (subject == null)
-       // return null;
-       // Principal principal = subject.getPrincipals().iterator().next();
-       // return principal.getName();
-       //
-       // }
-
        public final static String getUsername() {
-               return getAuthentication().getName();
+               Subject subject = getSubject();
+               if (subject == null)
+                       return null;
+               Principal principal = subject.getPrincipals(X500Principal.class)
+                               .iterator().next();
+               return principal.getName();
+
        }
 
        public final static Set<String> roles() {
                Set<String> roles = Collections.synchronizedSet(new HashSet<String>());
-               Authentication authentication = getAuthentication();
-               for (GrantedAuthority ga : authentication.getAuthorities()) {
-                       roles.add(ga.getAuthority());
+               // roles.add("ROLE_USER");
+               Subject subject = getSubject();
+               X500Principal userPrincipal = subject
+                               .getPrincipals(X500Principal.class).iterator().next();
+               roles.add(userPrincipal.getName());
+               for (Principal group : subject.getPrincipals(Group.class)) {
+                       roles.add(group.getName());
                }
-               return Collections.unmodifiableSet(roles);
+               return roles;
        }
 
-       public final static Authentication getAuthentication() {
-               return SecurityContextHolder.getContext().getAuthentication();
-       }
+       // public final static String getUsername() {
+       // return getAuthentication().getName();
+       // }
+
+       // public final static Set<String> roles() {
+       // Set<String> roles = Collections.synchronizedSet(new HashSet<String>());
+       // Authentication authentication = getAuthentication();
+       // for (GrantedAuthority ga : authentication.getAuthorities()) {
+       // roles.add(ga.getAuthority());
+       // }
+       // return Collections.unmodifiableSet(roles);
+       // }
+       //
+       // public final static Authentication getAuthentication() {
+       // return SecurityContextHolder.getContext().getAuthentication();
+       // }
 
        // public final static Authentication getAuthentication() {
        // Set<Authentication> authens = getSubject().getPrincipals(
@@ -65,10 +83,10 @@ public class CurrentUser {
        // throw new ArgeoException("No authentication found");
        // }
 
-       // public final static Subject getSubject() {
-       // Subject subject = Subject.getSubject(AccessController.getContext());
-       // if (subject == null)
-       // throw new ArgeoException("Not authenticated.");
-       // return subject;
-       // }
+       public final static Subject getSubject() {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               if (subject == null)
+                       throw new ArgeoException("Not authenticated.");
+               return subject;
+       }
 }
index 590fb35d6bda2a8a3ebd406ed2fdd3d280aa58bf..dd7f6cdd297e182b86dbf974beb7350936249c85 100644 (file)
@@ -42,9 +42,9 @@ public class UserProfile extends ViewPart {
        public void createPartControl(Composite parent) {
                parent.setLayout(new GridLayout(2, false));
 
-               Authentication authentication = CurrentUser.getAuthentication();
-               EclipseUiUtils.createGridLL(parent, "Name", authentication
-                               .getPrincipal().toString());
+//             Authentication authentication = CurrentUser.getAuthentication();
+//             EclipseUiUtils.createGridLL(parent, "Name", authentication
+//                             .getPrincipal().toString());
                EclipseUiUtils.createGridLL(parent, "User ID",
                                CurrentUser.getUsername());
 
index f132db9f97e0cdd05792ee700200a31e1f55bf94..943d48efef73181ddf6a2343f5c9794ce2505fa2 100644 (file)
@@ -20,6 +20,5 @@ additional.bundles = org.junit,\
                      org.apache.lucene,\
                      org.apache.tika.core,\
                      org.apache.tika.parsers,\
-                     org.argeo.security.jackrabbit,\
                      org.apache.commons.dbcp,\
                      org.apache.commons.pool
index f1e55230869b1bad8dd9c673d90330044c6719c8..d3f35b7560354c4d8693ab53a7d5d9aa131a186e 100644 (file)
@@ -37,12 +37,16 @@ public abstract class AbstractJackrabbitTestCase extends AbstractJcrTestCase {
                // getHomeDir());
                RepositoryConfig repositoryConfig = RepositoryConfig.create(
                                AbstractJackrabbitTestCase.class
-                                               .getResourceAsStream("repository-memory.xml"),
+                                               .getResourceAsStream(getRepositoryConfigResource()),
                                getHomeDir().getAbsolutePath());
                RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig);
                return repositoryImpl;
        }
 
+       protected String getRepositoryConfigResource() {
+               return "repository-memory.xml";
+       }
+
        @Override
        protected void clearRepository(Repository repository) throws Exception {
                RepositoryImpl repositoryImpl = (RepositoryImpl) repository;