From 10c220cf49f5b146bac50ed7fe2578135cd466f1 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 16 Mar 2011 15:21:11 +0000 Subject: [PATCH] Jackrabbit security improved Remote logging on a DAVEX server (not everything working) git-svn-id: https://svn.argeo.org/commons/trunk@4296 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../META-INF/MANIFEST.MF | 1 + .../META-INF/spring/osgi.xml | 11 +- .../META-INF/spring/services.xml | 12 +- .../org.argeo.security.equinox/pom.xml | 5 + .../security/equinox/SpringLoginModule.java | 16 ++- .../org.argeo.security.ui.application.product | 42 ++++++ .../AbstractSecureApplication.java | 58 ++++---- .../ui/dialogs/DefaultLoginDialog.java | 2 +- .../runtime/org.argeo.security.core/pom.xml | 5 + .../security/SiteAuthenticationToken.java | 34 +++++ .../core/ArgeoAuthenticationManager.java | 28 ++++ .../jcr/JcrAuthenticationProvider.java | 136 ++++++++++++++++++ .../security/jcr/JcrAuthenticationToken.java | 34 +++++ .../jackrabbit/ArgeoAccessManager.java | 8 ++ ...LoginModule.java => ArgeoLoginModule.java} | 27 ++-- .../jackrabbit/ArgeoSecurityManager.java | 80 +++++++++++ .../GrantedAuthorityPrincipal.java | 4 +- .../JackrabbitAuthenticationProvider.java | 38 +++++ .../META-INF/MANIFEST.MF | 1 + .../WEB-INF/applicationContext.xml | 4 +- .../WEB-INF/osgi.xml | 4 +- .../WEB-INF/security-filters.xml | 114 +++++++++++++++ .../WEB-INF/security.xml | 20 ++- .../META-INF/MANIFEST.MF | 5 +- .../META-INF/spring/noderepo-osgi.xml | 14 ++ .../META-INF/spring/noderepo.xml | 7 + .../repository-h2.xml | 9 +- .../repository-postgresql.xml | 9 +- .../META-INF/spring/server.xml | 2 +- .../org.argeo.jcr.ui.explorer.product | 9 ++ .../JackrabbitRepositoryFactory.java | 34 ++++- .../remote/SimpleSessionProvider.java | 42 ++++-- .../argeo/jcr/DefaultRepositoryFactory.java | 24 ++++ 33 files changed, 755 insertions(+), 84 deletions(-) create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SiteAuthenticationToken.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/ArgeoAuthenticationManager.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationProvider.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationToken.java create mode 100644 security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java rename security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/{spring/SpringLoginModule.java => ArgeoLoginModule.java} (74%) create mode 100644 security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java rename security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/{spring => }/GrantedAuthorityPrincipal.java (78%) create mode 100644 security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/providers/JackrabbitAuthenticationProvider.java create mode 100644 server/modules/org.argeo.jackrabbit.webapp/WEB-INF/security-filters.xml create mode 100644 server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryFactory.java diff --git a/security/modules/org.argeo.security.services/META-INF/MANIFEST.MF b/security/modules/org.argeo.security.services/META-INF/MANIFEST.MF index 3d22df281..8658c2bdc 100644 --- a/security/modules/org.argeo.security.services/META-INF/MANIFEST.MF +++ b/security/modules/org.argeo.security.services/META-INF/MANIFEST.MF @@ -6,6 +6,7 @@ Import-Package: org.argeo.security, org.springframework.security;specification-version="2.0.4.A", org.springframework.security.adapters;specification-version="2.0.6.RELEASE", org.springframework.security.providers;specification-version="2.0.6.RELEASE", + org.springframework.security.providers.anonymous;specification-version="2.0.6.RELEASE", org.springframework.security.providers.encoding;specification-version="2.0.6.RELEASE", org.springframework.security.providers.rememberme;specification-version="2.0.6.RELEASE" Bundle-Name: Security Services diff --git a/security/modules/org.argeo.security.services/META-INF/spring/osgi.xml b/security/modules/org.argeo.security.services/META-INF/spring/osgi.xml index 62872b1c0..92b4129fd 100644 --- a/security/modules/org.argeo.security.services/META-INF/spring/osgi.xml +++ b/security/modules/org.argeo.security.services/META-INF/spring/osgi.xml @@ -12,8 +12,15 @@ - + + + + + diff --git a/security/modules/org.argeo.security.services/META-INF/spring/services.xml b/security/modules/org.argeo.security.services/META-INF/spring/services.xml index bb79de265..c602a6bc5 100644 --- a/security/modules/org.argeo.security.services/META-INF/spring/services.xml +++ b/security/modules/org.argeo.security.services/META-INF/spring/services.xml @@ -25,18 +25,24 @@ - + + class="org.springframework.security.providers.anonymous.AnonymousAuthenticationProvider"> - + + + + + + + \ No newline at end of file diff --git a/security/plugins/org.argeo.security.equinox/pom.xml b/security/plugins/org.argeo.security.equinox/pom.xml index 944a6e0df..9e0299f44 100644 --- a/security/plugins/org.argeo.security.equinox/pom.xml +++ b/security/plugins/org.argeo.security.equinox/pom.xml @@ -69,6 +69,11 @@ org.argeo.basic.nodeps 0.2.3-SNAPSHOT + + org.argeo.commons.security + org.argeo.security.core + 0.2.3-SNAPSHOT + diff --git a/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java b/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java index 2222faecc..c357a9ea7 100644 --- a/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java +++ b/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java @@ -10,11 +10,11 @@ import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.TextOutputCallback; import javax.security.auth.login.LoginException; +import org.argeo.security.SiteAuthenticationToken; import org.springframework.security.Authentication; import org.springframework.security.AuthenticationManager; import org.springframework.security.BadCredentialsException; import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.providers.jaas.SecurityContextLoginModule; /** Login module which caches one subject per thread. */ @@ -56,6 +56,7 @@ public class SpringLoginModule extends SecurityContextLoginModule { NameCallback nameCallback = new NameCallback("User"); PasswordCallback passwordCallback = new PasswordCallback("Password", false); + NameCallback urlCallback = new NameCallback("Site URL"); if (callbackHandler == null) { throw new LoginException("No call back handler available"); @@ -63,7 +64,7 @@ public class SpringLoginModule extends SecurityContextLoginModule { } try { callbackHandler.handle(new Callback[] { label, nameCallback, - passwordCallback }); + passwordCallback, urlCallback }); } catch (Exception e) { LoginException le = new LoginException("Callback handling failed"); le.initCause(e); @@ -76,8 +77,15 @@ public class SpringLoginModule extends SecurityContextLoginModule { if (passwordCallback.getPassword() != null) { password = String.valueOf(passwordCallback.getPassword()); } - UsernamePasswordAuthenticationToken credentials = new UsernamePasswordAuthenticationToken( - username, password); + String url = urlCallback.getName(); + // TODO: set it via system properties + String workspace = null; + + // UsernamePasswordAuthenticationToken credentials = new + // UsernamePasswordAuthenticationToken( + // username, password); + SiteAuthenticationToken credentials = new SiteAuthenticationToken( + username, password, url, workspace); try { Authentication authentication = authenticationManager diff --git a/security/plugins/org.argeo.security.ui.application/org.argeo.security.ui.application.product b/security/plugins/org.argeo.security.ui.application/org.argeo.security.ui.application.product index c410c4ed5..3fabf3a19 100644 --- a/security/plugins/org.argeo.security.ui.application/org.argeo.security.ui.application.product +++ b/security/plugins/org.argeo.security.ui.application/org.argeo.security.ui.application.product @@ -25,7 +25,10 @@ + + + @@ -33,6 +36,10 @@ + + + + @@ -48,22 +55,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -72,8 +104,15 @@ + + + + + + + @@ -105,13 +144,16 @@ + + + diff --git a/security/plugins/org.argeo.security.ui.application/src/main/java/org/argeo/security/ui/application/AbstractSecureApplication.java b/security/plugins/org.argeo.security.ui.application/src/main/java/org/argeo/security/ui/application/AbstractSecureApplication.java index 955066000..d866cb69c 100644 --- a/security/plugins/org.argeo.security.ui.application/src/main/java/org/argeo/security/ui/application/AbstractSecureApplication.java +++ b/security/plugins/org.argeo.security.ui.application/src/main/java/org/argeo/security/ui/application/AbstractSecureApplication.java @@ -3,9 +3,11 @@ package org.argeo.security.ui.application; import java.security.PrivilegedAction; import javax.security.auth.Subject; +import javax.security.auth.login.LoginException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.argeo.eclipse.ui.dialogs.Error; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.equinox.app.IApplication; @@ -17,8 +19,8 @@ import org.eclipse.ui.PlatformUI; import org.eclipse.ui.application.WorkbenchAdvisor; /** - * Common base class for authenticated access to the Eclipse UI framework (RAP and - * RCP) + * Common base class for authenticated access to the Eclipse UI framework (RAP + * and RCP) */ public abstract class AbstractSecureApplication implements IApplication { private static final Log log = LogFactory @@ -30,34 +32,42 @@ public abstract class AbstractSecureApplication implements IApplication { Integer returnCode = null; Display display = PlatformUI.createDisplay(); - - // Force login - try { - String username = null; - Exception loginException = null; Subject subject = null; - try { - SecureApplicationActivator.getLoginContext().login(); - subject = SecureApplicationActivator.getLoginContext() - .getSubject(); - - // username = CurrentUser.getUsername(); - } catch (Exception e) { - loginException = e; - e.printStackTrace(); + Boolean retry = true; + while (retry) { + try { + SecureApplicationActivator.getLoginContext().login(); + subject = SecureApplicationActivator.getLoginContext() + .getSubject(); + retry = false; + } catch (LoginException e) { + Error.show("Cannot login", e); + retry = true; + } catch (Exception e) { + Error.show("Unexpected exception while trying to login", e); + retry = false; + } } + if (subject == null) { - IStatus status = new Status(IStatus.ERROR, - "org.argeo.security.application", "Login is mandatory", - loginException); - ErrorDialog.openError(null, "Error", "Shutdown...", status); - return status.getSeverity(); + // IStatus status = new Status(IStatus.ERROR, + // "org.argeo.security.application", "Login is mandatory", + // loginException); + // ErrorDialog.openError(null, "Error", "Shutdown...", status); + // return status.getSeverity(); + + // TODO: log as anonymous } - returnCode = (Integer) Subject.doAs(subject, getRunAction(display)); - SecureApplicationActivator.getLoginContext().logout(); - return processReturnCode(returnCode); + if (subject != null) { + returnCode = (Integer) Subject.doAs(subject, + getRunAction(display)); + SecureApplicationActivator.getLoginContext().logout(); + return processReturnCode(returnCode); + } else { + return -1; + } } catch (Exception e) { // e.printStackTrace(); IStatus status = new Status(IStatus.ERROR, diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/DefaultLoginDialog.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/DefaultLoginDialog.java index d00e961fb..7a846f7b5 100644 --- a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/DefaultLoginDialog.java +++ b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/DefaultLoginDialog.java @@ -30,7 +30,7 @@ public class DefaultLoginDialog extends AbstractLoginDialog { } protected Point getInitialSize() { - return new Point(300, 250); + return new Point(300, 350); } protected Control createDialogArea(Composite parent) { diff --git a/security/runtime/org.argeo.security.core/pom.xml b/security/runtime/org.argeo.security.core/pom.xml index 5c9a1f9eb..e9eca020c 100644 --- a/security/runtime/org.argeo.security.core/pom.xml +++ b/security/runtime/org.argeo.security.core/pom.xml @@ -48,6 +48,11 @@ org.argeo.basic.nodeps 0.2.3-SNAPSHOT + + org.argeo.commons.server + org.argeo.server.jcr + 0.2.3-SNAPSHOT + diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SiteAuthenticationToken.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SiteAuthenticationToken.java new file mode 100644 index 000000000..d836b6f7a --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/SiteAuthenticationToken.java @@ -0,0 +1,34 @@ +package org.argeo.security; + +import org.springframework.security.GrantedAuthority; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; + +public class SiteAuthenticationToken extends + UsernamePasswordAuthenticationToken { + private static final long serialVersionUID = 1955222132884795213L; + private final String url; + private final String workspace; + + public SiteAuthenticationToken(Object principal, Object credentials, + String url, String workspace) { + super(principal, credentials); + this.url = url; + this.workspace = workspace; + } + + public SiteAuthenticationToken(Object principal, Object credentials, + GrantedAuthority[] authorities, String url, String workspace) { + super(principal, credentials, authorities); + this.url = url; + this.workspace = workspace; + } + + public String getUrl() { + return url; + } + + public String getWorkspace() { + return workspace; + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/ArgeoAuthenticationManager.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/ArgeoAuthenticationManager.java new file mode 100644 index 000000000..de60bada2 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/ArgeoAuthenticationManager.java @@ -0,0 +1,28 @@ +package org.argeo.security.core; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.providers.AuthenticationProvider; +import org.springframework.security.providers.ProviderManager; + +public class ArgeoAuthenticationManager extends ProviderManager { + private Log log = LogFactory.getLog(ArgeoAuthenticationManager.class); + + @SuppressWarnings("unchecked") + public void register(AuthenticationProvider authenticationProvider, + Map parameters) { + getProviders().add(authenticationProvider); + if (log.isDebugEnabled()) + log.debug("Registered authentication provider " + parameters); + } + + public void unregister(AuthenticationProvider authenticationProvider, + Map parameters) { + getProviders().remove(authenticationProvider); + if (log.isDebugEnabled()) + log.debug("Unregistered authentication provider " + parameters); + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationProvider.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationProvider.java new file mode 100644 index 000000000..844984a52 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationProvider.java @@ -0,0 +1,136 @@ +package org.argeo.security.jcr; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.security.SiteAuthenticationToken; +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationException; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.providers.AuthenticationProvider; + +/** Connects to a JCR repository and delegate authentication to it. */ +public class JcrAuthenticationProvider implements AuthenticationProvider { + private List repositoryFactories = new ArrayList(); + private final String defaultHome; + private final String userRole; + + public JcrAuthenticationProvider() { + this("ROLE_USER", "home"); + } + + public JcrAuthenticationProvider(String userRole) { + this(userRole, "home"); + } + + public JcrAuthenticationProvider(String defaultHome, String userRole) { + super(); + this.defaultHome = defaultHome; + this.userRole = userRole; + } + + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + if (!(authentication instanceof SiteAuthenticationToken)) + return null; + SiteAuthenticationToken siteAuth = (SiteAuthenticationToken) authentication; + String url = siteAuth.getUrl(); + if (url == null) + return null; + + try { + Map parameters = new HashMap(); + parameters.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url); + + Repository repository = null; + for (Iterator it = repositoryFactories + .iterator(); it.hasNext();) { + repository = it.next().getRepository(parameters); + } + if (repository == null) + return null; + + SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(), + siteAuth.getCredentials().toString().toCharArray()); + String workspace = siteAuth.getWorkspace(); + Session session; + if (workspace == null || workspace.trim().equals("")) + session = repository.login(sp); + else + session = repository.login(sp, workspace); + Node userHome = getUserHome(session); + GrantedAuthority[] authorities = {}; + return new JcrAuthenticationToken(siteAuth.getPrincipal(), + siteAuth.getCredentials(), authorities, url, userHome); + } catch (RepositoryException e) { + throw new ArgeoException( + "Unexpected exception when authenticating to " + url, e); + } + } + + protected GrantedAuthority[] getGrantedAuthorities(Session session) { + return new GrantedAuthority[] { new GrantedAuthorityImpl(userRole) }; + } + + @SuppressWarnings("rawtypes") + public boolean supports(Class authentication) { + return SiteAuthenticationToken.class.isAssignableFrom(authentication); + } + + protected Node getUserHome(Session session) { + String userID = ""; + try { + userID = session.getUserID(); + Node rootNode = session.getRootNode(); + Node homeNode; + if (!rootNode.hasNode(defaultHome)) { + homeNode = rootNode.addNode(defaultHome, ArgeoTypes.ARGEO_HOME); + } else { + homeNode = rootNode.getNode(defaultHome); + } + + Node userHome; + if (!homeNode.hasNode(userID)) { + userHome = homeNode.addNode(userID); + userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + userHome.setProperty(ArgeoNames.ARGEO_USER_ID, userID); + } else { + userHome = homeNode.getNode(userID); + } + session.save(); + return userHome; + } catch (Exception e) { + throw new ArgeoException("Cannot initialize home for user '" + + userID + "'", e); + } + } + + public void setRepositoryFactories( + List repositoryFactories) { + this.repositoryFactories = repositoryFactories; + } + + public String getDefaultHome() { + return defaultHome; + } + + public String getUserRole() { + return userRole; + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationToken.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationToken.java new file mode 100644 index 000000000..865508317 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrAuthenticationToken.java @@ -0,0 +1,34 @@ +package org.argeo.security.jcr; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.ArgeoException; +import org.argeo.security.SiteAuthenticationToken; +import org.springframework.security.GrantedAuthority; + +public class JcrAuthenticationToken extends SiteAuthenticationToken { + private static final long serialVersionUID = -2736830165315486169L; + private final transient Node userHome; + + public JcrAuthenticationToken(Object principal, Object credentials, + GrantedAuthority[] authorities, String url, Node userHome) { + super(principal, credentials, authorities, url, + extractWorkspace(userHome)); + this.userHome = userHome; + } + + private static String extractWorkspace(Node userHome) { + try { + return userHome.getSession().getWorkspace().getName(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot extract workspace of " + userHome, + e); + } + } + + public Node getUserHome() { + return userHome; + } + +} diff --git a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java new file mode 100644 index 000000000..4403def10 --- /dev/null +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoAccessManager.java @@ -0,0 +1,8 @@ +package org.argeo.security.jackrabbit; + +import org.apache.jackrabbit.core.security.DefaultAccessManager; + +/** Intermediary class in order to have a consistent naming in config files. */ +public class ArgeoAccessManager extends DefaultAccessManager { + +} diff --git a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/spring/SpringLoginModule.java b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java similarity index 74% rename from security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/spring/SpringLoginModule.java rename to security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java index c6b456d62..d05cdf9ce 100644 --- a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/spring/SpringLoginModule.java +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java @@ -1,4 +1,4 @@ -package org.argeo.security.jackrabbit.spring; +package org.argeo.security.jackrabbit; import java.security.Principal; import java.security.acl.Group; @@ -9,6 +9,7 @@ import java.util.Set; import javax.jcr.Credentials; import javax.jcr.RepositoryException; import javax.jcr.Session; +import javax.jcr.SimpleCredentials; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.LoginException; @@ -21,7 +22,8 @@ import org.springframework.security.GrantedAuthority; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken; -public class SpringLoginModule extends AbstractLoginModule { +public class ArgeoLoginModule extends AbstractLoginModule { + private String adminRole = "ROLE_ADMIN"; /** * Returns the Spring {@link org.springframework.security.Authentication} @@ -40,13 +42,19 @@ public class SpringLoginModule extends AbstractLoginModule { org.springframework.security.Authentication authen = (org.springframework.security.Authentication) principal; - if (authen instanceof AnonymousAuthenticationToken) - principals.add(new AnonymousPrincipal()); if (authen instanceof SystemAuthentication) principals.add(new AdminPrincipal(authen.getName())); + else if (authen instanceof AnonymousAuthenticationToken) + principals.add(new AnonymousPrincipal()); + else + for (GrantedAuthority ga : authen.getAuthorities()) { + if (adminRole.equals(ga.getAuthority())) + principals.add(new AdminPrincipal(authen.getName())); + } - for (GrantedAuthority authority : authen.getAuthorities()) - principals.add(new GrantedAuthorityPrincipal(authority)); + // override credentials since we did not used the one passed to us + credentials = new SimpleCredentials(authen.getName(), authen + .getCredentials().toString().toCharArray()); return principals; } @@ -65,19 +73,20 @@ public class SpringLoginModule extends AbstractLoginModule { } @Override - protected Authentication getAuthentication(Principal principal, + protected Authentication getAuthentication(final Principal principal, Credentials creds) throws RepositoryException { if (principal instanceof Group) { return null; } return new Authentication() { public boolean canHandle(Credentials credentials) { - return true; + return principal instanceof org.springframework.security.Authentication; } public boolean authenticate(Credentials credentials) throws RepositoryException { - return true; + return ((org.springframework.security.Authentication) principal) + .isAuthenticated(); } }; } 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 new file mode 100644 index 000000000..a3a6d42d6 --- /dev/null +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoSecurityManager.java @@ -0,0 +1,80 @@ +package org.argeo.security.jackrabbit; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.jcr.RepositoryException; +import javax.security.auth.Subject; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.api.security.user.Group; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.jackrabbit.core.DefaultSecurityManager; +import org.apache.jackrabbit.core.security.SystemPrincipal; +import org.argeo.ArgeoException; +import org.springframework.security.Authentication; +import org.springframework.security.GrantedAuthority; + +/** Intermediary class in order to have a consistent naming in config files. */ +public class ArgeoSecurityManager extends DefaultSecurityManager { + private Log log = LogFactory.getLog(ArgeoSecurityManager.class); + + @Override + /** Since this is called once when the session is created, we take the opportunity to synchronize Spring and Jackrabbit users and groups.*/ + public String getUserID(Subject subject, String workspaceName) + throws RepositoryException { + long begin = System.currentTimeMillis(); + + if (!subject.getPrincipals(SystemPrincipal.class).isEmpty()) + return super.getUserID(subject, workspaceName); + + Authentication authen; + Set authens = subject + .getPrincipals(Authentication.class); + if (authens.size() == 0) + throw new ArgeoException("No Spring authentication found in " + + subject); + else + authen = authens.iterator().next(); + + UserManager systemUm = getSystemUserManager(workspaceName); + + String userId = authen.getName(); + User user = (User) systemUm.getAuthorizable(userId); + if (user == null) { + user = systemUm.createUser(userId, authen.getCredentials() + .toString(), authen, null); + log.info(userId + " added as " + user); + } + + List userGroupIds = new ArrayList(); + for (GrantedAuthority ga : authen.getAuthorities()) { + Group group = (Group) systemUm.getAuthorizable(ga.getAuthority()); + if (group == null) { + group = systemUm.createGroup(ga.getAuthority(), + new GrantedAuthorityPrincipal(ga), null); + log.info(ga.getAuthority() + " added as " + group); + } + if (!group.isMember(user)) + group.addMember(user); + userGroupIds.add(ga.getAuthority()); + } + + // check if user has not been removed from some groups + for (Iterator it = user.declaredMemberOf(); it.hasNext();) { + Group group = it.next(); + if (!userGroupIds.contains(group.getID())) + group.removeMember(user); + } + + if (log.isDebugEnabled()) + log.debug("Spring and Jackrabbit Security synchronized for user " + + userId + " in " + (System.currentTimeMillis() - begin) + + " ms"); + return userId; + } +} diff --git a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/spring/GrantedAuthorityPrincipal.java b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java similarity index 78% rename from security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/spring/GrantedAuthorityPrincipal.java rename to security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java index ec6152edb..ffd8d6f39 100644 --- a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/spring/GrantedAuthorityPrincipal.java +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/GrantedAuthorityPrincipal.java @@ -1,11 +1,11 @@ -package org.argeo.security.jackrabbit.spring; +package org.argeo.security.jackrabbit; import java.security.Principal; import org.springframework.security.GrantedAuthority; /** Wraps a {@link GrantedAuthority} as a prin,cipal. */ -public class GrantedAuthorityPrincipal implements Principal { +class GrantedAuthorityPrincipal implements Principal { private final GrantedAuthority grantedAuthority; public GrantedAuthorityPrincipal(GrantedAuthority grantedAuthority) { diff --git a/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/providers/JackrabbitAuthenticationProvider.java b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/providers/JackrabbitAuthenticationProvider.java new file mode 100644 index 000000000..b7dedad79 --- /dev/null +++ b/security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/providers/JackrabbitAuthenticationProvider.java @@ -0,0 +1,38 @@ +package org.argeo.security.jackrabbit.providers; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +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.security.jcr.JcrAuthenticationProvider; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; + +public class JackrabbitAuthenticationProvider extends JcrAuthenticationProvider { + + @Override + protected GrantedAuthority[] getGrantedAuthorities(Session session) { + try { + JackrabbitSession jackrabbitSession = (JackrabbitSession) session; + UserManager userManager = jackrabbitSession.getUserManager(); + User user = (User) userManager.getAuthorizable(session.getUserID()); + List authorities = new ArrayList(); + for (Iterator it = user.memberOf(); it.hasNext();) + authorities.add(new GrantedAuthorityImpl(it.next().getID())); + return authorities + .toArray(new GrantedAuthority[authorities.size()]); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot retrieve authorities for " + + session.getUserID(), e); + } + } + +} diff --git a/server/modules/org.argeo.jackrabbit.webapp/META-INF/MANIFEST.MF b/server/modules/org.argeo.jackrabbit.webapp/META-INF/MANIFEST.MF index 0e6854f79..17709f27a 100644 --- a/server/modules/org.argeo.jackrabbit.webapp/META-INF/MANIFEST.MF +++ b/server/modules/org.argeo.jackrabbit.webapp/META-INF/MANIFEST.MF @@ -21,6 +21,7 @@ Import-Package: javax.jcr, org.springframework.aop.scope;version="2.5.6.SEC01", org.springframework.osgi.web.context.support;version="1.2.1", org.springframework.security;version="2.0.6.RELEASE", + org.springframework.security.providers.anonymous;version="2.0.6.RELEASE", org.springframework.security.ui.webapp;version="2.0.6.RELEASE", org.springframework.web.context;version="2.5.6.SEC01", org.springframework.web.filter;version="2.5.6.SEC01", diff --git a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/applicationContext.xml b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/applicationContext.xml index 5898b1952..42db5686a 100644 --- a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/applicationContext.xml +++ b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/applicationContext.xml @@ -10,8 +10,8 @@ - + diff --git a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/osgi.xml b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/osgi.xml index 80b804ef6..34fe6ad81 100644 --- a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/osgi.xml +++ b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/osgi.xml @@ -13,6 +13,6 @@ unbind-method="unregister" /> - + + \ No newline at end of file diff --git a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/security-filters.xml b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/security-filters.xml new file mode 100644 index 000000000..c969b4d82 --- /dev/null +++ b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/security-filters.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/security.xml b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/security.xml index 2e08bfdb0..caba1aa9f 100644 --- a/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/security.xml +++ b/server/modules/org.argeo.jackrabbit.webapp/WEB-INF/security.xml @@ -7,8 +7,26 @@ http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"> - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/MANIFEST.MF b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/MANIFEST.MF index fbabbb2a2..1131a3861 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/MANIFEST.MF +++ b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/MANIFEST.MF @@ -10,6 +10,9 @@ Import-Package: com.mysql.jdbc;version="[5.0.0,6.0.0)";resolution:=optional, org.apache.xalan.processor, org.argeo.jackrabbit, org.argeo.jcr, + org.argeo.security.jackrabbit.providers, + org.argeo.security.jcr, org.h2;version="[1.0.0,2.0.0)";resolution:=optional, org.postgresql;version="[8.0.0,9.0.0)";resolution:=optional, - org.springframework.beans.factory.config + org.springframework.beans.factory.config, + org.springframework.security.providers;version="2.0.6.RELEASE" diff --git a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml index 222f30290..9721feebb 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml @@ -9,6 +9,18 @@ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> + + + + + + + + + @@ -31,5 +43,7 @@ + \ No newline at end of file diff --git a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml index ccb517d4f..a03a421b4 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml @@ -33,4 +33,11 @@ + + + + + + + \ No newline at end of file diff --git a/server/modules/org.argeo.node.repo.jackrabbit/repository-h2.xml b/server/modules/org.argeo.node.repo.jackrabbit/repository-h2.xml index 3040ad176..5e63e5de8 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/repository-h2.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/repository-h2.xml @@ -67,15 +67,12 @@ - - + - + \ No newline at end of file diff --git a/server/modules/org.argeo.node.repo.jackrabbit/repository-postgresql.xml b/server/modules/org.argeo.node.repo.jackrabbit/repository-postgresql.xml index 0bfdef42b..4c4f4fb41 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/repository-postgresql.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/repository-postgresql.xml @@ -67,15 +67,12 @@ - - + - + \ No newline at end of file diff --git a/server/modules/org.argeo.server.ads.server/META-INF/spring/server.xml b/server/modules/org.argeo.server.ads.server/META-INF/spring/server.xml index 2bf80b941..84407be13 100644 --- a/server/modules/org.argeo.server.ads.server/META-INF/spring/server.xml +++ b/server/modules/org.argeo.server.ads.server/META-INF/spring/server.xml @@ -79,7 +79,7 @@ --> - + diff --git a/server/plugins/org.argeo.jcr.ui.explorer/org.argeo.jcr.ui.explorer.product b/server/plugins/org.argeo.jcr.ui.explorer/org.argeo.jcr.ui.explorer.product index 885f079c3..3d5c77352 100644 --- a/server/plugins/org.argeo.jcr.ui.explorer/org.argeo.jcr.ui.explorer.product +++ b/server/plugins/org.argeo.jcr.ui.explorer/org.argeo.jcr.ui.explorer.product @@ -117,11 +117,14 @@ + + + @@ -132,12 +135,17 @@ + + + + + @@ -171,6 +179,7 @@ + diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitRepositoryFactory.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitRepositoryFactory.java index ac3cce9fd..9f2926cb3 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitRepositoryFactory.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitRepositoryFactory.java @@ -1,24 +1,46 @@ package org.argeo.jackrabbit; +import java.util.HashMap; import java.util.Map; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.RepositoryFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; +import org.argeo.ArgeoException; import org.argeo.jcr.ArgeoJcrConstants; -import org.argeo.jcr.DefaultRepositoryRegister; +import org.argeo.jcr.DefaultRepositoryFactory; -public class JackrabbitRepositoryFactory extends DefaultRepositoryRegister +public class JackrabbitRepositoryFactory extends DefaultRepositoryFactory implements RepositoryFactory, ArgeoJcrConstants { + private final static Log log = LogFactory + .getLog(JackrabbitRepositoryFactory.class); @SuppressWarnings("rawtypes") public Repository getRepository(Map parameters) throws RepositoryException { - String alias; - if (parameters.containsKey(JCR_REPOSITORY_ALIAS)) { - alias = parameters.get(JCR_REPOSITORY_ALIAS).toString(); + Repository repository = super.getRepository(parameters); + if (repository != null) + return repository; + + if (parameters.containsKey(JCR_REPOSITORY_URI)) { + String uri = parameters.get(JCR_REPOSITORY_URI).toString(); + Map params = new HashMap(); + + params.put(JcrUtils.REPOSITORY_URI, uri); + repository = new Jcr2davRepositoryFactory().getRepository(params); + if (repository == null) + throw new ArgeoException("Remote Davex repository " + uri + + " not found"); + log.info("Initialized remote Jackrabbit repository " + repository + + " from uri " + uri); + } - return null; + + return repository; } } diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java index ded491510..eab7451db 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java @@ -16,6 +16,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.server.SessionProvider; +import org.argeo.ArgeoException; /** To be injected, typically of scope="session" */ public class SimpleSessionProvider implements SessionProvider, Serializable { @@ -24,8 +25,7 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { private final static Log log = LogFactory .getLog(SimpleSessionProvider.class); - private transient Map sessions = Collections - .synchronizedMap(new HashMap()); + private transient Map sessions; private Credentials credentials = null; @@ -33,10 +33,22 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { String workspace) throws LoginException, ServletException, RepositoryException { + // since sessions is transient it can be restored from the session + if (sessions == null) + sessions = Collections + .synchronizedMap(new HashMap()); + if (!sessions.containsKey(workspace)) { - Session session = rep.login(credentials, workspace); - sessions.put(workspace, session); - return session; + try { + Session session = rep.login(credentials, workspace); + if (log.isDebugEnabled()) + log.debug("User " + session.getUserID() + " logged into " + + request.getServletPath()); + sessions.put(workspace, session); + return session; + } catch (Exception e) { + throw new ArgeoException("Cannot open session", e); + } } else { Session session = sessions.get(workspace); if (!session.isLive()) { @@ -49,17 +61,19 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { } public void releaseSession(Session session) { - if (log.isDebugEnabled()) - log.debug("Releasing JCR session " + session); - // session.logout(); - // FIXME: find a way to log out when the HTTP session is expired + if (log.isTraceEnabled()) + log.trace("Releasing JCR session " + session); + } + + public void init() { } public void dispose() { - for (String workspace : sessions.keySet()) { - Session session = sessions.get(workspace); - if (session.isLive()) - session.logout(); - } + if (sessions != null) + for (String workspace : sessions.keySet()) { + Session session = sessions.get(workspace); + if (session.isLive()) + session.logout(); + } } } diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryFactory.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryFactory.java new file mode 100644 index 000000000..25c355467 --- /dev/null +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryFactory.java @@ -0,0 +1,24 @@ +package org.argeo.jcr; + +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; + +public class DefaultRepositoryFactory extends DefaultRepositoryRegister + implements RepositoryFactory, ArgeoJcrConstants { +// private final static Log log = LogFactory +// .getLog(DefaultRepositoryFactory.class); + + @SuppressWarnings("rawtypes") + public Repository getRepository(Map parameters) throws RepositoryException { + if (parameters.containsKey(JCR_REPOSITORY_ALIAS)) { + String alias = parameters.get(JCR_REPOSITORY_ALIAS).toString(); + if (getRepositories().containsKey(alias)) + return getRepositories().get(alias); + } + return null; + } + +} -- 2.30.2