Moves Spring Security / Jackrabbit synchronization in JcrSecurityModel
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 1 Nov 2012 12:04:09 +0000 (12:04 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 1 Nov 2012 12:04:09 +0000 (12:04 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@5685 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

12 files changed:
security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-jcr.xml
security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml
security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/wizards/NewUserWizard.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SimpleJcrSecurityModel.java [new file with mode: 0644]
security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java
security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java
security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java
security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java
server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java

index 3235e66f4c445c9c726aa9dd80fbaf9983fce1fe..b7b3bf3ee55ace2de2970e0631e68f63613a76be 100644 (file)
                <property name="repository" ref="nodeRepository" />
                <property name="bundleContext" ref="bundleContext" />
        </bean>
+       <bean class="org.argeo.jackrabbit.JackrabbitAuthorizations"
+               init-method="run">
+               <property name="principalPrivileges">
+                       <map>
+                               <entry key="jcr:all" value="ROLE_ADMIN" />
+                       </map>
+               </property>
+               <property name="repository" ref="argeoDataModel" />
+       </bean>
 
        <bean id="jcrLdapSynchronizer" class="org.argeo.security.ldap.jcr.JcrLdapSynchronizer"
                init-method="init" destroy-method="destroy" depends-on="argeoDataModel">
index 3e155626370ebda1cb0843e5f05a9d11882fe8e3..188476c511c5b0e7ba0c3951f25925687136b062 100644 (file)
                <property name="repository" ref="nodeRepository" />
                <property name="bundleContext" ref="bundleContext" />
        </bean>
+       <bean class="org.argeo.jackrabbit.JackrabbitAuthorizations"
+               init-method="run">
+               <property name="principalPrivileges">
+                       <map>
+                               <entry key="jcr:all" value="ROLE_ADMIN" />
+                       </map>
+               </property>
+               <property name="repository" ref="argeoDataModel" />
+       </bean>
 
        <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
                <property name="providers">
@@ -71,4 +80,5 @@
                <property name="repository" ref="nodeRepository" />
        </bean>
 
+
 </beans>
\ No newline at end of file
index cfb783303d574ee040ace35c9dfc12f267c101fc..10281e68074b81d863aec1b5aa3d4d3d8ccd4032 100644 (file)
@@ -62,7 +62,7 @@ public class NewUserWizard extends Wizard {
                try {
                        // Node userProfile = SecurityJcrUtils.createUserProfile(session,
                        // username);
-                       Node userProfile = jcrSecurityModel.sync(session, username);
+                       Node userProfile = jcrSecurityModel.sync(session, username, null);
                        session.getWorkspace().getVersionManager()
                                        .checkout(userProfile.getPath());
                        mainUserInfo.mapToProfileNode(userProfile);
index 40e3bffff3ee486913bf16b63736dac342518c22..a09a52d45dc293155c7b4d9450e06cfad698ea83 100644 (file)
  */
 package org.argeo.security;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 import org.springframework.security.Authentication;
 import org.springframework.security.GrantedAuthority;
 import org.springframework.security.context.SecurityContext;
@@ -79,4 +83,16 @@ public class SecurityUtils {
                }
                return null;
        }
+
+       /**
+        * Converts an array of Spring Security {@link GrantedAuthority} to a
+        * read-only list of strings, for portability and integration
+        */
+       public static List<String> authoritiesToStringList(
+                       GrantedAuthority[] authorities) {
+               List<String> lst = new ArrayList<String>();
+               for (GrantedAuthority ga : authorities)
+                       lst.add(ga.getAuthority());
+               return Collections.unmodifiableList(lst);
+       }
 }
index 1ec6d280fe2f6733abdcfb09097b026c4f770175..f0705679d9aa6b357eb4fe2505cd2bff04eb6596 100644 (file)
 package org.argeo.security.jcr;
 
+import java.util.List;
+
 import javax.jcr.Node;
-import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.security.Privilege;
-import javax.jcr.version.VersionManager;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoJcrConstants;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
 
 /**
  * Manages data expected by the Argeo security model, such as user home and
  * profile.
  */
-public class JcrSecurityModel {
-       // ArgeoNames not implemented as interface in order to ease derivation by
-       // Jackrabbit bundles
-
-       /** The home base path. */
-       private String homeBasePath = "/home";
-
+public interface JcrSecurityModel {
        /**
         * To be called before user details are loaded. Make sure than any logged in
         * user has a home directory with full access and a profile with information
         * about him (read access)
         * 
-        * @return the user profile (whose parent is the user home)
+        * @return the user profile (whose parent is the user home), never null
         */
-       public Node sync(Session session, String username) {
-               // TODO check user name validity (e.g. should not start by ROLE_)
-
-               try {
-                       Node userHome = UserJcrUtils.getUserHome(session, username);
-                       if (userHome == null) {
-                               String homePath = generateUserPath(homeBasePath, username);
-                               userHome = JcrUtils.mkdirs(session, homePath);
-                               // userHome = JcrUtils.mkfolders(session, homePath);
-                               userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
-                               userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
-                               session.save();
-
-                               JcrUtils.clearAccessControList(session, homePath, username);
-                               JcrUtils.addPrivilege(session, homePath, username,
-                                               Privilege.JCR_ALL);
-                       } else {
-                               // for backward compatibility with pre 1.0 security model
-                               if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) {
-                                       userHome.getNode(ArgeoNames.ARGEO_PROFILE).remove();
-                                       userHome.getSession().save();
-                               }
-                       }
-
-                       Node userProfile = UserJcrUtils.getUserProfile(session, username);
-                       if (userProfile == null) {
-                               String personPath = generateUserPath(
-                                               ArgeoJcrConstants.PEOPLE_BASE_PATH, username);
-                               Node personBase = JcrUtils.mkdirs(session, personPath);
-                               userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE);
-                               userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
-                               userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
-                               userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true);
-                               userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED,
-                                               true);
-                               userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED,
-                                               true);
-                               userProfile.setProperty(
-                                               ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true);
-                               session.save();
-
-                               JcrUtils.clearAccessControList(session, userProfile.getPath(),
-                                               username);
-                               JcrUtils.addPrivilege(session, userProfile.getPath(), username,
-                                               Privilege.JCR_READ);
-
-                               VersionManager versionManager = session.getWorkspace()
-                                               .getVersionManager();
-                               if (versionManager.isCheckedOut(userProfile.getPath()))
-                                       versionManager.checkin(userProfile.getPath());
-                       }
-                       return userProfile;
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new ArgeoException("Cannot sync node security model for "
-                                       + username, e);
-               }
-       }
-
-       /** Generate path for a new user home */
-       protected String generateUserPath(String base, String username) {
-               int atIndex = username.indexOf('@');
-               if (atIndex > 0) {
-                       String domain = username.substring(0, atIndex);
-                       String name = username.substring(atIndex + 1);
-                       return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/'
-                                       + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/'
-                                       + name;
-               } else if (atIndex == 0 || atIndex == (username.length() - 1)) {
-                       throw new ArgeoException("Unsupported username " + username);
-               } else {
-                       return base + '/' + JcrUtils.firstCharsToPath(username, 2) + '/'
-                                       + username;
-               }
-       }
-
-       public void setHomeBasePath(String homeBasePath) {
-               this.homeBasePath = homeBasePath;
-       }
-
+       public Node sync(Session session, String username, List<String> roles);
 }
