From 0450e423bd759c9e27b43044670e659271392d71 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Thu, 23 Aug 2012 20:19:14 +0000 Subject: [PATCH] Change profile and user home git-svn-id: https://svn.argeo.org/commons/trunk@5539 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../META-INF/spring/security-ldap-jcr.xml | 4 +- .../META-INF/spring/security-ldap.xml | 1 + .../ldap.properties | 3 +- .../META-INF/spring/security-os.xml | 3 + .../META-INF/spring/common.xml | 1 - .../security-admin.properties | 1 - .../security/ui/admin/commands/NewUser.java | 8 +- .../ui/admin/wizards/NewUserWizard.java | 11 +- .../argeo/security/jcr/JcrSecurityModel.java | 106 +++++++++++++++++ .../argeo/security/jcr/JcrUserDetails.java | 4 +- .../jcr/OsJcrAuthenticationProvider.java | 30 +++-- .../security/jcr/OsJcrUserAdminService.java | 4 +- .../jackrabbit/ArgeoSecurityManager.java | 14 +-- .../jackrabbit/JackrabbitSecurityModel.java | 35 ++++++ .../ldap/jcr/JcrLdapSynchronizer.java | 112 +++++++++--------- .../commands/AddRemoteRepository.java | 6 - .../java/org/argeo/jcr/ArgeoJcrConstants.java | 4 +- .../src/main/java/org/argeo/jcr/JcrUtils.java | 43 +++++-- .../main/java/org/argeo/jcr/UserJcrUtils.java | 70 ++++++----- .../argeo/jcr/security/SecurityJcrUtils.java | 14 ++- 20 files changed, 335 insertions(+), 139 deletions(-) create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java create mode 100644 security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java 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 ef18e0419..9940b5b1f 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 @@ -37,10 +37,12 @@ - + + + diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap.xml index a31f79dc1..3777f8853 100644 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap.xml +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/security-ldap.xml @@ -55,6 +55,7 @@ + diff --git a/security/modules/org.argeo.security.dao.ldap/ldap.properties b/security/modules/org.argeo.security.dao.ldap/ldap.properties index b00b5261f..5c88a2255 100644 --- a/security/modules/org.argeo.security.dao.ldap/ldap.properties +++ b/security/modules/org.argeo.security.dao.ldap/ldap.properties @@ -1,9 +1,8 @@ -argeo.node.repo.securityWorkspace=security - argeo.security.defaultRole=ROLE_USER argeo.security.rolePrefix=ROLE_ argeo.security.systemKey=argeo +argeo.security.superUsername=root argeo.ldap.rootdn=dc=demo,dc=argeo,dc=org argeo.ldap.protocol=ldap 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 ee424b9e2..970f38182 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 @@ -36,8 +36,11 @@ + + + diff --git a/security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml b/security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml index 5b64119f8..b034f0362 100644 --- a/security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml +++ b/security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml @@ -12,7 +12,6 @@ - \ No newline at end of file diff --git a/security/plugins/org.argeo.security.ui.admin/security-admin.properties b/security/plugins/org.argeo.security.ui.admin/security-admin.properties index 92cfc6371..e69de29bb 100644 --- a/security/plugins/org.argeo.security.ui.admin/security-admin.properties +++ b/security/plugins/org.argeo.security.ui.admin/security-admin.properties @@ -1 +0,0 @@ -argeo.node.repo.securityWorkspace=security diff --git a/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/commands/NewUser.java b/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/commands/NewUser.java index 7dcc6104e..dab1b4ce7 100644 --- a/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/commands/NewUser.java +++ b/security/plugins/org.argeo.security.ui.admin/src/main/java/org/argeo/security/ui/admin/commands/NewUser.java @@ -18,6 +18,7 @@ package org.argeo.security.ui.admin.commands; import javax.jcr.Session; import org.argeo.security.UserAdminService; +import org.argeo.security.jcr.JcrSecurityModel; import org.argeo.security.ui.admin.wizards.NewUserWizard; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; @@ -29,11 +30,12 @@ import org.eclipse.ui.handlers.HandlerUtil; public class NewUser extends AbstractHandler { private Session session; private UserAdminService userAdminService; + private JcrSecurityModel jcrSecurityModel; public Object execute(ExecutionEvent event) throws ExecutionException { try { NewUserWizard newUserWizard = new NewUserWizard(session, - userAdminService); + userAdminService,jcrSecurityModel); WizardDialog dialog = new WizardDialog( HandlerUtil.getActiveShell(event), newUserWizard); dialog.open(); @@ -51,4 +53,8 @@ public class NewUser extends AbstractHandler { this.userAdminService = userAdminService; } + public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) { + this.jcrSecurityModel = jcrSecurityModel; + } + } 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 40a243f94..719290f10 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 @@ -24,8 +24,8 @@ import org.apache.commons.logging.LogFactory; import org.argeo.eclipse.ui.ErrorFeedback; import org.argeo.jcr.JcrUtils; import org.argeo.jcr.UserJcrUtils; -import org.argeo.jcr.security.SecurityJcrUtils; import org.argeo.security.UserAdminService; +import org.argeo.security.jcr.JcrSecurityModel; import org.argeo.security.jcr.JcrUserDetails; import org.eclipse.jface.wizard.Wizard; import org.springframework.security.GrantedAuthority; @@ -35,13 +35,16 @@ public class NewUserWizard extends Wizard { private final static Log log = LogFactory.getLog(NewUserWizard.class); private Session session; private UserAdminService userAdminService; + private JcrSecurityModel jcrSecurityModel; // pages private MainUserInfoWizardPage mainUserInfo; - public NewUserWizard(Session session, UserAdminService userAdminService) { + public NewUserWizard(Session session, UserAdminService userAdminService, + JcrSecurityModel jcrSecurityModel) { this.session = session; this.userAdminService = userAdminService; + this.jcrSecurityModel = jcrSecurityModel; } @Override @@ -57,7 +60,9 @@ public class NewUserWizard extends Wizard { String username = mainUserInfo.getUsername(); try { - Node userProfile = SecurityJcrUtils.createUserProfile(session, username); + // Node userProfile = SecurityJcrUtils.createUserProfile(session, + // username); + Node userProfile = jcrSecurityModel.sync(session, username); // session.getWorkspace().getVersionManager() // .checkout(userProfile.getPath()); mainUserInfo.mapToProfileNode(userProfile); 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 new file mode 100644 index 000000000..3fffa1ac8 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java @@ -0,0 +1,106 @@ +package org.argeo.security.jcr; + +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"; + + /** + * To be called before user details are loaded + * + * @return the user profile (whose parent is the user home) + */ + 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.clearAccesControList(session, homePath, username); + JcrUtils.addPrivilege(session, homePath, username, + Privilege.JCR_ALL); + } + + 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.clearAccesControList(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; + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java index 1a19ecb20..0dbe3de08 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java @@ -24,7 +24,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import org.argeo.jcr.ArgeoNames; -import org.argeo.jcr.security.SecurityJcrUtils; +import org.argeo.jcr.UserJcrUtils; import org.springframework.security.BadCredentialsException; import org.springframework.security.DisabledException; import org.springframework.security.GrantedAuthority; @@ -90,7 +90,7 @@ public class JcrUserDetails extends User implements ArgeoNames { */ public JcrUserDetails(Session session, String username, String password, GrantedAuthority[] authorities) throws RepositoryException { - this(SecurityJcrUtils.getUserProfile(session, username), + this(UserJcrUtils.getUserProfile(session, username), password != null ? password : "", authorities); } 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 d304dc365..cb9146a8b 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 @@ -22,7 +22,6 @@ import javax.jcr.Session; import org.argeo.ArgeoException; import org.argeo.jcr.JcrUtils; -import org.argeo.jcr.security.SecurityJcrUtils; import org.argeo.security.OsAuthenticationToken; import org.argeo.security.core.OsAuthenticationProvider; import org.springframework.security.Authentication; @@ -34,15 +33,16 @@ 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 String securityWorkspace = "security"; + // private Session securitySession; private Session nodeSession; private UserDetails userDetails; + private JcrSecurityModel jcrSecurityModel = new JcrSecurityModel(); public void init() { try { - securitySession = repository.login(securityWorkspace); + // securitySession = repository.login(); nodeSession = repository.login(); } catch (RepositoryException e) { throw new ArgeoException("Cannot initialize", e); @@ -50,7 +50,7 @@ public class OsJcrAuthenticationProvider extends OsAuthenticationProvider { } public void destroy() { - JcrUtils.logoutQuietly(securitySession); + // JcrUtils.logoutQuietly(securitySession); JcrUtils.logoutQuietly(nodeSession); } @@ -77,24 +77,24 @@ public class OsJcrAuthenticationProvider extends OsAuthenticationProvider { // WARNING: at this stage we assume that the java properties // will have the same value String username = System.getProperty("user.name"); - Node userProfile = SecurityJcrUtils.createUserProfileIfNeeded( - securitySession, username); + Node userProfile = jcrSecurityModel.sync(nodeSession, username); JcrUserDetails.checkAccountStatus(userProfile); // each user should have a writable area in the default // workspace of the node - SecurityJcrUtils.createUserHomeIfNeeded(nodeSession, username); + // SecurityJcrUtils.createUserHomeIfNeeded(nodeSession, + // username); userDetails = new JcrUserDetails(userProfile, authen .getCredentials().toString(), getBaseAuthorities()); authen.setDetails(userDetails); return authen; } catch (RepositoryException e) { - JcrUtils.discardQuietly(securitySession); + JcrUtils.discardQuietly(nodeSession); throw new ArgeoException( "Unexpected exception when synchronizing OS and JCR security ", e); } finally { - JcrUtils.logoutQuietly(securitySession); + JcrUtils.logoutQuietly(nodeSession); } } else { throw new ArgeoException("Unsupported authentication " @@ -102,14 +102,18 @@ public class OsJcrAuthenticationProvider extends OsAuthenticationProvider { } } - public void setSecurityWorkspace(String securityWorkspace) { - this.securityWorkspace = securityWorkspace; - } + // public void setSecurityWorkspace(String securityWorkspace) { + // this.securityWorkspace = securityWorkspace; + // } public void setRepository(Repository repository) { this.repository = repository; } + public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) { + this.jcrSecurityModel = jcrSecurityModel; + } + @SuppressWarnings("rawtypes") public boolean supports(Class authentication) { return OsAuthenticationToken.class.isAssignableFrom(authentication) diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrUserAdminService.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrUserAdminService.java index 80ef1e559..44521f1bd 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrUserAdminService.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrUserAdminService.java @@ -10,7 +10,7 @@ import javax.jcr.Session; import org.argeo.ArgeoException; import org.argeo.jcr.JcrUtils; -import org.argeo.jcr.security.SecurityJcrUtils; +import org.argeo.jcr.UserJcrUtils; import org.argeo.security.UserAdminService; import org.springframework.dao.DataAccessException; import org.springframework.security.userdetails.UserDetails; @@ -68,7 +68,7 @@ public class OsJcrUserAdminService implements UserAdminService { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { if (getSPropertyUsername().equals(username)) { - Node userProfile = SecurityJcrUtils.getUserProfile(securitySession, + Node userProfile = UserJcrUtils.getUserProfile(securitySession, username); JcrUserDetails userDetails; try { 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 4af5d3f3d..efd19b87d 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 @@ -40,7 +40,8 @@ import org.springframework.security.GrantedAuthority; /** Integrates Spring Security and Jackrabbit Security user and roles. */ public class ArgeoSecurityManager extends DefaultSecurityManager { - private Log log = LogFactory.getLog(ArgeoSecurityManager.class); + private final static Log log = LogFactory + .getLog(ArgeoSecurityManager.class); /** * Since this is called once when the session is created, we take the @@ -71,7 +72,9 @@ public class ArgeoSecurityManager extends DefaultSecurityManager { authen = authens.iterator().next(); // sync Spring and Jackrabbit - syncSpringAndJackrabbitSecurity(authen); + // workspace is irrelevant here + UserManager systemUm = getSystemUserManager(null); + syncSpringAndJackrabbitSecurity(systemUm, authen); return authen.getName(); } @@ -80,13 +83,10 @@ public class ArgeoSecurityManager extends DefaultSecurityManager { * Make sure that the Jackrabbit security model contains this user and its * granted authorities */ - protected void syncSpringAndJackrabbitSecurity(Authentication authen) - throws RepositoryException { + static void syncSpringAndJackrabbitSecurity(UserManager systemUm, + Authentication authen) throws RepositoryException { long begin = System.currentTimeMillis(); - // workspace is irrelevant here - UserManager systemUm = getSystemUserManager(null); - String userId = authen.getName(); User user = (User) systemUm.getAuthorizable(userId); if (user == null) { 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 new file mode 100644 index 000000000..75345edc8 --- /dev/null +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/JackrabbitSecurityModel.java @@ -0,0 +1,35 @@ +package org.argeo.security.jackrabbit; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.api.JackrabbitSession; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.argeo.ArgeoException; +import org.argeo.security.jcr.JcrSecurityModel; +import org.argeo.util.security.SimplePrincipal; + +/** Make sure that user authorizable exists before syncing user directories. */ +public class JackrabbitSecurityModel extends JcrSecurityModel { + + @Override + public Node sync(Session session, String username) { + try { + if (session instanceof JackrabbitSession) { + UserManager userManager = ((JackrabbitSession) session) + .getUserManager(); + User user = (User) userManager + .getAuthorizable(new SimplePrincipal(username)); + if (user == null) + userManager.createUser(username, ""); + } + } catch (RepositoryException e) { + throw new ArgeoException( + "Cannot perform Jackrabbit specific operaitons", e); + } + return super.sync(session, username); + } + +} 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 178785602..fa940f93d 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,7 +59,7 @@ import org.argeo.ArgeoException; import org.argeo.jcr.ArgeoNames; import org.argeo.jcr.ArgeoTypes; import org.argeo.jcr.JcrUtils; -import org.argeo.jcr.security.SecurityJcrUtils; +import org.argeo.security.jcr.JcrSecurityModel; import org.argeo.security.jcr.JcrUserDetails; import org.springframework.ldap.core.ContextExecutor; import org.springframework.ldap.core.ContextMapper; @@ -73,7 +73,7 @@ import org.springframework.security.providers.encoding.PasswordEncoder; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.ldap.UserDetailsContextMapper; -/** Guarantees that LDAP and JCR are in line. */ +/** Makes sure that LDAP and JCR are in line. */ public class JcrLdapSynchronizer implements UserDetailsContextMapper, ArgeoNames { private final static Log log = LogFactory.getLog(JcrLdapSynchronizer.class); @@ -102,12 +102,11 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, // JCR /** Admin session on the security workspace */ - private Session securitySession; + private Session nodeSession; private Repository repository; - private String securityWorkspace = "security"; - private JcrProfileListener jcrProfileListener; + private JcrSecurityModel jcrSecurityModel = new JcrSecurityModel(); // Mapping private Map propertyToAttributes = new HashMap(); @@ -118,7 +117,7 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, public void init() { try { - securitySession = repository.login(securityWorkspace); + nodeSession = repository.login(); synchronize(); @@ -142,22 +141,22 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, jcrProfileListener = new JcrProfileListener(); // noLocal is used so that we are not notified when we modify JCR // from LDAP - securitySession + nodeSession .getWorkspace() .getObservationManager() .addEventListener(jcrProfileListener, Event.PROPERTY_CHANGED | Event.NODE_ADDED, "/", true, null, nodeTypes, true); } catch (Exception e) { - JcrUtils.logoutQuietly(securitySession); + JcrUtils.logoutQuietly(nodeSession); throw new ArgeoException("Cannot initialize LDAP/JCR synchronizer", e); } } public void destroy() { - JcrUtils.removeListenerQuietly(securitySession, jcrProfileListener); - JcrUtils.logoutQuietly(securitySession); + JcrUtils.removeListenerQuietly(nodeSession, jcrProfileListener); + JcrUtils.logoutQuietly(nodeSession); try { rawLdapTemplate.executeReadOnly(new ContextExecutor() { public Object executeWithContext(DirContext ctx) @@ -191,7 +190,7 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, }); // disable accounts which are not in LDAP - Query query = securitySession + Query query = nodeSession .getWorkspace() .getQueryManager() .createQuery( @@ -207,28 +206,30 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, + " not found in LDAP, disabling user " + userProfile.getProperty(ArgeoNames.ARGEO_USER_ID) .getString()); - VersionManager versionManager = securitySession - .getWorkspace().getVersionManager(); + VersionManager versionManager = nodeSession.getWorkspace() + .getVersionManager(); versionManager.checkout(userProfile.getPath()); userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, false); - securitySession.save(); + nodeSession.save(); versionManager.checkin(userProfile.getPath()); } } } catch (Exception e) { - JcrUtils.discardQuietly(securitySession); - throw new ArgeoException("Cannot synchronized LDAP and JCR", e); + JcrUtils.discardQuietly(nodeSession); + log.error("Cannot synchronize LDAP and JCR", e); + // throw new ArgeoException("Cannot synchronize LDAP and JCR", e); } } /** Called during authentication in order to retrieve user details */ public UserDetails mapUserFromContext(final DirContextOperations ctx, final String username, GrantedAuthority[] authorities) { - log.debug("mapUserFromContext"); if (ctx == null) throw new ArgeoException("No LDAP information for user " + username); - Node userProfile = SecurityJcrUtils.createUserProfileIfNeeded(securitySession, - username); + + // Node userProfile = SecurityJcrUtils.createUserProfileIfNeeded( + // securitySession, username); + Node userProfile = jcrSecurityModel.sync(nodeSession, username); JcrUserDetails.checkAccountStatus(userProfile); // password @@ -258,36 +259,38 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, * @return path to user profile */ protected synchronized String mapLdapToJcr(DirContextAdapter ctx) { - Session session = securitySession; + Session session = nodeSession; try { // process String username = ctx.getStringAttribute(usernameAttribute); - Node userHome = SecurityJcrUtils.createUserHomeIfNeeded(session, username); - Node userProfile; // = userHome.getNode(ARGEO_PROFILE); - if (userHome.hasNode(ARGEO_PROFILE)) { - userProfile = userHome.getNode(ARGEO_PROFILE); - - // compatibility with legacy, will be removed - if (!userProfile.hasProperty(ARGEO_ENABLED)) { - session.getWorkspace().getVersionManager() - .checkout(userProfile.getPath()); - userProfile.setProperty(ARGEO_ENABLED, true); - userProfile.setProperty(ARGEO_ACCOUNT_NON_EXPIRED, true); - userProfile.setProperty(ARGEO_ACCOUNT_NON_LOCKED, true); - userProfile - .setProperty(ARGEO_CREDENTIALS_NON_EXPIRED, true); - session.save(); - session.getWorkspace().getVersionManager() - .checkin(userProfile.getPath()); - } - } else { - userProfile = SecurityJcrUtils.createUserProfile(securitySession, - username); - userProfile.getSession().save(); - userProfile.getSession().getWorkspace().getVersionManager() - .checkin(userProfile.getPath()); - } - + // Node userHome = SecurityJcrUtils.createUserHomeIfNeeded(session, + // username); + // Node userProfile; // = userHome.getNode(ARGEO_PROFILE); + // if (userHome.hasNode(ARGEO_PROFILE)) { + // userProfile = userHome.getNode(ARGEO_PROFILE); + // + // // compatibility with legacy, will be removed + // if (!userProfile.hasProperty(ARGEO_ENABLED)) { + // session.getWorkspace().getVersionManager() + // .checkout(userProfile.getPath()); + // userProfile.setProperty(ARGEO_ENABLED, true); + // userProfile.setProperty(ARGEO_ACCOUNT_NON_EXPIRED, true); + // userProfile.setProperty(ARGEO_ACCOUNT_NON_LOCKED, true); + // userProfile + // .setProperty(ARGEO_CREDENTIALS_NON_EXPIRED, true); + // session.save(); + // session.getWorkspace().getVersionManager() + // .checkin(userProfile.getPath()); + // } + // } else { + // userProfile = SecurityJcrUtils.createUserProfile( + // securitySession, username); + // userProfile.getSession().save(); + // userProfile.getSession().getWorkspace().getVersionManager() + // .checkin(userProfile.getPath()); + // } + + Node userProfile = jcrSecurityModel.sync(session, username); Map modifications = new HashMap(); for (String jcrProperty : propertyToAttributes.keySet()) ldapToJcr(userProfile, jcrProperty, ctx, modifications); @@ -375,8 +378,9 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, final JcrUserDetails jcrUserDetails = (JcrUserDetails) user; try { - Node userProfile = securitySession.getNode( - jcrUserDetails.getHomePath()).getNode(ARGEO_PROFILE); + Node userProfile = nodeSession + .getNode(jcrUserDetails.getHomePath()).getNode( + ARGEO_PROFILE); for (String jcrProperty : propertyToAttributes.keySet()) { if (userProfile.hasProperty(jcrProperty)) { ModificationItem mi = jcrToLdap(jcrProperty, userProfile @@ -457,10 +461,6 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, this.repository = repository; } - public void setSecurityWorkspace(String securityWorkspace) { - this.securityWorkspace = securityWorkspace; - } - public void setUserBase(String userBase) { this.userBase = userBase; } @@ -489,6 +489,10 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, this.passwordEncoder = passwordEncoder; } + public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) { + this.jcrSecurityModel = jcrSecurityModel; + } + /** Listen to LDAP */ class LdapUserListener implements ObjectChangeListener, NamespaceChangeListener, UnsolicitedNotificationListener { @@ -550,7 +554,7 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, Event event = events.nextEvent(); try { if (Event.PROPERTY_CHANGED == event.getType()) { - Property property = (Property) securitySession + Property property = (Property) nodeSession .getItem(event.getPath()); String propertyName = property.getName(); Node userProfile = property.getParent(); @@ -568,7 +572,7 @@ public class JcrLdapSynchronizer implements UserDetailsContextMapper, modifications.get(name).add(mi); } } else if (Event.NODE_ADDED == event.getType()) { - Node userProfile = securitySession.getNode(event + Node userProfile = nodeSession.getNode(event .getPath()); String username = userProfile.getProperty( ARGEO_USER_ID).getString(); diff --git a/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java b/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java index 332e94444..b7d4e030a 100644 --- a/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java +++ b/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java @@ -32,7 +32,6 @@ import org.argeo.jcr.ArgeoTypes; import org.argeo.jcr.JcrUtils; import org.argeo.jcr.UserJcrUtils; import org.argeo.jcr.security.JcrKeyring; -import org.argeo.jcr.security.SecurityJcrUtils; import org.argeo.jcr.ui.explorer.JcrExplorerConstants; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; @@ -162,11 +161,6 @@ public class AddRemoteRepository extends AbstractHandler implements Session nodeSession = keyring.getSession(); Node home = UserJcrUtils.getUserHome(nodeSession); - // FIXME better deal with non existing home dir - if (home == null) - home = SecurityJcrUtils.createUserHomeIfNeeded(nodeSession, - nodeSession.getUserID()); - Node remote = home.hasNode(ARGEO_REMOTE) ? home .getNode(ARGEO_REMOTE) : home.addNode(ARGEO_REMOTE); if (remote.hasNode(name.getText())) diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java index 5cfb1d0fa..0e23f8893 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java @@ -15,11 +15,13 @@ */ package org.argeo.jcr; -/** JCR related constants */ +/** Argeo model specific constants */ public interface ArgeoJcrConstants { public final static String ARGEO_BASE_PATH = "/argeo:system"; public final static String DATA_MODELS_BASE_PATH = ARGEO_BASE_PATH + "/argeo:dataModels"; + public final static String PEOPLE_BASE_PATH = ARGEO_BASE_PATH + + "/argeo:people"; // parameters (typically for call to a RepositoryFactory) public final static String JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias"; 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 3364ebfea..0d889626e 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 @@ -1289,6 +1289,23 @@ public class JcrUtils implements ArgeoJcrConstants { Principal principal, List privs) throws RepositoryException { AccessControlManager acm = session.getAccessControlManager(); + AccessControlList acl = getAccessControlList(acm, path); + acl.addAccessControlEntry(principal, + privs.toArray(new Privilege[privs.size()])); + acm.setPolicy(path, acl); + if (log.isDebugEnabled()) { + StringBuffer privBuf = new StringBuffer(); + for (Privilege priv : privs) + privBuf.append(priv.getName()); + log.debug("Added privileges " + privBuf + " to " + principal + + " on " + path); + } + session.save(); + } + + /** Gets access control list for this path, throws exception if not found */ + public static AccessControlList getAccessControlList( + AccessControlManager acm, String path) throws RepositoryException { // search for an access control list AccessControlList acl = null; AccessControlPolicyIterator policyIterator = acm @@ -1307,19 +1324,21 @@ public class JcrUtils implements ArgeoJcrConstants { acl = ((AccessControlList) acp); } } + if (acl != null) + return acl; + else + throw new ArgeoException("ACL not found at " + path); + } - if (acl != null) { - acl.addAccessControlEntry(principal, - privs.toArray(new Privilege[privs.size()])); - acm.setPolicy(path, acl); - if (log.isDebugEnabled()) - log.debug("Added privileges " + privs + " to " + principal - + " on " + path); - } else { - throw new ArgeoException("Don't know how to apply privileges " - + privs + " to " + principal + " on " + path); + /** Clear authorizations for a user at this path */ + public static void clearAccesControList(Session session, String path, + String username) throws RepositoryException { + AccessControlManager acm = session.getAccessControlManager(); + AccessControlList acl = getAccessControlList(acm, path); + for (AccessControlEntry ace : acl.getAccessControlEntries()) { + if (ace.getPrincipal().getName().equals(username)) { + acl.removeAccessControlEntry(ace); + } } - session.save(); } - } diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/UserJcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/UserJcrUtils.java index 016347ada..b357227a5 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/UserJcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/UserJcrUtils.java @@ -3,19 +3,22 @@ package org.argeo.jcr; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; +import javax.jcr.query.Query; +import javax.jcr.query.qom.Constraint; +import javax.jcr.query.qom.DynamicOperand; +import javax.jcr.query.qom.QueryObjectModelFactory; +import javax.jcr.query.qom.Selector; +import javax.jcr.query.qom.StaticOperand; import org.argeo.ArgeoException; /** Utilities related to the user home and properties based on Argeo JCR model. */ public class UserJcrUtils { /** The home base path. Not yet configurable */ - public final static String DEFAULT_HOME_BASE_PATH = "/argeo:home"; - - private UserJcrUtils() { - } + public final static String DEFAULT_HOME_BASE_PATH = "/home"; /** - * Returns the home node of the session user or null if none was found. + * Returns the home node of the user or null if none was found. * * @param session * the session to use in order to perform the search, this can be @@ -26,23 +29,41 @@ public class UserJcrUtils { */ public static Node getUserHome(Session session, String username) { try { - String homePath = UserJcrUtils.getUserHomePath(username); - return session.itemExists(homePath) ? session.getNode(homePath) - : null; + // String homePath = UserJcrUtils.getUserHomePath(username); + // return session.itemExists(homePath) ? session.getNode(homePath) + // : null; // kept for example of QOM queries - // QueryObjectModelFactory qomf = session.getWorkspace() - // .getQueryManager().getQOMFactory(); - // Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME, - // "userHome"); - // DynamicOperand userIdDop = qomf.propertyValue("userHome", - // ArgeoNames.ARGEO_USER_ID); - // StaticOperand userIdSop = qomf.literal(session.getValueFactory() - // .createValue(username)); - // Constraint constraint = qomf.comparison(userIdDop, - // QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); - // Query query = qomf.createQuery(userHomeSel, constraint, null, - // null); - // Node userHome = JcrUtils.querySingleNode(query); + QueryObjectModelFactory qomf = session.getWorkspace() + .getQueryManager().getQOMFactory(); + Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME, + "userHome"); + DynamicOperand userIdDop = qomf.propertyValue( + userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID); + StaticOperand userIdSop = qomf.literal(session.getValueFactory() + .createValue(username)); + Constraint constraint = qomf.comparison(userIdDop, + QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); + Query query = qomf.createQuery(userHomeSel, constraint, null, null); + return JcrUtils.querySingleNode(query); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot find home for user " + username, e); + } + } + + public static Node getUserProfile(Session session, String username) { + try { + QueryObjectModelFactory qomf = session.getWorkspace() + .getQueryManager().getQOMFactory(); + Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_PROFILE, + "userProfile"); + DynamicOperand userIdDop = qomf.propertyValue( + userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID); + StaticOperand userIdSop = qomf.literal(session.getValueFactory() + .createValue(username)); + Constraint constraint = qomf.comparison(userIdDop, + QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); + Query query = qomf.createQuery(userHomeSel, constraint, null, null); + return JcrUtils.querySingleNode(query); } catch (RepositoryException e) { throw new ArgeoException("Cannot find home for user " + username, e); } @@ -54,11 +75,6 @@ public class UserJcrUtils { return getUserHome(session, userID); } - /** @deprecated Use {@link getUserHome} directly */ - @Deprecated - static String getUserHomePath(String username) { - String homeBasePath = DEFAULT_HOME_BASE_PATH; - return homeBasePath + '/' + JcrUtils.firstCharsToPath(username, 2) - + '/' + username; + private UserJcrUtils() { } } diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/SecurityJcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/SecurityJcrUtils.java index 1ba42197e..90a8d87bd 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/SecurityJcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/SecurityJcrUtils.java @@ -13,12 +13,13 @@ import org.argeo.jcr.JcrUtils; import org.argeo.jcr.UserJcrUtils; /** Utilities related to Argeo security model in JCR */ +@Deprecated public class SecurityJcrUtils implements ArgeoJcrConstants { /** * Creates an Argeo user home, does nothing if it already exists. Session is * NOT saved. */ - public static Node createUserHomeIfNeeded(Session session, String username) { + static Node createUserHomeIfNeeded(Session session, String username) { try { String homePath = generateUserHomePath(username); if (session.itemExists(homePath)) @@ -27,8 +28,9 @@ public class SecurityJcrUtils implements ArgeoJcrConstants { Node userHome = JcrUtils.mkdirs(session, homePath); userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); - - //JcrUtils.addPrivilege(session, homePath, username, "jcr:all"); + + // JcrUtils.addPrivilege(session, homePath, username, + // "jcr:all"); return userHome; } } catch (RepositoryException e) { @@ -50,7 +52,7 @@ public class SecurityJcrUtils implements ArgeoJcrConstants { * is not saved and the node is in a checkedOut state (that is, it requires * a subsequent checkin after saving the session). */ - public static Node createUserProfile(Session session, String username) { + static Node createUserProfile(Session session, String username) { try { Node userHome = createUserHomeIfNeeded(session, username); if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) @@ -78,7 +80,7 @@ public class SecurityJcrUtils implements ArgeoJcrConstants { * * @return the user profile */ - public static Node createUserProfileIfNeeded(Session securitySession, + static Node createUserProfileIfNeeded(Session securitySession, String username) { try { Node userHome = createUserHomeIfNeeded(securitySession, username); @@ -103,7 +105,7 @@ public class SecurityJcrUtils implements ArgeoJcrConstants { /** * @return null if not found * */ - public static Node getUserProfile(Session session, String username) { + static Node getUserProfile(Session session, String username) { try { Node userHome = UserJcrUtils.getUserHome(session, username); if (userHome == null) -- 2.30.2