From 2745f0c8c57d9468855179d56f858fb2448f779c Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 22 Mar 2011 12:49:03 +0000 Subject: [PATCH] Fix various issues with security git-svn-id: https://svn.argeo.org/commons/trunk@4337 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../META-INF/MANIFEST.MF | 1 + .../META-INF/spring/ldap-jcr.xml | 4 + .../META-INF/spring/ldap-osgi.xml | 18 +-- .../META-INF/spring/ldap.xml | 20 +-- .../META-INF/spring/osgi.xml | 2 +- .../META-INF/spring/services.xml | 8 +- .../security/equinox/SpringLoginModule.java | 41 +++--- .../META-INF/spring/common.xml | 9 ++ .../META-INF/spring/osgi.xml | 2 +- .../META-INF/spring/views.xml | 2 +- .../security/ui/rap/SecureEntryPoint.java | 31 +---- .../META-INF/spring/commands.xml | 2 +- .../META-INF/spring/osgi.xml | 3 +- .../ui/commands/OpenChangePasswordDialog.java | 10 +- .../ui/dialogs/ChangePasswordDialog.java | 10 +- .../runtime/org.argeo.security.core/pom.xml | 2 + .../org/argeo/security/CurrentUserDao.java | 1 + .../argeo/security/CurrentUserService.java | 1 + .../core/ArgeoAuthenticationManager.java | 28 ----- .../core/AuthenticationProvidersRegister.java | 48 +++++++ .../core/DefaultCurrentUserService.java | 1 + .../jcr/SecureThreadBoundSession.java | 31 +++++ .../security/jackrabbit/ArgeoLoginModule.java | 23 ++++ .../ldap/ArgeoLdapUserDetailsManager.java | 48 +++++++ .../ldap/jcr/JcrUserDetailsContextMapper.java | 14 ++- .../META-INF/spring/noderepo-osgi.xml | 11 -- .../META-INF/spring/noderepo.xml | 5 - .../jcr/ThreadBoundJcrSessionFactory.java | 117 +++++++++++++++--- 28 files changed, 332 insertions(+), 161 deletions(-) create mode 100644 security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml delete 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/core/AuthenticationProvidersRegister.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java create mode 100644 security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/MANIFEST.MF b/security/modules/org.argeo.security.dao.ldap/META-INF/MANIFEST.MF index 24e756417..60bdd8210 100644 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/MANIFEST.MF +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/MANIFEST.MF @@ -4,6 +4,7 @@ Import-Package: com.sun.jndi.ldap;resolution:=optional, javax.jcr;version="[2.0.0,3.0.0)", org.argeo.jcr, org.argeo.security, + org.argeo.security.jcr, org.argeo.security.ldap, org.argeo.security.ldap.jcr, org.argeo.security.ldap.nature, diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-jcr.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-jcr.xml index b1dc9a9e1..8e0ebc052 100644 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-jcr.xml +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-jcr.xml @@ -55,6 +55,10 @@ + + + + diff --git a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml index 8deaeacde..d0ad0eca9 100644 --- a/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml +++ b/security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml @@ -7,14 +7,14 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - - - - - - - - + + + + + + + @@ -22,7 +22,7 @@ - + - + + + - - - - - - - - - - - - - - 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 6c21dbd26..91f2e8429 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 @@ -16,7 +16,7 @@ - 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 e04e2f197..f49de0914 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 @@ -21,8 +21,14 @@ - + + + + + + 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 90e8b3dec..c25be6afb 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 @@ -1,7 +1,7 @@ package org.argeo.security.equinox; import java.util.Map; -import java.util.concurrent.Executor; +import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; @@ -11,6 +11,8 @@ import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.TextOutputCallback; import javax.security.auth.login.LoginException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.argeo.security.SiteAuthenticationToken; import org.springframework.security.Authentication; import org.springframework.security.AuthenticationManager; @@ -20,11 +22,14 @@ import org.springframework.security.providers.jaas.SecurityContextLoginModule; /** Login module which caches one subject per thread. */ public class SpringLoginModule extends SecurityContextLoginModule { + private final static Log log = LogFactory.getLog(SpringLoginModule.class); + private AuthenticationManager authenticationManager; - private Executor systemExecutor; private CallbackHandler callbackHandler; + private Subject subject; + public SpringLoginModule() { } @@ -33,25 +38,21 @@ public class SpringLoginModule extends SecurityContextLoginModule { public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { super.initialize(subject, callbackHandler, sharedState, options); - // this.subject.set(subject); this.callbackHandler = callbackHandler; + this.subject = subject; } public boolean login() throws LoginException { + // try to retrieve Authentication from Subject + Set auths = subject.getPrincipals(Authentication.class); + if (auths.size() > 0) + SecurityContextHolder.getContext().setAuthentication( + auths.iterator().next()); + // thread already logged in if (SecurityContextHolder.getContext().getAuthentication() != null) return super.login(); - // if (getSubject().getPrincipals(Authentication.class).size() == 1) { - // registerAuthentication(getSubject() - // .getPrincipals(Authentication.class).iterator().next()); - // return super.login(); - // } else if (getSubject().getPrincipals(Authentication.class).size() > - // 1) { - // throw new LoginException( - // "Multiple Authentication principals not supported: " - // + getSubject().getPrincipals(Authentication.class)); - // } else { // ask for username and password Callback label = new TextOutputCallback(TextOutputCallback.INFORMATION, "Required login"); @@ -90,13 +91,10 @@ public class SpringLoginModule extends SecurityContextLoginModule { username, password, url, workspace); try { - Authentication authentication = authenticationManager .authenticate(credentials); registerAuthentication(authentication); boolean res = super.login(); - // if (log.isDebugEnabled()) - // log.debug("User " + username + " logged in"); return res; } catch (BadCredentialsException bce) { throw bce; @@ -111,6 +109,8 @@ public class SpringLoginModule extends SecurityContextLoginModule { @Override public boolean logout() throws LoginException { +// if (log.isDebugEnabled()) +// log.debug("logout subject=" + subject); return super.logout(); } @@ -129,13 +129,4 @@ public class SpringLoginModule extends SecurityContextLoginModule { AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } - - public void setSystemExecutor(Executor systemExecutor) { - this.systemExecutor = systemExecutor; - } - - // protected Subject getSubject() { - // return subject.get(); - // } - } 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 new file mode 100644 index 000000000..131ccdac2 --- /dev/null +++ b/security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/security/plugins/org.argeo.security.ui.admin/META-INF/spring/osgi.xml b/security/plugins/org.argeo.security.ui.admin/META-INF/spring/osgi.xml index 690dd16a9..b0947860f 100644 --- a/security/plugins/org.argeo.security.ui.admin/META-INF/spring/osgi.xml +++ b/security/plugins/org.argeo.security.ui.admin/META-INF/spring/osgi.xml @@ -8,7 +8,7 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" osgi:default-timeout="30000"> - diff --git a/security/plugins/org.argeo.security.ui.admin/META-INF/spring/views.xml b/security/plugins/org.argeo.security.ui.admin/META-INF/spring/views.xml index cc5fbb57d..49cf9d8a0 100644 --- a/security/plugins/org.argeo.security.ui.admin/META-INF/spring/views.xml +++ b/security/plugins/org.argeo.security.ui.admin/META-INF/spring/views.xml @@ -7,7 +7,7 @@ - + diff --git a/security/plugins/org.argeo.security.ui.rap/src/main/java/org/argeo/security/ui/rap/SecureEntryPoint.java b/security/plugins/org.argeo.security.ui.rap/src/main/java/org/argeo/security/ui/rap/SecureEntryPoint.java index 50f74e9c7..cfc1ca215 100644 --- a/security/plugins/org.argeo.security.ui.rap/src/main/java/org/argeo/security/ui/rap/SecureEntryPoint.java +++ b/security/plugins/org.argeo.security.ui.rap/src/main/java/org/argeo/security/ui/rap/SecureEntryPoint.java @@ -1,7 +1,6 @@ package org.argeo.security.ui.rap; import java.security.PrivilegedAction; -import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.login.LoginException; @@ -12,7 +11,6 @@ import org.argeo.eclipse.ui.dialogs.Error; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.ErrorDialog; -import org.eclipse.rwt.RWT; import org.eclipse.rwt.lifecycle.IEntryPoint; import org.eclipse.rwt.service.SessionStoreEvent; import org.eclipse.rwt.service.SessionStoreListener; @@ -21,24 +19,15 @@ import org.eclipse.ui.PlatformUI; import org.eclipse.ui.application.IWorkbenchWindowConfigurer; import org.eclipse.ui.application.WorkbenchAdvisor; import org.eclipse.ui.application.WorkbenchWindowAdvisor; -import org.springframework.security.Authentication; -import org.springframework.security.context.SecurityContextHolder; public class SecureEntryPoint implements IEntryPoint, SessionStoreListener { private Log log = LogFactory.getLog(SecureEntryPoint.class); - private final static String SECURITY_CONTEXT_ATTRIBUTE = "securityContextAttribute"; - @Override public int createUI() { -// log.debug("THREAD=" + Thread.currentThread().getId() -// + ", RWT.getSessionStore().getId()=" -// + RWT.getSessionStore().getId()); - - Authentication authen = (Authentication) RWT.getSessionStore() - .getAttribute(SECURITY_CONTEXT_ATTRIBUTE); - if (authen != null) - SecurityContextHolder.getContext().setAuthentication(authen); + // log.debug("THREAD=" + Thread.currentThread().getId() + // + ", RWT.getSessionStore().getId()=" + // + RWT.getSessionStore().getId()); Integer returnCode = null; Display display = PlatformUI.createDisplay(); @@ -47,18 +36,10 @@ public class SecureEntryPoint implements IEntryPoint, SessionStoreListener { Boolean retry = true; while (retry) { try { - // if (authen == null) - // SecureRapActivator.getLoginContext().login(); + // force login in order to give Spring Security a chance to + // load + SecureRapActivator.getLoginContext().login(); subject = SecureRapActivator.getLoginContext().getSubject(); - Set auths = subject - .getPrincipals(Authentication.class); - if (auths.size() > 0) - SecurityContextHolder.getContext().setAuthentication( - auths.iterator().next()); - // authen = SecurityContextHolder.getContext() - // .getAuthentication(); - // RWT.getSessionStore().setAttribute( - // SECURITY_CONTEXT_ATTRIBUTE, authen); retry = false; } catch (LoginException e) { Error.show("Cannot login", e); diff --git a/security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml b/security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml index c5fe0e337..1dc8d53ce 100644 --- a/security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml +++ b/security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml @@ -6,6 +6,6 @@ - + diff --git a/security/plugins/org.argeo.security.ui/META-INF/spring/osgi.xml b/security/plugins/org.argeo.security.ui/META-INF/spring/osgi.xml index 2477c93a8..a0d30b506 100644 --- a/security/plugins/org.argeo.security.ui/META-INF/spring/osgi.xml +++ b/security/plugins/org.argeo.security.ui/META-INF/spring/osgi.xml @@ -8,5 +8,6 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" osgi:default-timeout="30000"> - + \ No newline at end of file diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/commands/OpenChangePasswordDialog.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/commands/OpenChangePasswordDialog.java index bd6da3657..ab52b116c 100644 --- a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/commands/OpenChangePasswordDialog.java +++ b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/commands/OpenChangePasswordDialog.java @@ -1,25 +1,25 @@ package org.argeo.security.ui.commands; -import org.argeo.security.CurrentUserService; import org.argeo.security.ui.dialogs.ChangePasswordDialog; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.ui.handlers.HandlerUtil; +import org.springframework.security.userdetails.UserDetailsManager; /** Opens the change password dialog. */ public class OpenChangePasswordDialog extends AbstractHandler { - private CurrentUserService currentUserService; + private UserDetailsManager userDetailsManager; public Object execute(ExecutionEvent event) throws ExecutionException { ChangePasswordDialog dialog = new ChangePasswordDialog( - HandlerUtil.getActiveShell(event), currentUserService); + HandlerUtil.getActiveShell(event), userDetailsManager); dialog.open(); return null; } - public void setCurrentUserService(CurrentUserService currentUserService) { - this.currentUserService = currentUserService; + public void setUserDetailsManager(UserDetailsManager userDetailsManager) { + this.userDetailsManager = userDetailsManager; } } diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/ChangePasswordDialog.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/ChangePasswordDialog.java index 21b37cb82..90d7320b0 100644 --- a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/ChangePasswordDialog.java +++ b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/ChangePasswordDialog.java @@ -1,7 +1,6 @@ package org.argeo.security.ui.dialogs; import org.argeo.ArgeoException; -import org.argeo.security.CurrentUserService; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.dialogs.TitleAreaDialog; import org.eclipse.swt.SWT; @@ -13,16 +12,17 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; +import org.springframework.security.userdetails.UserDetailsManager; /** Dialog to change the current user password */ public class ChangePasswordDialog extends TitleAreaDialog { private Text currentPassword, newPassword1, newPassword2; - private CurrentUserService securityService; + private UserDetailsManager userDetailsManager; public ChangePasswordDialog(Shell parentShell, - CurrentUserService securityService) { + UserDetailsManager securityService) { super(parentShell); - this.securityService = securityService; + this.userDetailsManager = securityService; } protected Point getInitialSize() { @@ -48,7 +48,7 @@ public class ChangePasswordDialog extends TitleAreaDialog { protected void okPressed() { if (!newPassword1.getText().equals(newPassword2.getText())) throw new ArgeoException("Passwords are different"); - securityService.updateCurrentUserPassword(currentPassword.getText(), + userDetailsManager.changePassword(currentPassword.getText(), newPassword1.getText()); close(); } diff --git a/security/runtime/org.argeo.security.core/pom.xml b/security/runtime/org.argeo.security.core/pom.xml index 8ffada70e..d9e943cc2 100644 --- a/security/runtime/org.argeo.security.core/pom.xml +++ b/security/runtime/org.argeo.security.core/pom.xml @@ -32,6 +32,8 @@ org.argeo.security.* + org.springframework.context, org.springframework.beans.factory, diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserDao.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserDao.java index 736b0bafd..37b6d7735 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserDao.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserDao.java @@ -19,6 +19,7 @@ package org.argeo.security; /** * Access to user backend for the currently logged in user */ +@Deprecated public interface CurrentUserDao { public void updateCurrentUserPassword(String oldPassword, String newPassword); diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserService.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserService.java index a82bc9bac..9ae88e37d 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserService.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserService.java @@ -2,6 +2,7 @@ package org.argeo.security; import java.util.Map; +@Deprecated public interface CurrentUserService { public ArgeoUser getCurrentUser(); 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 deleted file mode 100644 index de60bada2..000000000 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/ArgeoAuthenticationManager.java +++ /dev/null @@ -1,28 +0,0 @@ -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/core/AuthenticationProvidersRegister.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/AuthenticationProvidersRegister.java new file mode 100644 index 000000000..bd14659c8 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/AuthenticationProvidersRegister.java @@ -0,0 +1,48 @@ +package org.argeo.security.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * Maintains a list of authentication providers injected in to a provider + * manager, in order to avoid issues with OSGi services and use packages. + */ +public class AuthenticationProvidersRegister implements InitializingBean { + private Log log = LogFactory.getLog(AuthenticationProvidersRegister.class); + + private List providers = new ArrayList(); + private List defaultProviders = new ArrayList(); + + public void register(Object authenticationProvider, + Map parameters) { + providers.add(authenticationProvider); + if (log.isDebugEnabled()) + log.debug("Registered authentication provider " + parameters); + } + + public void unregister(Object authenticationProvider, + Map parameters) { + providers.remove(authenticationProvider); + if (log.isDebugEnabled()) + log.debug("Unregistered authentication provider " + parameters); + } + + public List getProviders() { + return providers; + } + + public void setDefaultProviders( + List defaultProviders) { + this.defaultProviders = defaultProviders; + } + + public void afterPropertiesSet() throws Exception { + providers.addAll(defaultProviders); + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/DefaultCurrentUserService.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/DefaultCurrentUserService.java index 34e4375d8..8e330cb11 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/DefaultCurrentUserService.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/DefaultCurrentUserService.java @@ -23,6 +23,7 @@ import org.argeo.security.CurrentUserDao; import org.argeo.security.CurrentUserService; import org.argeo.security.UserNature; +@Deprecated public class DefaultCurrentUserService implements CurrentUserService { private CurrentUserDao currentUserDao; diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java new file mode 100644 index 000000000..c83f3b594 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java @@ -0,0 +1,31 @@ +package org.argeo.security.jcr; + +import javax.jcr.Session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.jcr.ThreadBoundJcrSessionFactory; +import org.springframework.security.Authentication; +import org.springframework.security.context.SecurityContextHolder; + +public class SecureThreadBoundSession extends ThreadBoundJcrSessionFactory { + private final static Log log = LogFactory + .getLog(SecureThreadBoundSession.class); + + @Override + protected Session preCall(Session session) { + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + if (authentication != null) { + if (!session.getUserID().equals( + authentication.getPrincipal().toString())) { + log.warn("Current session has user ID " + session.getUserID() + + " while authentication is " + authentication + + ". Re-login."); + return login(); + } + } + return super.preCall(session); + } + +} 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 1ab93edbb..73ec76a8f 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 @@ -50,6 +50,7 @@ public class ArgeoLoginModule extends AbstractLoginModule { principals.add(new AnonymousPrincipal()); else for (GrantedAuthority ga : authen.getAuthorities()) { + // FIXME: make it more generic if (adminRole.equals(ga.getAuthority())) principals.add(new AdminPrincipal(authen.getName())); } @@ -61,6 +62,28 @@ public class ArgeoLoginModule extends AbstractLoginModule { return principals; } + /** + * Super implementation removes all {@link Principal}, the Spring + * {@link org.springframework.security.Authentication} as well. Here we + * simply clear Jackrabbit related {@link Principal}s. + */ + @Override + public boolean logout() throws LoginException { + Set adminPrincipals = subject + .getPrincipals(AdminPrincipal.class); + Set anonymousPrincipals = subject + .getPrincipals(AnonymousPrincipal.class); + Set thisCredentials = subject + .getPublicCredentials(SimpleCredentials.class); + if (thisCredentials != null) + thisCredentials.clear(); + if (adminPrincipals != null) + adminPrincipals.clear(); + if (anonymousPrincipals != null) + anonymousPrincipals.clear(); + return true; + } + @SuppressWarnings("rawtypes") @Override protected void doInit(CallbackHandler callbackHandler, Session session, diff --git a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java new file mode 100644 index 000000000..54ef836a0 --- /dev/null +++ b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java @@ -0,0 +1,48 @@ +package org.argeo.security.ldap; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; + +import org.springframework.ldap.core.ContextSource; +import org.springframework.security.providers.encoding.PasswordEncoder; +import org.springframework.security.userdetails.ldap.LdapUserDetailsManager; + +/** Extends {@link LdapUserDetailsManager} by adding password encoding support. */ +public class ArgeoLdapUserDetailsManager extends LdapUserDetailsManager { + private PasswordEncoder passwordEncoder; + private final Random random; + + public ArgeoLdapUserDetailsManager(ContextSource contextSource) { + super(contextSource); + this.random = createRandom(); + } + + private static Random createRandom() { + try { + return SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + return new Random(System.currentTimeMillis()); + } + } + + @Override + public void changePassword(String oldPassword, String newPassword) { + super.changePassword(oldPassword, encodePassword(newPassword)); + } + + protected String encodePassword(String password) { + if (!password.startsWith("{")) { + byte[] salt = new byte[16]; + random.nextBytes(salt); + return passwordEncoder.encodePassword(password, salt); + } else { + return password; + } + } + + public void setPasswordEncoder(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + +} diff --git a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java index 0e8dbab32..bfbed8558 100644 --- a/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java +++ b/security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java @@ -9,16 +9,13 @@ import java.util.Random; import java.util.concurrent.Executor; import javax.jcr.Node; -import javax.jcr.Repository; import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; import javax.jcr.Session; import javax.jcr.nodetype.NodeType; 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; @@ -76,10 +73,15 @@ public class JcrUserDetailsContextMapper implements UserDetailsContextMapper, userHomePathT.append(userHomepath); } }; - if (SecurityContextHolder.getContext().getAuthentication() == null)// authentication + + if (SecurityContextHolder.getContext().getAuthentication() == null) { + // authentication systemExecutor.execute(action); - else + JcrUtils.logoutQuietly(session); + } else { + // authenticated user action.run(); + } // password byte[] arr = (byte[]) ctx @@ -157,7 +159,7 @@ public class JcrUserDetailsContextMapper implements UserDetailsContextMapper, final JcrUserDetails jcrUserDetails = (JcrUserDetails) user; // systemExecutor.execute(new Runnable() { // public void run() { -// Session session = null; + // Session session = null; try { // Repository nodeRepo = JcrUtils.getRepositoryByAlias( // repositoryFactory, ArgeoJcrConstants.ALIAS_NODE); 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 755daaafb..c926720f4 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 @@ -23,15 +23,4 @@ - - - - - - - - - - \ 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 7f10c14c9..ec8bfca74 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 @@ -28,9 +28,4 @@ - - - - - \ No newline at end of file diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java index 9428c6959..1a37e3e85 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java @@ -21,7 +21,10 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; import javax.jcr.LoginException; import javax.jcr.Repository; @@ -34,19 +37,17 @@ import org.apache.commons.logging.LogFactory; import org.argeo.ArgeoException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; /** Proxy JCR sessions and attach them to calling threads. */ public class ThreadBoundJcrSessionFactory implements FactoryBean, - DisposableBean { + InitializingBean, DisposableBean { private final static Log log = LogFactory .getLog(ThreadBoundJcrSessionFactory.class); private Repository repository; - private final List activeSessions = Collections - .synchronizedList(new ArrayList()); private ThreadLocal session = new ThreadLocal(); - private boolean destroying = false; private final Session proxiedSession; /** If workspace is null, default will be used. */ private String workspace = null; @@ -55,6 +56,15 @@ public class ThreadBoundJcrSessionFactory implements FactoryBean, private String defaultPassword = "demo"; private Boolean forceDefaultCredentials = false; + private boolean active = true; + + // monitoring + private final List threads = Collections + .synchronizedList(new ArrayList()); + private final Map activeSessions = Collections + .synchronizedMap(new HashMap()); + private MonitoringThread monitoringThread; + public ThreadBoundJcrSessionFactory() { Class[] interfaces = { Session.class }; proxiedSession = (Session) Proxy.newProxyInstance(getClass() @@ -64,6 +74,14 @@ public class ThreadBoundJcrSessionFactory implements FactoryBean, /** Logs in to the repository using various strategies. */ protected Session login() { + // discard sesison previoussly attached to this thread + Thread thread = Thread.currentThread(); + if (activeSessions.containsKey(thread.getId())) { + Session oldSession = activeSessions.remove(thread.getId()); + oldSession.logout(); + session.remove(); + } + Session newSession = null; // first try to login without credentials, assuming the underlying login // module will have dealt with authentication (typically using Spring @@ -89,11 +107,15 @@ public class ThreadBoundJcrSessionFactory implements FactoryBean, throw new ArgeoException("Cannot log in to repository", e); } + session.set(newSession); // Log and monitor new session if (log.isTraceEnabled()) log.trace("Logged in to JCR session " + newSession + "; userId=" + newSession.getUserID()); - activeSessions.add(newSession); + + // monitoring + activeSessions.put(thread.getId(), newSession); + threads.add(thread); return newSession; } @@ -101,16 +123,31 @@ public class ThreadBoundJcrSessionFactory implements FactoryBean, return proxiedSession; } - public void destroy() throws Exception { + public void afterPropertiesSet() throws Exception { + monitoringThread = new MonitoringThread(); + monitoringThread.start(); + } + + public synchronized void destroy() throws Exception { if (log.isDebugEnabled()) log.debug("Cleaning up " + activeSessions.size() + " active JCR sessions..."); - destroying = true; - for (Session sess : activeSessions) { + deactivate(); + for (Session sess : activeSessions.values()) { sess.logout(); } activeSessions.clear(); + monitoringThread.join(1000); + } + + protected Boolean isActive() { + return active; + } + + protected synchronized void deactivate() { + active = false; + notifyAll(); } public Class getObjectType() { @@ -121,6 +158,15 @@ public class ThreadBoundJcrSessionFactory implements FactoryBean, return true; } + /** + * Called before a method is actually called, allowing to check the session + * or re-login it (e.g. if authentication has changed). The default + * implementation returns the session. + */ + protected Session preCall(Session session) { + return session; + } + public void setRepository(Repository repository) { this.repository = repository; } @@ -152,28 +198,61 @@ public class ThreadBoundJcrSessionFactory implements FactoryBean, else if ("toString".equals(method.getName()))// maybe logging return "Uninitialized Argeo thread bound JCR session"; threadSession = login(); - session.set(threadSession); } Object ret = method.invoke(threadSession, args); if ("logout".equals(method.getName())) { - session.remove(); - if (!destroying) - activeSessions.remove(threadSession); - if (log.isTraceEnabled()) - log.trace("Logged out from JCR session " + threadSession - + "; userId=" + threadSession.getUserID()); + synchronized (ThreadBoundJcrSessionFactory.this) { + session.remove(); + Thread thread = Thread.currentThread(); + if (isActive()) { + activeSessions.remove(thread.getId()); + threads.remove(thread); + } + if (log.isTraceEnabled()) + log.trace("Logged out JCR session (userId=" + + threadSession.getUserID() + ") on thread " + + thread.getId()); + } } return ret; } } - - protected class MonitoringThread extends Thread{ + + /** Monitors registered thread in order to clean up dead ones. */ + private class MonitoringThread extends Thread { @Override public void run() { - Thread thread=null; + while (isActive()) { + Iterator it = threads.iterator(); + while (it.hasNext()) { + Thread thread = it.next(); + if (!thread.isAlive() && isActive()) { + if (activeSessions.containsKey(thread.getId())) { + Session session = activeSessions + .get(thread.getId()); + activeSessions.remove(thread.getId()); + session.logout(); + if (log.isDebugEnabled()) + log.debug("Cleaned up JCR session (userID=" + + session.getUserID() + + ") from dead thread " + + thread.getId()); + } + it.remove(); + } + } + + synchronized (ThreadBoundJcrSessionFactory.this) { + try { + ThreadBoundJcrSessionFactory.this.wait(1000); + } catch (InterruptedException e) { + // silent + } + } + } } - + } } -- 2.30.2