index cb9146a8b662407593675de216655715d1c4504e..6c26d4627ec3ce291d03e7b3d1a5cb629f6f93cf 100644 (file)
@@ -23,26 +23,27 @@ import javax.jcr.Session;
 import org.argeo.ArgeoException;
 import org.argeo.jcr.JcrUtils;
 import org.argeo.security.OsAuthenticationToken;
+import org.argeo.security.SecurityUtils;
 import org.argeo.security.core.OsAuthenticationProvider;
 import org.springframework.security.Authentication;
 import org.springframework.security.AuthenticationException;
 import org.springframework.security.BadCredentialsException;
+import org.springframework.security.GrantedAuthority;
 import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
 import org.springframework.security.userdetails.UserDetails;
 
 /** Relies on OS to authenticate and additionally setup JCR */
 public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
        private Repository repository;
-       // private String securityWorkspace = "security";
-       // private Session securitySession;
        private Session nodeSession;
 
        private UserDetails userDetails;
-       private JcrSecurityModel jcrSecurityModel = new JcrSecurityModel();
+       private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
+
+       private final static String JVM_OSUSER = System.getProperty("user.name");
 
        public void init() {
                try {
-                       // securitySession = repository.login();
                        nodeSession = repository.login();
                } catch (RepositoryException e) {
                        throw new ArgeoException("Cannot initialize", e);
@@ -50,7 +51,6 @@ public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
        }
 
        public void destroy() {
-               // JcrUtils.logoutQuietly(securitySession);
                JcrUtils.logoutQuietly(nodeSession);
        }
 
@@ -62,8 +62,7 @@ public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
                        // consider using the keyring for username / password authentication
                        // or certificate
                        UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication;
-                       if (!upat.getPrincipal().toString()
-                                       .equals(System.getProperty("user.name")))
+                       if (!upat.getPrincipal().toString().equals(JVM_OSUSER))
                                throw new BadCredentialsException("Wrong credentials");
                        UsernamePasswordAuthenticationToken authen = new UsernamePasswordAuthenticationToken(
                                        authentication.getPrincipal(),
@@ -76,16 +75,14 @@ public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
                        try {
                                // WARNING: at this stage we assume that the java properties
                                // will have the same value
-                               String username = System.getProperty("user.name");
-                               Node userProfile = jcrSecurityModel.sync(nodeSession, username);
+                               GrantedAuthority[] authorities = getBaseAuthorities();
+                               String username = JVM_OSUSER;
+                               Node userProfile = jcrSecurityModel.sync(nodeSession, username,
+                                               SecurityUtils.authoritiesToStringList(authorities));
                                JcrUserDetails.checkAccountStatus(userProfile);
 
-                               // each user should have a writable area in the default
-                               // workspace of the node
-                               // SecurityJcrUtils.createUserHomeIfNeeded(nodeSession,
-                               // username);
                                userDetails = new JcrUserDetails(userProfile, authen
-                                               .getCredentials().toString(), getBaseAuthorities());
+                                               .getCredentials().toString(), authorities);
                                authen.setDetails(userDetails);
                                return authen;
                        } catch (RepositoryException e) {
@@ -102,10 +99,6 @@ public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
                }
        }
 
