From: Mathieu Baudier Date: Thu, 1 Nov 2012 12:04:09 +0000 (+0000) Subject: Moves Spring Security / Jackrabbit synchronization in JcrSecurityModel X-Git-Tag: argeo-commons-2.1.30~789 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=69f324f4c2e115192c08f9939d8ecb74e181a34b;p=lgpl%2Fargeo-commons.git Moves Spring Security / Jackrabbit synchronization in JcrSecurityModel git-svn-id: https://svn.argeo.org/commons/trunk@5685 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-jcr.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-jcr.xml index 3235e66f4..b7b3bf3ee 100644 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-jcr.xml +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap-jcr.xml @@ -19,6 +19,15 @@ + + + + + + + + diff --git a/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml b/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml index 3e1556263..188476c51 100644 --- a/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml +++ b/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml @@ -22,6 +22,15 @@ + + + + + + + + @@ -71,4 +80,5 @@ + \ No newline at end of file diff --git a/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/wizards/NewUserWizard.java b/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/wizards/NewUserWizard.java index cfb783303..10281e680 100644 --- a/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/wizards/NewUserWizard.java +++ b/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/wizards/NewUserWizard.java @@ -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); diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java index 40e3bffff..a09a52d45 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java @@ -15,6 +15,10 @@ */ 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 authoritiesToStringList( + GrantedAuthority[] authorities) { + List lst = new ArrayList(); + for (GrantedAuthority ga : authorities) + lst.add(ga.getAuthority()); + return Collections.unmodifiableList(lst); + } } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java index 1ec6d280f..f0705679d 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java @@ -1,114 +1,21 @@ 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 roles); } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java index cb9146a8b..6c26d4627 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java @@ -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 index 000000000..e564e9fd0 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SimpleJcrSecurityModel.java @@ -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 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 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; + } + +} diff --git a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java index 1bb421409..4199102a9 100644 --- a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java @@ -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())); } } diff --git a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java index 858786636..313fdca93 100644 --- a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java @@ -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 userRolesCache = Collections .synchronizedMap(new HashMap()); @@ -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 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(); diff --git a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java index 4d7dbc935..25de2bea5 100644 --- a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java @@ -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 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 roles) + throws RepositoryException { + List userGroupIds = new ArrayList(); + 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 it = user.declaredMemberOf(); it.hasNext();) { + Group group = it.next(); + if (!userGroupIds.contains(group.getID())) + group.removeMember(user); + } + } } diff --git a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java index 669231bc9..f329df356 100644 --- a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java +++ b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrLdapSynchronizer.java @@ -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 propertyToAttributes = new HashMap(); @@ -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 modifications = new HashMap(); for (String jcrProperty : propertyToAttributes.keySet()) ldapToJcr(userProfile, jcrProperty, ctx, modifications); diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java index 6f5765ed5..fdb82cb85 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java @@ -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 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(); }