New project conventions
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 25 Nov 2014 13:16:18 +0000 (13:16 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 25 Nov 2014 13:16:18 +0000 (13:16 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@7531 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

17 files changed:
org.argeo.security.core/build.properties
org.argeo.security.jackrabbit/.classpath
org.argeo.security.jackrabbit/bnd.bnd [new file with mode: 0644]
org.argeo.security.jackrabbit/build.properties
org.argeo.security.jackrabbit/pom.xml
org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java [deleted file]
org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java [deleted file]
org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java [deleted file]
org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java [deleted file]
org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java [deleted file]
org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java [deleted file]
org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java [new file with mode: 0644]
org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoLoginModule.java [new file with mode: 0644]
org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java [new file with mode: 0644]
org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java [new file with mode: 0644]
org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java [new file with mode: 0644]
org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java [new file with mode: 0644]

index c988eeb5db5dea3582d9474f22569b49d7ccf1cc..a25d2eaecbf6c32b2948641c93fa097d30d536fa 100644 (file)
@@ -9,4 +9,3 @@ additional.bundles = org.springframework.transaction,\
                      slf4j.org.apache.commons.logging
 source.. = src/,\
            ext/test/
-bin.. = bin/
index 5641c7ca39f6826ec4ff5af926bfd34f528f3684..d2953a684d400476bf716ed6a6c3c3ae486fcd0e 100644 (file)
@@ -1,7 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="src" path="src/main/java"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>>>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="output" path="target/classes"/>
+       <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.6" />
+       <classpathentry kind="output" path="bin" />
 </classpath>
diff --git a/org.argeo.security.jackrabbit/bnd.bnd b/org.argeo.security.jackrabbit/bnd.bnd
new file mode 100644 (file)
index 0000000..6cd12f8
--- /dev/null
@@ -0,0 +1,4 @@
+Fragment-Host: org.apache.jackrabbit
+Import-Package: org.springframework.core,\
+org.argeo.jcr,\
+*
index 5fc538bc83f35ba123c9f84f2eb6124dd65c8789..30f715358d984db427238bb1984c0d19b6ca4004 100644 (file)
@@ -1,4 +1 @@
-source.. = src/main/java/
-output.. = target/classes/
-bin.includes = META-INF/,\
-               .
+source.. = src/
index a18c453dddc6b3f98fe6dfbdd327546985e23766..d615194b8a5f9a7cb3f5a45b247cb1959701ed6e 100644 (file)
        </parent>
        <artifactId>org.argeo.security.jackrabbit</artifactId>
        <name>Commons Security Jackrabbit</name>
-       <build>
-               <plugins>
-                       <plugin>
-                               <groupId>org.apache.maven.plugins</groupId>
-                               <artifactId>maven-compiler-plugin</artifactId>
-                       </plugin>
-                       <plugin>
-                               <groupId>org.apache.maven.plugins</groupId>
-                               <artifactId>maven-source-plugin</artifactId>
-                       </plugin>
-                       <plugin>
-                               <groupId>org.apache.maven.plugins</groupId>
-                               <artifactId>maven-jar-plugin</artifactId>
-                       </plugin>
-                       <plugin>
-                               <groupId>org.apache.felix</groupId>
-                               <artifactId>maven-bundle-plugin</artifactId>
-
-                               <configuration>
-                                       <instructions>
-                                               <Fragment-Host>org.apache.jackrabbit</Fragment-Host>
-                                               <Export-Package>org.argeo.security.jackrabbit.*</Export-Package>
-                                               <Import-Package>
-                                                       org.springframework.core,
-                                                       org.argeo.jcr,
-                                                       *</Import-Package>
-                                       </instructions>
-                               </configuration>
-                       </plugin>
-               </plugins>
-       </build>
        <dependencies>
                <dependency>
                        <groupId>org.argeo.commons</groupId>
diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java
deleted file mode 100644 (file)
index 52ea3c9..0000000
+++ /dev/null
@@ -1,50 +0,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.
- */
-package org.argeo.security.jackrabbit;
-
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.security.Privilege;
-
-import org.apache.jackrabbit.core.id.ItemId;
-import org.apache.jackrabbit.core.security.DefaultAccessManager;
-import org.apache.jackrabbit.spi.Path;
-
-/**
- * Intermediary class in order to have a consistent naming in config files. Does
- * nothing for the time being, but may in the future.
- */
-public class ArgeoAccessManager extends DefaultAccessManager {
-
-       @Override
-       public boolean canRead(Path itemPath, ItemId itemId)
-                       throws RepositoryException {
-               return super.canRead(itemPath, itemId);
-       }
-
-       @Override
-       public Privilege[] getPrivileges(String absPath)
-                       throws PathNotFoundException, RepositoryException {
-               return super.getPrivileges(absPath);
-       }
-
-       @Override
-       public boolean hasPrivileges(String absPath, Privilege[] privileges)
-                       throws PathNotFoundException, RepositoryException {
-               return super.hasPrivileges(absPath, privileges);
-       }
-
-}
diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java
deleted file mode 100644 (file)
index 43c5440..0000000
+++ /dev/null
@@ -1,178 +0,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.
- */
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-import java.security.acl.Group;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Credentials;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginException;
-
-import org.apache.jackrabbit.core.security.AnonymousPrincipal;
-import org.apache.jackrabbit.core.security.authentication.AbstractLoginModule;
-import org.apache.jackrabbit.core.security.authentication.Authentication;
-import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-import org.argeo.security.SystemAuthentication;
-import org.springframework.security.GrantedAuthority;
-import org.springframework.security.context.SecurityContextHolder;
-import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
-
-/** Jackrabbit login mechanism based on Spring Security */
-public class ArgeoLoginModule extends AbstractLoginModule {
-       private String adminRole = "ROLE_ADMIN";
-
-       @SuppressWarnings("unused")
-       @Override
-       public boolean login() throws LoginException {
-               boolean loginOk = super.login();
-               if (!loginOk) {
-                       org.springframework.security.Authentication authen = (org.springframework.security.Authentication) SecurityContextHolder
-                                       .getContext().getAuthentication();
-               }
-               return loginOk;
-       }
-
-       @SuppressWarnings("unused")
-       @Override
-       public boolean commit() throws LoginException {
-               boolean commitOk = super.commit();
-               if (!commitOk) {
-                       org.springframework.security.Authentication authen = (org.springframework.security.Authentication) SecurityContextHolder
-                                       .getContext().getAuthentication();
-               }
-               return commitOk;
-       }
-
-       /**
-        * Returns the Spring {@link org.springframework.security.Authentication}
-        * (which can be null)
-        */
-       @Override
-       protected Principal getPrincipal(Credentials credentials) {
-               org.springframework.security.Authentication authen = SecurityContextHolder
-                               .getContext().getAuthentication();
-               return authen;
-       }
-
-       protected Set<Principal> getPrincipals() {
-               // clear already registered Jackrabbit principals
-               // clearPrincipals(AdminPrincipal.class);
-               // clearPrincipals(AnonymousPrincipal.class);
-               // clearPrincipals(GrantedAuthorityPrincipal.class);
-
-               return syncPrincipals();
-       }
-
-       protected Set<Principal> syncPrincipals() {
-               // use linked HashSet instead of HashSet in order to maintain the order
-               // of principals (as in the Subject).
-               org.springframework.security.Authentication authen = (org.springframework.security.Authentication) principal;
-
-               Set<Principal> principals = new LinkedHashSet<Principal>();
-               principals.add(authen);
-
-               if (authen instanceof SystemAuthentication) {
-                       principals.add(new AdminPrincipal(authen.getName()));
-                       principals.add(new ArgeoSystemPrincipal(authen.getName()));
-               } else if (authen instanceof AnonymousAuthenticationToken) {
-                       principals.add(new AnonymousPrincipal());
-               } else {
-                       for (GrantedAuthority ga : authen.getAuthorities()) {
-                               principals.add(new GrantedAuthorityPrincipal(ga));
-                               // FIXME: make it more generic
-                               if (adminRole.equals(ga.getAuthority()))
-                                       principals.add(new AdminPrincipal(authen.getName()));
-                       }
-               }
-
-               // remove previous credentials
-               Set<SimpleCredentials> thisCredentials = subject
-                               .getPublicCredentials(SimpleCredentials.class);
-               if (thisCredentials != null)
-                       thisCredentials.clear();
-               // override credentials since we did not used the one passed to us
-               // credentials = new SimpleCredentials(authen.getName(), authen
-               // .getCredentials().toString().toCharArray());
-
-               return principals;
-       }
-
-       /**
-        * Super implementation removes all {@link Principal}, the Spring
-        * {@link org.springframework.security.Authentication} as well. Here we
-        * simply clear Jackrabbit related {@link Principal}s.
-        */
-       @Override
-       public boolean logout() throws LoginException {
-               clearPrincipals(AdminPrincipal.class);
-               clearPrincipals(ArgeoSystemPrincipal.class);
-               clearPrincipals(AnonymousPrincipal.class);
-               clearPrincipals(GrantedAuthorityPrincipal.class);
-
-               // we resync with Spring Security since the subject may have been reused
-               // in beetween
-               // TODO: check if this is clean
-               // subject.getPrincipals().addAll(syncPrincipals());
-
-               return true;
-       }
-
-       private <T extends Principal> void clearPrincipals(Class<T> clss) {
-               Set<T> principals = subject.getPrincipals(clss);
-               if (principals != null)
-                       principals.clear();
-       }
-
-       @SuppressWarnings("rawtypes")
-       @Override
-       protected void doInit(CallbackHandler callbackHandler, Session session,
-                       Map options) throws LoginException {
-       }
-
-       @Override
-       protected boolean impersonate(Principal principal, Credentials credentials)
-                       throws RepositoryException, LoginException {
-               throw new UnsupportedOperationException(
-                               "Impersonation is not yet supported");
-       }
-
-       @Override
-       protected Authentication getAuthentication(final Principal principal,
-                       Credentials creds) throws RepositoryException {
-               if (principal instanceof Group) {
-                       return null;
-               }
-               return new Authentication() {
-                       public boolean canHandle(Credentials credentials) {
-                               return principal instanceof org.springframework.security.Authentication;
-                       }
-
-                       public boolean authenticate(Credentials credentials)
-                                       throws RepositoryException {
-                               return ((org.springframework.security.Authentication) principal)
-                                               .isAuthenticated();
-                       }
-               };
-       }
-
-}
diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java
deleted file mode 100644 (file)
index 3450c75..0000000
+++ /dev/null
@@ -1,213 +0,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.
- */
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.core.DefaultSecurityManager;
-import org.apache.jackrabbit.core.security.AMContext;
-import org.apache.jackrabbit.core.security.AccessManager;
-import org.apache.jackrabbit.core.security.AnonymousPrincipal;
-import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
-import org.springframework.security.Authentication;
-import org.springframework.security.GrantedAuthority;
-import org.springframework.security.context.SecurityContextHolder;
-
-/** Integrates Spring Security and Jackrabbit Security users and roles. */
-public class ArgeoSecurityManager extends DefaultSecurityManager {
-       /** Legacy security sync */
-       final static String PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1 = "argeo.jackarabbit.securitySync.1.1";
-
-       private final static Log log = LogFactory
-                       .getLog(ArgeoSecurityManager.class);
-
-       private static Boolean synchronize = Boolean.parseBoolean(System
-                       .getProperty(PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1, "false"));
-
-       /** TODO? use a bounded buffer */
-       private Map<String, String> userRolesCache = Collections
-                       .synchronizedMap(new HashMap<String, String>());
-
-       @Override
-       public AccessManager getAccessManager(Session session, AMContext amContext)
-                       throws RepositoryException {
-               synchronized (getSystemSession()) {
-                       return super.getAccessManager(session, amContext);
-               }
-       }
-
-       @Override
-       public UserManager getUserManager(Session session)
-                       throws RepositoryException {
-               synchronized (getSystemSession()) {
-                       return super.getUserManager(session);
-               }
-       }
-
-       /**
-        * Since this is called once when the session is created, we take the
-        * opportunity to make sure that Jackrabbit users and groups reflect Spring
-        * Security name and authorities.
-        */
-       @Override
-       public String getUserID(Subject subject, String workspaceName)
-                       throws RepositoryException {
-               if (!synchronize) {
-                       Authentication authentication = SecurityContextHolder.getContext()
-                                       .getAuthentication();
-                       if (authentication != null)
-                               return authentication.getName();
-                       else
-                               return super.getUserID(subject, workspaceName);
-               }
-
-               if (log.isTraceEnabled())
-                       log.trace(subject);
-               // skip anonymous user (no rights)
-               if (!subject.getPrincipals(AnonymousPrincipal.class).isEmpty())
-                       return super.getUserID(subject, workspaceName);
-               // skip Jackrabbit system user (all rights)
-               if (!subject.getPrincipals(ArgeoSystemPrincipal.class).isEmpty())
-                       return super.getUserID(subject, workspaceName);
-
-               // retrieve Spring authentication from JAAS
-               // TODO? use Spring Security context holder
-               Authentication authen;
-               Set<Authentication> authens = subject
-                               .getPrincipals(Authentication.class);
-               String userId = super.getUserID(subject, workspaceName);
-               if (authens.size() == 0) {
-                       // make sure that logged-in user has a Principal, useful for testing
-                       // using an admin user
-                       UserManager systemUm = getSystemUserManager(null);
-                       if (systemUm.getAuthorizable(userId) == null)
-                               systemUm.createUser(userId, "");
-               } else {// Spring Security
-                       authen = authens.iterator().next();
-
-                       if (!userId.equals(authen.getName()))
-                               log.warn("User ID is '" + userId + "' but authen is "
-                                               + authen.getName());
-                       StringBuffer roles = new StringBuffer("");
-                       GrantedAuthority[] authorities = authen.getAuthorities();
-                       for (GrantedAuthority ga : authorities) {
-                               roles.append(ga.toString());
-                       }
-
-                       // do not sync if not changed
-                       if (userRolesCache.containsKey(userId)
-                                       && userRolesCache.get(userId).equals(roles.toString()))
-                               return userId;
-
-                       // sync Spring and Jackrabbit
-                       // workspace is irrelevant here
-                       UserManager systemUm = getSystemUserManager(null);
-                       syncSpringAndJackrabbitSecurity(systemUm, authen);
-                       userRolesCache.put(userId, roles.toString());
-               }
-               return userId;
-       }
-
-       /**
-        * Make sure that the Jackrabbit security model contains this user and its
-        * granted authorities
-        */
-       static private void syncSpringAndJackrabbitSecurity(UserManager systemUm,
-                       Authentication authen) throws RepositoryException {
-               long begin = System.currentTimeMillis();
-
-               String userId = authen.getName();
-               User user = (User) systemUm.getAuthorizable(userId);
-               if (user == null) {
-                       user = systemUm.createUser(userId, authen.getCredentials()
-                                       .toString(), authen, null);
-                       log.info(userId + " added as " + user);
-               }
-
-               // process groups
-               List<String> userGroupIds = new ArrayList<String>();
-               for (GrantedAuthority ga : authen.getAuthorities()) {
-                       Group group = (Group) systemUm.getAuthorizable(ga.getAuthority());
-                       if (group == null) {
-                               group = systemUm.createGroup(ga.getAuthority());
-                               log.info(ga.getAuthority() + " added as " + group);
-                       }
-                       if (!group.isMember(user))
-                               group.addMember(user);
-                       userGroupIds.add(ga.getAuthority());
-               }
-
-               // check if user has not been removed from some groups
-               for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
-                       Group group = it.next();
-                       if (!userGroupIds.contains(group.getID()))
-                               group.removeMember(user);
-               }
-
-               if (log.isTraceEnabled())
-                       log.trace("Spring and Jackrabbit Security synchronized for user "
-                                       + userId + " in " + (System.currentTimeMillis() - begin)
-                                       + " ms");
-       }
-
-       @Override
-       protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
-               WorkspaceAccessManager wam = super
-                               .createDefaultWorkspaceAccessManager();
-               return new ArgeoWorkspaceAccessManagerImpl(wam);
-       }
-
-       private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants,
-                       WorkspaceAccessManager {
-               private final WorkspaceAccessManager wam;
-
-               public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) {
-                       super();
-                       this.wam = wam;
-               }
-
-               public void init(Session systemSession) throws RepositoryException {
-                       wam.init(systemSession);
-               }
-
-               public void close() throws RepositoryException {
-               }
-
-               public boolean grants(Set<Principal> principals, String workspaceName)
-                               throws RepositoryException {
-                       // TODO: implements finer access to workspaces
-                       return true;
-               }
-       }
-
-}
diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java
deleted file mode 100644 (file)
index e38981e..0000000
+++ /dev/null
@@ -1,50 +0,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.
- */
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-
-/** Principal for non-interactive system actions. */
-class ArgeoSystemPrincipal implements Principal {
-       private String name;
-
-       public ArgeoSystemPrincipal(String name) {
-               super();
-               this.name = name;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public int hashCode() {
-               return getName().hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof ArgeoSystemPrincipal))
-                       return false;
-               return getName().equals(((ArgeoSystemPrincipal) obj).getName());
-       }
-
-       @Override
-       public String toString() {
-               return "Argeo System (non interactive) name=" + getName();
-       }
-
-}
diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java
deleted file mode 100644 (file)
index 482214e..0000000
+++ /dev/null
@@ -1,51 +0,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.
- */
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-
-import org.springframework.security.GrantedAuthority;
-
-/** Wraps a {@link GrantedAuthority} as a principal. */
-class GrantedAuthorityPrincipal implements Principal {
-       private final GrantedAuthority grantedAuthority;
-
-       public GrantedAuthorityPrincipal(GrantedAuthority grantedAuthority) {
-               this.grantedAuthority = grantedAuthority;
-       }
-
-       public String getName() {
-               return grantedAuthority.getAuthority();
-       }
-
-       @Override
-       public int hashCode() {
-               return getName().hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof GrantedAuthorityPrincipal))
-                       return false;
-               return getName().equals(((GrantedAuthorityPrincipal) obj).getName());
-       }
-
-       @Override
-       public String toString() {
-               return "Granted Authority " + getName();
-       }
-
-}
diff --git a/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java b/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java
deleted file mode 100644 (file)
index a9985f9..0000000
+++ /dev/null
@@ -1,109 +0,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.
- */
-package org.argeo.security.jackrabbit;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.security.jcr.SimpleJcrSecurityModel;
-
-/** Make sure that user authorizable exists before syncing user directories. */
-public class JackrabbitSecurityModel extends SimpleJcrSecurityModel {
-       private final static Log log = LogFactory
-                       .getLog(JackrabbitSecurityModel.class);
-
-       @Override
-       public synchronized Node sync(Session session, String username,
-                       List<String> roles) {
-               if (!(session instanceof JackrabbitSession))
-                       return super.sync(session, username, roles);
-
-               try {
-                       UserManager userManager = ((JackrabbitSession) session)
-                                       .getUserManager();
-                       User user = (User) userManager.getAuthorizable(username);
-                       if (user != null) {
-                               String principalName = user.getPrincipal().getName();
-                               if (!principalName.equals(username)) {
-                                       log.warn("Jackrabbit principal is '" + principalName
-                                                       + "' but username is '" + username
-                                                       + "'. Recreating...");
-                                       user.remove();
-                                       user = userManager.createUser(username, "");
-                               }
-                       } else {
-                               // create new principal
-                               user = userManager.createUser(username, "");
-                               log.info(username + " added as Jackrabbit user " + user);
-                       }
-
-                       // generic JCR sync
-                       Node userProfile = super.sync(session, username, roles);
-
-                       Boolean enabled = userProfile.getProperty(ArgeoNames.ARGEO_ENABLED)
-                                       .getBoolean();
-                       if (enabled && user.isDisabled())
-                               user.disable(null);
-                       else if (!enabled && !user.isDisabled())
-                               user.disable(userProfile.getPath() + " is disabled");
-
-                       // Sync Jackrabbit roles
-                       if (roles != null)
-                               syncRoles(userManager, user, roles);
-
-                       return userProfile;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException(
-                                       "Cannot perform Jackrabbit specific operations", e);
-               }
-       }
-
-       /** Make sure Jackrabbit roles are in line with authentication */
-       void syncRoles(UserManager userManager, User user, List<String> roles)
-                       throws RepositoryException {
-               List<String> userGroupIds = new ArrayList<String>();
-               for (String role : roles) {
-                       Group group = (Group) userManager.getAuthorizable(role);
-                       if (group == null) {
-                               group = userManager.createGroup(role);
-                               log.info(role + " added as " + group);
-                       }
-                       if (!group.isMember(user))
-                               group.addMember(user);
-                       userGroupIds.add(role);
-               }
-
-               // check if user has not been removed from some groups
-               for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
-                       Group group = it.next();
-                       if (!userGroupIds.contains(group.getID()))
-                               group.removeMember(user);
-               }
-       }
-}
diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java
new file mode 100644 (file)
index 0000000..52ea3c9
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.security.jackrabbit;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.core.id.ItemId;
+import org.apache.jackrabbit.core.security.DefaultAccessManager;
+import org.apache.jackrabbit.spi.Path;
+
+/**
+ * Intermediary class in order to have a consistent naming in config files. Does
+ * nothing for the time being, but may in the future.
+ */
+public class ArgeoAccessManager extends DefaultAccessManager {
+
+       @Override
+       public boolean canRead(Path itemPath, ItemId itemId)
+                       throws RepositoryException {
+               return super.canRead(itemPath, itemId);
+       }
+
+       @Override
+       public Privilege[] getPrivileges(String absPath)
+                       throws PathNotFoundException, RepositoryException {
+               return super.getPrivileges(absPath);
+       }
+
+       @Override
+       public boolean hasPrivileges(String absPath, Privilege[] privileges)
+                       throws PathNotFoundException, RepositoryException {
+               return super.hasPrivileges(absPath, privileges);
+       }
+
+}
diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoLoginModule.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoLoginModule.java
new file mode 100644 (file)
index 0000000..43c5440
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * 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.security.jackrabbit;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Credentials;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+
+import org.apache.jackrabbit.core.security.AnonymousPrincipal;
+import org.apache.jackrabbit.core.security.authentication.AbstractLoginModule;
+import org.apache.jackrabbit.core.security.authentication.Authentication;
+import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
+import org.argeo.security.SystemAuthentication;
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.context.SecurityContextHolder;
+import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
+
+/** Jackrabbit login mechanism based on Spring Security */
+public class ArgeoLoginModule extends AbstractLoginModule {
+       private String adminRole = "ROLE_ADMIN";
+
+       @SuppressWarnings("unused")
+       @Override
+       public boolean login() throws LoginException {
+               boolean loginOk = super.login();
+               if (!loginOk) {
+                       org.springframework.security.Authentication authen = (org.springframework.security.Authentication) SecurityContextHolder
+                                       .getContext().getAuthentication();
+               }
+               return loginOk;
+       }
+
+       @SuppressWarnings("unused")
+       @Override
+       public boolean commit() throws LoginException {
+               boolean commitOk = super.commit();
+               if (!commitOk) {
+                       org.springframework.security.Authentication authen = (org.springframework.security.Authentication) SecurityContextHolder
+                                       .getContext().getAuthentication();
+               }
+               return commitOk;
+       }
+
+       /**
+        * Returns the Spring {@link org.springframework.security.Authentication}
+        * (which can be null)
+        */
+       @Override
+       protected Principal getPrincipal(Credentials credentials) {
+               org.springframework.security.Authentication authen = SecurityContextHolder
+                               .getContext().getAuthentication();
+               return authen;
+       }
+
+       protected Set<Principal> getPrincipals() {
+               // clear already registered Jackrabbit principals
+               // clearPrincipals(AdminPrincipal.class);
+               // clearPrincipals(AnonymousPrincipal.class);
+               // clearPrincipals(GrantedAuthorityPrincipal.class);
+
+               return syncPrincipals();
+       }
+
+       protected Set<Principal> syncPrincipals() {
+               // use linked HashSet instead of HashSet in order to maintain the order
+               // of principals (as in the Subject).
+               org.springframework.security.Authentication authen = (org.springframework.security.Authentication) principal;
+
+               Set<Principal> principals = new LinkedHashSet<Principal>();
+               principals.add(authen);
+
+               if (authen instanceof SystemAuthentication) {
+                       principals.add(new AdminPrincipal(authen.getName()));
+                       principals.add(new ArgeoSystemPrincipal(authen.getName()));
+               } else if (authen instanceof AnonymousAuthenticationToken) {
+                       principals.add(new AnonymousPrincipal());
+               } else {
+                       for (GrantedAuthority ga : authen.getAuthorities()) {
+                               principals.add(new GrantedAuthorityPrincipal(ga));
+                               // FIXME: make it more generic
+                               if (adminRole.equals(ga.getAuthority()))
+                                       principals.add(new AdminPrincipal(authen.getName()));
+                       }
+               }
+
+               // remove previous credentials
+               Set<SimpleCredentials> thisCredentials = subject
+                               .getPublicCredentials(SimpleCredentials.class);
+               if (thisCredentials != null)
+                       thisCredentials.clear();
+               // override credentials since we did not used the one passed to us
+               // credentials = new SimpleCredentials(authen.getName(), authen
+               // .getCredentials().toString().toCharArray());
+
+               return principals;
+       }
+
+       /**
+        * Super implementation removes all {@link Principal}, the Spring
+        * {@link org.springframework.security.Authentication} as well. Here we
+        * simply clear Jackrabbit related {@link Principal}s.
+        */
+       @Override
+       public boolean logout() throws LoginException {
+               clearPrincipals(AdminPrincipal.class);
+               clearPrincipals(ArgeoSystemPrincipal.class);
+               clearPrincipals(AnonymousPrincipal.class);
+               clearPrincipals(GrantedAuthorityPrincipal.class);
+
+               // we resync with Spring Security since the subject may have been reused
+               // in beetween
+               // TODO: check if this is clean
+               // subject.getPrincipals().addAll(syncPrincipals());
+
+               return true;
+       }
+
+       private <T extends Principal> void clearPrincipals(Class<T> clss) {
+               Set<T> principals = subject.getPrincipals(clss);
+               if (principals != null)
+                       principals.clear();
+       }
+
+       @SuppressWarnings("rawtypes")
+       @Override
+       protected void doInit(CallbackHandler callbackHandler, Session session,
+                       Map options) throws LoginException {
+       }
+
+       @Override
+       protected boolean impersonate(Principal principal, Credentials credentials)
+                       throws RepositoryException, LoginException {
+               throw new UnsupportedOperationException(
+                               "Impersonation is not yet supported");
+       }
+
+       @Override
+       protected Authentication getAuthentication(final Principal principal,
+                       Credentials creds) throws RepositoryException {
+               if (principal instanceof Group) {
+                       return null;
+               }
+               return new Authentication() {
+                       public boolean canHandle(Credentials credentials) {
+                               return principal instanceof org.springframework.security.Authentication;
+                       }
+
+                       public boolean authenticate(Credentials credentials)
+                                       throws RepositoryException {
+                               return ((org.springframework.security.Authentication) principal)
+                                               .isAuthenticated();
+                       }
+               };
+       }
+
+}
diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java
new file mode 100644 (file)
index 0000000..3450c75
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * 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.security.jackrabbit;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.DefaultSecurityManager;
+import org.apache.jackrabbit.core.security.AMContext;
+import org.apache.jackrabbit.core.security.AccessManager;
+import org.apache.jackrabbit.core.security.AnonymousPrincipal;
+import org.apache.jackrabbit.core.security.SecurityConstants;
+import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
+import org.springframework.security.Authentication;
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.context.SecurityContextHolder;
+
+/** Integrates Spring Security and Jackrabbit Security users and roles. */
+public class ArgeoSecurityManager extends DefaultSecurityManager {
+       /** Legacy security sync */
+       final static String PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1 = "argeo.jackarabbit.securitySync.1.1";
+
+       private final static Log log = LogFactory
+                       .getLog(ArgeoSecurityManager.class);
+
+       private static Boolean synchronize = Boolean.parseBoolean(System
+                       .getProperty(PROPERTY_JACKRABBIT_SECURITY_SYNC_1_1, "false"));
+
+       /** TODO? use a bounded buffer */
+       private Map<String, String> userRolesCache = Collections
+                       .synchronizedMap(new HashMap<String, String>());
+
+       @Override
+       public AccessManager getAccessManager(Session session, AMContext amContext)
+                       throws RepositoryException {
+               synchronized (getSystemSession()) {
+                       return super.getAccessManager(session, amContext);
+               }
+       }
+
+       @Override
+       public UserManager getUserManager(Session session)
+                       throws RepositoryException {
+               synchronized (getSystemSession()) {
+                       return super.getUserManager(session);
+               }
+       }
+
+       /**
+        * Since this is called once when the session is created, we take the
+        * opportunity to make sure that Jackrabbit users and groups reflect Spring
+        * Security name and authorities.
+        */
+       @Override
+       public String getUserID(Subject subject, String workspaceName)
+                       throws RepositoryException {
+               if (!synchronize) {
+                       Authentication authentication = SecurityContextHolder.getContext()
+                                       .getAuthentication();
+                       if (authentication != null)
+                               return authentication.getName();
+                       else
+                               return super.getUserID(subject, workspaceName);
+               }
+
+               if (log.isTraceEnabled())
+                       log.trace(subject);
+               // skip anonymous user (no rights)
+               if (!subject.getPrincipals(AnonymousPrincipal.class).isEmpty())
+                       return super.getUserID(subject, workspaceName);
+               // skip Jackrabbit system user (all rights)
+               if (!subject.getPrincipals(ArgeoSystemPrincipal.class).isEmpty())
+                       return super.getUserID(subject, workspaceName);
+
+               // retrieve Spring authentication from JAAS
+               // TODO? use Spring Security context holder
+               Authentication authen;
+               Set<Authentication> authens = subject
+                               .getPrincipals(Authentication.class);
+               String userId = super.getUserID(subject, workspaceName);
+               if (authens.size() == 0) {
+                       // make sure that logged-in user has a Principal, useful for testing
+                       // using an admin user
+                       UserManager systemUm = getSystemUserManager(null);
+                       if (systemUm.getAuthorizable(userId) == null)
+                               systemUm.createUser(userId, "");
+               } else {// Spring Security
+                       authen = authens.iterator().next();
+
+                       if (!userId.equals(authen.getName()))
+                               log.warn("User ID is '" + userId + "' but authen is "
+                                               + authen.getName());
+                       StringBuffer roles = new StringBuffer("");
+                       GrantedAuthority[] authorities = authen.getAuthorities();
+                       for (GrantedAuthority ga : authorities) {
+                               roles.append(ga.toString());
+                       }
+
+                       // do not sync if not changed
+                       if (userRolesCache.containsKey(userId)
+                                       && userRolesCache.get(userId).equals(roles.toString()))
+                               return userId;
+
+                       // sync Spring and Jackrabbit
+                       // workspace is irrelevant here
+                       UserManager systemUm = getSystemUserManager(null);
+                       syncSpringAndJackrabbitSecurity(systemUm, authen);
+                       userRolesCache.put(userId, roles.toString());
+               }
+               return userId;
+       }
+
+       /**
+        * Make sure that the Jackrabbit security model contains this user and its
+        * granted authorities
+        */
+       static private void syncSpringAndJackrabbitSecurity(UserManager systemUm,
+                       Authentication authen) throws RepositoryException {
+               long begin = System.currentTimeMillis();
+
+               String userId = authen.getName();
+               User user = (User) systemUm.getAuthorizable(userId);
+               if (user == null) {
+                       user = systemUm.createUser(userId, authen.getCredentials()
+                                       .toString(), authen, null);
+                       log.info(userId + " added as " + user);
+               }
+
+               // process groups
+               List<String> userGroupIds = new ArrayList<String>();
+               for (GrantedAuthority ga : authen.getAuthorities()) {
+                       Group group = (Group) systemUm.getAuthorizable(ga.getAuthority());
+                       if (group == null) {
+                               group = systemUm.createGroup(ga.getAuthority());
+                               log.info(ga.getAuthority() + " added as " + group);
+                       }
+                       if (!group.isMember(user))
+                               group.addMember(user);
+                       userGroupIds.add(ga.getAuthority());
+               }
+
+               // check if user has not been removed from some groups
+               for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
+                       Group group = it.next();
+                       if (!userGroupIds.contains(group.getID()))
+                               group.removeMember(user);
+               }
+
+               if (log.isTraceEnabled())
+                       log.trace("Spring and Jackrabbit Security synchronized for user "
+                                       + userId + " in " + (System.currentTimeMillis() - begin)
+                                       + " ms");
+       }
+
+       @Override
+       protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
+               WorkspaceAccessManager wam = super
+                               .createDefaultWorkspaceAccessManager();
+               return new ArgeoWorkspaceAccessManagerImpl(wam);
+       }
+
+       private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants,
+                       WorkspaceAccessManager {
+               private final WorkspaceAccessManager wam;
+
+               public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) {
+                       super();
+                       this.wam = wam;
+               }
+
+               public void init(Session systemSession) throws RepositoryException {
+                       wam.init(systemSession);
+               }
+
+               public void close() throws RepositoryException {
+               }
+
+               public boolean grants(Set<Principal> principals, String workspaceName)
+                               throws RepositoryException {
+                       // TODO: implements finer access to workspaces
+                       return true;
+               }
+       }
+
+}
diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSystemPrincipal.java
new file mode 100644 (file)
index 0000000..e38981e
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.security.jackrabbit;
+
+import java.security.Principal;
+
+/** Principal for non-interactive system actions. */
+class ArgeoSystemPrincipal implements Principal {
+       private String name;
+
+       public ArgeoSystemPrincipal(String name) {
+               super();
+               this.name = name;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public int hashCode() {
+               return getName().hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof ArgeoSystemPrincipal))
+                       return false;
+               return getName().equals(((ArgeoSystemPrincipal) obj).getName());
+       }
+
+       @Override
+       public String toString() {
+               return "Argeo System (non interactive) name=" + getName();
+       }
+
+}
diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java
new file mode 100644 (file)
index 0000000..482214e
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.security.jackrabbit;
+
+import java.security.Principal;
+
+import org.springframework.security.GrantedAuthority;
+
+/** Wraps a {@link GrantedAuthority} as a principal. */
+class GrantedAuthorityPrincipal implements Principal {
+       private final GrantedAuthority grantedAuthority;
+
+       public GrantedAuthorityPrincipal(GrantedAuthority grantedAuthority) {
+               this.grantedAuthority = grantedAuthority;
+       }
+
+       public String getName() {
+               return grantedAuthority.getAuthority();
+       }
+
+       @Override
+       public int hashCode() {
+               return getName().hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof GrantedAuthorityPrincipal))
+                       return false;
+               return getName().equals(((GrantedAuthorityPrincipal) obj).getName());
+       }
+
+       @Override
+       public String toString() {
+               return "Granted Authority " + getName();
+       }
+
+}
diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java
new file mode 100644 (file)
index 0000000..a9985f9
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * 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.security.jackrabbit;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.security.jcr.SimpleJcrSecurityModel;
+
+/** Make sure that user authorizable exists before syncing user directories. */
+public class JackrabbitSecurityModel extends SimpleJcrSecurityModel {
+       private final static Log log = LogFactory
+                       .getLog(JackrabbitSecurityModel.class);
+
+       @Override
+       public synchronized Node sync(Session session, String username,
+                       List<String> roles) {
+               if (!(session instanceof JackrabbitSession))
+                       return super.sync(session, username, roles);
+
+               try {
+                       UserManager userManager = ((JackrabbitSession) session)
+                                       .getUserManager();
+                       User user = (User) userManager.getAuthorizable(username);
+                       if (user != null) {
+                               String principalName = user.getPrincipal().getName();
+                               if (!principalName.equals(username)) {
+                                       log.warn("Jackrabbit principal is '" + principalName
+                                                       + "' but username is '" + username
+                                                       + "'. Recreating...");
+                                       user.remove();
+                                       user = userManager.createUser(username, "");
+                               }
+                       } else {
+                               // create new principal
+                               user = userManager.createUser(username, "");
+                               log.info(username + " added as Jackrabbit user " + user);
+                       }
+
+                       // generic JCR sync
+                       Node userProfile = super.sync(session, username, roles);
+
+                       Boolean enabled = userProfile.getProperty(ArgeoNames.ARGEO_ENABLED)
+                                       .getBoolean();
+                       if (enabled && user.isDisabled())
+                               user.disable(null);
+                       else if (!enabled && !user.isDisabled())
+                               user.disable(userProfile.getPath() + " is disabled");
+
+                       // Sync Jackrabbit roles
+                       if (roles != null)
+                               syncRoles(userManager, user, roles);
+
+                       return userProfile;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException(
+                                       "Cannot perform Jackrabbit specific operations", e);
+               }
+       }
+
+       /** Make sure Jackrabbit roles are in line with authentication */
+       void syncRoles(UserManager userManager, User user, List<String> roles)
+                       throws RepositoryException {
+               List<String> userGroupIds = new ArrayList<String>();
+               for (String role : roles) {
+                       Group group = (Group) userManager.getAuthorizable(role);
+                       if (group == null) {
+                               group = userManager.createGroup(role);
+                               log.info(role + " added as " + group);
+                       }
+                       if (!group.isMember(user))
+                               group.addMember(user);
+                       userGroupIds.add(role);
+               }
+
+               // check if user has not been removed from some groups
+               for (Iterator<Group> it = user.declaredMemberOf(); it.hasNext();) {
+                       Group group = it.next();
+                       if (!userGroupIds.contains(group.getID()))
+                               group.removeMember(user);
+               }
+       }
+}