-       // public void setSecurityWorkspace(String securityWorkspace) {
-       // this.securityWorkspace = securityWorkspace;
-       // }
-
        public void setRepository(Repository repository) {
                this.repository = repository;
        }
@@ -120,5 +113,4 @@ public class OsJcrAuthenticationProvider extends OsAuthenticationProvider {
                                || UsernamePasswordAuthenticationToken.class
                                                .isAssignableFrom(authentication);
        }
-
-}
+}
\ No newline at end of file
diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SimpleJcrSecurityModel.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SimpleJcrSecurityModel.java
new file mode 100644 (file)
index 0000000..e564e9f
--- /dev/null
@@ -0,0 +1,151 @@
+package org.argeo.security.jcr;
+
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.VersionManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoJcrConstants;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+
+/**
+ * Manages data expected by the Argeo security model, such as user home and
+ * profile.
+ */
+public class SimpleJcrSecurityModel implements JcrSecurityModel {
+       private final static Log log = LogFactory
+                       .getLog(SimpleJcrSecurityModel.class);
+       // ArgeoNames not implemented as interface in order to ease derivation by
+       // Jackrabbit bundles
+
+       /** The home base path. */
+       private String homeBasePath = "/home";
+
+       public Node sync(Session session, String username, List<String> roles) {
+               // TODO check user name validity (e.g. should not start by ROLE_)
+
+               try {
+                       Node userHome = UserJcrUtils.getUserHome(session, username);
+                       if (userHome == null) {
+                               String homePath = generateUserPath(homeBasePath, username);
+                               userHome = JcrUtils.mkdirs(session, homePath);
+                               // userHome = JcrUtils.mkfolders(session, homePath);
+                               userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
+                               userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
+                               session.save();
+
+                               JcrUtils.clearAccessControList(session, homePath, username);
+                               JcrUtils.addPrivilege(session, homePath, username,
+                                               Privilege.JCR_ALL);
+                       } else {
+                               // for backward compatibility with pre 1.0 security model
+                               if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) {
+                                       userHome.getNode(ArgeoNames.ARGEO_PROFILE).remove();
+                                       userHome.getSession().save();
+                               }
+                       }
+
+                       // Remote roles
+                       if (roles != null) {
+                               //writeRemoteRoles(userHome, roles);
+                       }
+
+                       Node userProfile = UserJcrUtils.getUserProfile(session, username);
+                       if (userProfile == null) {
+                               String personPath = generateUserPath(
+                                               ArgeoJcrConstants.PEOPLE_BASE_PATH, username);
+                               Node personBase = JcrUtils.mkdirs(session, personPath);
+                               userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE);
+                               userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
+                               userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
+                               userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true);
+                               userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED,
+                                               true);
+                               userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED,
+                                               true);
+                               userProfile.setProperty(
+                                               ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true);
+                               session.save();
+
+                               JcrUtils.clearAccessControList(session, userProfile.getPath(),
+                                               username);
+                               JcrUtils.addPrivilege(session, userProfile.getPath(), username,
+                                               Privilege.JCR_READ);
+
+                               VersionManager versionManager = session.getWorkspace()
+                                               .getVersionManager();
+                               if (versionManager.isCheckedOut(userProfile.getPath()))
+                                       versionManager.checkin(userProfile.getPath());
+                       }
+                       return userProfile;
+               } catch (RepositoryException e) {
+                       JcrUtils.discardQuietly(session);
+                       throw new ArgeoException("Cannot sync node security model for "
+                                       + username, e);
+               }
+       }
+
+       /** Generate path for a new user home */
+       protected String generateUserPath(String base, String username) {
+               int atIndex = username.indexOf('@');
+               if (atIndex > 0) {
+                       String domain = username.substring(0, atIndex);
+                       String name = username.substring(atIndex + 1);
+                       return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/'
+                                       + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/'
+                                       + name;
+               } else if (atIndex == 0 || atIndex == (username.length() - 1)) {
+                       throw new ArgeoException("Unsupported username " + username);
+               } else {
+                       return base + '/' + JcrUtils.firstCharsToPath(username, 2) + '/'
+                                       + username;
+               }
+       }
+
+       /** Write remote roles used by remote access in the home directory */
+       protected void writeRemoteRoles(Node userHome, List<String> roles)
+                       throws RepositoryException {
+               boolean writeRoles = false;
+               if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) {
+                       Value[] remoteRoles = userHome.getProperty(
+                                       ArgeoNames.ARGEO_REMOTE_ROLES).getValues();
+                       if (remoteRoles.length != roles.size())
+                               writeRoles = true;
+                       else
+                               for (int i = 0; i < remoteRoles.length; i++)
+                                       if (!remoteRoles[i].getString().equals(roles.get(i)))
+                                               writeRoles = true;
+               } else
+                       writeRoles = true;
+
+               if (writeRoles) {
+                       userHome.getSession().getWorkspace().getVersionManager()
+                                       .checkout(userHome.getPath());
+                       String[] roleIds = roles.toArray(new String[roles.size()]);
+                       userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds);
+                       JcrUtils.updateLastModified(userHome);
+                       userHome.getSession().save();
+                       userHome.getSession().getWorkspace().getVersionManager()
+                                       .checkin(userHome.getPath());
+                       if (log.isDebugEnabled())
+                               log.debug("Wrote remote roles " + roles + " for "
+                                               + userHome.getProperty(ArgeoNames.ARGEO_USER_ID));
+               }
+
+       }
+
+       public void setHomeBasePath(String homeBasePath) {
+               this.homeBasePath = homeBasePath;
+       }
+
+}
index 1bb421409460a1a59a4e184b56768a4ceffb86b1..4199102a93efb278e66d71ed6d626dec36c56575 100644 (file)
@@ -39,7 +39,7 @@ import org.springframework.security.providers.anonymous.AnonymousAuthenticationT
 
 /** Jackrabbit login mechanism based on Spring Security */
 public class ArgeoLoginModule extends AbstractLoginModule {
-       private String adminRole = "ROLE_ADMIN";
+       // private String adminRole = "ROLE_ADMIN";
 
        @SuppressWarnings("unused")
        @Override
@@ -100,8 +100,8 @@ public class ArgeoLoginModule extends AbstractLoginModule {
                        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()));
+                               // if (adminRole.equals(ga.getAuthority()))
+                               // principals.add(new AdminPrincipal(authen.getName()));
                        }
                }
 
index 85878663692719b6e07c02839a9d134de611ec00..313fdca93ae9768801783034b595b82fefdb39c1 100644 (file)
@@ -42,9 +42,15 @@ import org.springframework.security.GrantedAuthority;
 
 /** 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>());
@@ -57,6 +63,9 @@ public class ArgeoSecurityManager extends DefaultSecurityManager {
        @Override
        public String getUserID(Subject subject, String workspaceName)
                        throws RepositoryException {
+               if (!synchronize)
+                       return super.getUserID(subject, workspaceName);
+
                if (log.isTraceEnabled())
                        log.trace(subject);
                // skip anonymous user (no rights)
@@ -71,18 +80,19 @@ public class ArgeoSecurityManager extends DefaultSecurityManager {
                Authentication authen;
                Set<Authentication> authens = subject
                                .getPrincipals(Authentication.class);
-               String userId;
+               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
-                       userId = super.getUserID(subject, workspaceName);
                        UserManager systemUm = getSystemUserManager(null);
                        if (systemUm.getAuthorizable(userId) == null)
                                systemUm.createUser(userId, "");
                } else {// Spring Security
                        authen = authens.iterator().next();
 
-                       userId = authen.getName();
+                       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) {
@@ -107,7 +117,7 @@ public class ArgeoSecurityManager extends DefaultSecurityManager {
         * Make sure that the Jackrabbit security model contains this user and its
         * granted authorities
         */
-       static void syncSpringAndJackrabbitSecurity(UserManager systemUm,
+       static private void syncSpringAndJackrabbitSecurity(UserManager systemUm,
                        Authentication authen) throws RepositoryException {
                long begin = System.currentTimeMillis();
 
index 4d7dbc935e9360d7589c8ae0e30cbf33bbb62535..25de2bea568d977b0f351ce2364f78f2ccb0666b 100644 (file)
@@ -1,5 +1,9 @@
 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;
@@ -7,52 +11,83 @@ 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.JcrSecurityModel;
+import org.argeo.security.jcr.SimpleJcrSecurityModel;
 
 /** Make sure that user authorizable exists before syncing user directories. */
-public class JackrabbitSecurityModel extends JcrSecurityModel {
+public class JackrabbitSecurityModel extends SimpleJcrSecurityModel {
        private final static Log log = LogFactory
                        .getLog(JackrabbitSecurityModel.class);
 
        @Override
-       public Node sync(Session session, String username) {
-               User user = null;
+       public Node sync(Session session, String username, List<String> roles) {
+               if (!(session instanceof JackrabbitSession))
+                       return super.sync(session, username, roles);
+
                try {
-                       if (session instanceof JackrabbitSession) {
-                               UserManager userManager = ((JackrabbitSession) session)
-                                               .getUserManager();
-                               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
-                                       userManager.createUser(username, "");
+                       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);
                        }
-                       Node userProfile = super.sync(session, username);
-                       if (user != null && userProfile != null) {
-                               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");
-                       }
+
+                       // 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);
+               }
+       }
 }
index 669231bc91e483944e357ef1c18cc2003d7cf6f8..f329df356fd3877314a561eaddbb011e37f230b8 100644 (file)
@@ -59,8 +59,10 @@ import org.argeo.ArgeoException;
 import org.argeo.jcr.ArgeoNames;
 import org.argeo.jcr.ArgeoTypes;
 import org.argeo.jcr.JcrUtils;
+import org.argeo.security.SecurityUtils;
 import org.argeo.security.jcr.JcrSecurityModel;
 import org.argeo.security.jcr.JcrUserDetails;
+import org.argeo.security.jcr.SimpleJcrSecurityModel;
 import org.springframework.ldap.core.ContextExecutor;
 import org.springframework.ldap.core.ContextMapper;
 import org.springframework.ldap.core.DirContextAdapter;
@@ -106,7 +108,7 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper,
        private Repository repository;
 
        private JcrProfileListener jcrProfileListener;
-       private JcrSecurityModel jcrSecurityModel = new JcrSecurityModel();
+       private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel();
 
        // Mapping
        private Map<String, String> propertyToAttributes = new HashMap<String, String>();
@@ -237,7 +239,8 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper,
 
                // Node userProfile = SecurityJcrUtils.createUserProfileIfNeeded(
                // securitySession, username);
-               Node userProfile = jcrSecurityModel.sync(nodeSession, username);
+               Node userProfile = jcrSecurityModel.sync(nodeSession, username,
+                               SecurityUtils.authoritiesToStringList(authorities));
                // JcrUserDetails.checkAccountStatus(userProfile);
 
                // password
@@ -272,7 +275,7 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper,
                        // process
                        String username = ctx.getStringAttribute(usernameAttribute);
 
-                       Node userProfile = jcrSecurityModel.sync(session, username);
+                       Node userProfile = jcrSecurityModel.sync(session, username, null);
                        Map<String, String> modifications = new HashMap<String, String>();
                        for (String jcrProperty : propertyToAttributes.keySet())
                                ldapToJcr(userProfile, jcrProperty, ctx, modifications);
index 6f5765ed5c9672c1cdf12909613a069db73a4803..fdb82cb85f2bb054e44a39feb95472b2d4b542e9 100644 (file)
@@ -1239,11 +1239,14 @@ public class JcrUtils implements ArgeoJcrConstants {
 
        /**
         * Add privileges on a path to a {@link Principal}. The path must already
-        * exist. Session is saved.
+        * exist. Session is saved. Synchronized to prevent concurrent modifications
+        * of the same node.
         */
-       public static void addPrivileges(Session session, String path,
+       public synchronized static void addPrivileges(Session session, String path,
                        Principal principal, List<Privilege> privs)
                        throws RepositoryException {
+               // make sure the session is in line with the persisted state
+               session.refresh(false);
                AccessControlManager acm = session.getAccessControlManager();
                AccessControlList acl = getAccessControlList(acm, path);
                acl.addAccessControlEntry(principal,
@@ -1256,6 +1259,7 @@ public class JcrUtils implements ArgeoJcrConstants {
                        log.debug("Added privileges " + privBuf + " to " + principal
                                        + " on " + path);
                }
+               session.refresh(true);
                session.save();
        }