Fix various issues with security
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 22 Mar 2011 12:49:03 +0000 (12:49 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 22 Mar 2011 12:49:03 +0000 (12:49 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@4337 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

28 files changed:
security/modules/org.argeo.security.dao.ldap/META-INF/MANIFEST.MF
security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-jcr.xml
security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap-osgi.xml
security/modules/org.argeo.security.dao.ldap/META-INF/spring/ldap.xml
security/modules/org.argeo.security.services/META-INF/spring/osgi.xml
security/modules/org.argeo.security.services/META-INF/spring/services.xml
security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java
security/plugins/org.argeo.security.ui.admin/META-INF/spring/common.xml [new file with mode: 0644]
security/plugins/org.argeo.security.ui.admin/META-INF/spring/osgi.xml
security/plugins/org.argeo.security.ui.admin/META-INF/spring/views.xml
security/plugins/org.argeo.security.ui.rap/src/main/java/org/argeo/security/ui/rap/SecureEntryPoint.java
security/plugins/org.argeo.security.ui/META-INF/spring/commands.xml
security/plugins/org.argeo.security.ui/META-INF/spring/osgi.xml
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/commands/OpenChangePasswordDialog.java
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/ChangePasswordDialog.java
security/runtime/org.argeo.security.core/pom.xml
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserDao.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/CurrentUserService.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/ArgeoAuthenticationManager.java [deleted file]
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/AuthenticationProvidersRegister.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/DefaultCurrentUserService.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java [new file with mode: 0644]
security/runtime/org.argeo.security.jackrabbit/src/main/java/org/argeo/security/jackrabbit/ArgeoLoginModule.java
security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/ArgeoLdapUserDetailsManager.java [new file with mode: 0644]
security/runtime/org.argeo.security.ldap/src/main/java/org/argeo/security/ldap/jcr/JcrUserDetailsContextMapper.java
server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml
server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml
server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java

index 24e756417dcfbb0ab04c10b9b160bef29c644ff3..60bdd82106a79cb377d204e63c2ee33332d45c96 100644 (file)
@@ -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,
index b1dc9a9e16e390831436a6d48c5ce5e35dbad545..8e0ebc0526c989c62d47a7b7bf549c8c1df5864a 100644 (file)
                                </entry>
                        </map>
                </property>
+       </bean>
 
+       <bean id="nodeSession" class="org.argeo.security.jcr.SecureThreadBoundSession">
+               <property name="repository" ref="nodeRepository" />
        </bean>
+
 </beans>
index 8deaeacdea6af41542c282890364d6d7109b1934..d0ad0eca945419762ca5213a618f318b90e70caa 100644 (file)
@@ -7,14 +7,14 @@
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">\r
 \r
        <!-- REFERENCES -->\r
-<!--   <list id="userNatureMappers" interface="org.argeo.security.ldap.UserNatureMapper" -->\r
-<!--           cardinality="0..N" /> -->\r
-<!--   <reference id="repositoryFactory" interface="javax.jcr.RepositoryFactory" -->\r
-<!--           cardinality="0..1"> -->\r
-<!--           <listener ref="jcrUserDetailsContextMapper" bind-method="register" -->\r
-<!--                   unbind-method="unregister" /> -->\r
-<!--   </reference> -->\r
-       <reference id="nodeSession" interface="javax.jcr.Session"\r
+       <!-- <list id="userNatureMappers" interface="org.argeo.security.ldap.UserNatureMapper" -->\r
+       <!-- cardinality="0..N" /> -->\r
+       <!-- <reference id="repositoryFactory" interface="javax.jcr.RepositoryFactory" -->\r
+       <!-- cardinality="0..1"> -->\r
+       <!-- <listener ref="jcrUserDetailsContextMapper" bind-method="register" -->\r
+       <!-- unbind-method="unregister" /> -->\r
+       <!-- </reference> -->\r
+       <reference id="nodeRepository" interface="javax.jcr.Repository"\r
                filter="(argeo.jcr.repository.alias=node)" />\r
        <reference id="systemExecutionService" interface="org.argeo.security.SystemExecutionService" />\r
 \r
@@ -22,7 +22,7 @@
        <service ref="ldapAuthenticationProvider"\r
                interface="org.springframework.security.providers.AuthenticationProvider"\r
                context-class-loader="service-provider" />\r
-               \r
+\r
        <service ref="securityDao" interface="org.argeo.security.CurrentUserDao"\r
                context-class-loader="service-provider" />\r
        <service ref="securityDao" interface="org.argeo.security.UserAdminDao"\r
index 72de115ca04cda85b2b41381d5277b807721748c..77a6ac7d5b48d9281420f38a67c5b9d8c562abd2 100644 (file)
                <property name="rolePrefix" value="${argeo.security.rolePrefix}" />
        </bean>
 
-       <bean id="userDetailsManager"
-               class="org.springframework.security.userdetails.ldap.LdapUserDetailsManager">
+       <bean id="userDetailsManager" class="org.argeo.security.ldap.ArgeoLdapUserDetailsManager">
                <constructor-arg ref="contextSource" />
                <property name="groupSearchBase" value="${argeo.ldap.groupBase}" />
                <property name="groupMemberAttributeName" value="${argeo.ldap.groupMemberAttribute}" />
                <property name="usernameMapper" ref="usernameMapper" />
                <property name="userDetailsMapper" ref="jcrUserDetailsContextMapper" />
+               <property name="passwordEncoder" ref="passwordEncoder" />
+               <property name="passwordAttributeName" value="${argeo.ldap.passwordAttribute}" />
        </bean>
-
-       <!-- <bean id="userDetailsService" -->
-       <!-- class="org.springframework.security.userdetails.ldap.LdapUserDetailsManager"> -->
-       <!-- <constructor-arg> -->
-       <!-- <bean -->
-       <!-- class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> -->
-       <!-- <constructor-arg value="${argeo.ldap.userBase}" /> -->
-       <!-- <constructor-arg value="(${argeo.ldap.usernameAttribute}={0})" /> -->
-       <!-- <constructor-arg ref="contextSource" /> -->
-       <!-- </bean> -->
-       <!-- </constructor-arg> -->
-       <!-- <constructor-arg ref="authoritiesPopulator" /> -->
-       <!-- <property name="userDetailsMapper" ref="jcrUserDetailsContextMapper" 
-               /> -->
-       <!-- </bean> -->
 </beans>
index 6c21dbd2601a5551e5138599283008ec04874805..91f2e84290a897a1a5e4879569970ed5b96e5ec8 100644 (file)
@@ -16,7 +16,7 @@
        <list id="authenticationProviders"\r
                interface="org.springframework.security.providers.AuthenticationProvider"\r
                cardinality="0..N">\r
-               <listener ref="authenticationManager" bind-method="register"\r
+               <listener ref="authenticationProvidersRegister" bind-method="register"\r
                        unbind-method="unregister" />\r
        </list>\r
 \r
index e04e2f197e6e192bb975abf4f6c4c98b1f5e4d54..f49de0914e5a51963f7f6732f1e307764f015690 100644 (file)
                <property name="systemAuthenticationKey" value="${argeo.security.systemKey}" />
        </bean>
 
-       <bean id="authenticationManager" class="org.argeo.security.core.ArgeoAuthenticationManager">
+       <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
                <property name="providers">
+                       <bean factory-bean="authenticationProvidersRegister"
+                               factory-method="getProviders" />
+               </property>
+       </bean>
+       <bean id="authenticationProvidersRegister" class="org.argeo.security.core.AuthenticationProvidersRegister">
+               <property name="defaultProviders">
                        <list>
                                <bean class="org.springframework.security.adapters.AuthByAdapterProvider">
                                        <property name="key" value="${argeo.security.systemKey}" />
index 90e8b3decedae4932ed7b6d0ba9d20d05c3c313c..c25be6afbdf46b845adc37c083065a3c2d095be5 100644 (file)
@@ -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<Authentication> 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 (file)
index 0000000..131ccda
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<beans xmlns="http://www.springframework.org/schema/beans"\r
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">\r
+\r
+       <bean id="nodeSession" class="org.argeo.security.jcr.SecureThreadBoundSession">\r
+               <property name="repository" ref="nodeRepository" />\r
+       </bean>\r
+</beans>
\ No newline at end of file
index 690dd16a971b2f467f5d9750945aadf296664da6..b0947860fe648896ccb1550dcdfef772a3902881 100644 (file)
@@ -8,7 +8,7 @@
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"\r
        osgi:default-timeout="30000">\r
 \r
-       <reference id="jcrSession" interface="javax.jcr.Session"\r
+       <reference id="nodeRepository" interface="javax.jcr.Repository"\r
                filter="(argeo.jcr.repository.alias=node)" />\r
        <reference id="userAdminService" interface="org.argeo.security.UserAdminService" />\r
        <reference id="currentUserService" interface="org.argeo.security.CurrentUserService" />\r
index cc5fbb57d061fad48219ada8db2930f5cd439447..49cf9d8a01b7b55b51c5ede4d83810574292a684 100644 (file)
@@ -7,7 +7,7 @@
        <bean id="adminUsersView" class="org.argeo.security.ui.admin.views.UsersView"
                scope="prototype">
 <!--           <property name="userAdminService" ref="userAdminService" /> -->
-               <property name="session" ref="jcrSession" />
+               <property name="session" ref="nodeSession" />
        </bean>
        <bean id="adminRolesView" class="org.argeo.security.ui.admin.views.RolesView"
                scope="prototype">
index 50f74e9c72fe6a06f7227f04b524577e14288b59..cfc1ca215d2818765fafa8bbd2164125844e9686 100644 (file)
@@ -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<Authentication> 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);
index c5fe0e337628132c9b0c84871fe2c714e1400063..1dc8d53cecea3c460d9fdc96a466c4ee69bb1cf7 100644 (file)
@@ -6,6 +6,6 @@
 
        <bean id="openChangePasswordDialog" class="org.argeo.security.ui.commands.OpenChangePasswordDialog"
                scope="prototype">
-               <property name="currentUserService" ref="currentUserService" />
+               <property name="userDetailsManager" ref="userDetailsManager" />
        </bean>
 </beans>
index 2477c93a8d990ee0a62e0c35632f861f87ce65c2..a0d30b50619342b8176a1a1f81c8d186627a9ef4 100644 (file)
@@ -8,5 +8,6 @@
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"\r
        osgi:default-timeout="30000">\r
 \r
-       <reference id="currentUserService" interface="org.argeo.security.CurrentUserService" />\r
+       <reference id="userDetailsManager"\r
+               interface="org.springframework.security.userdetails.UserDetailsManager" />\r
 </beans:beans>
\ No newline at end of file
index bd6da36570d0e4fa1177bcca64bc242bef353000..ab52b116c8bae9830af02768fff52654295fab13 100644 (file)
@@ -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;
        }
 
 }
index 21b37cb821fba4ae2a9f001e52a0a037d7f7bb43..90d7320b004f7b9e54902f3e496f434b783e3488 100644 (file)
@@ -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();
        }
index 8ffada70e5d6526c445692e785589c31c8382cb2..d9e943cc2aad73361daa1f850b55291f91a13785 100644 (file)
@@ -32,6 +32,8 @@
                                                <Export-Package>
                                                        org.argeo.security.*
                                                </Export-Package>
+                                               <!-- We need to exclude some packages which are added by BND but cause 
+                                                       use package conflict with some deployments -->
                                                <Import-Package>
                                                        org.springframework.context,
                                                        org.springframework.beans.factory,
index 736b0bafd7fad3df5cbc3c52859d272b515b574a..37b6d773570194a5a8bb3e526160bcb059b47823 100644 (file)
@@ -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);
 
index a82bc9bac0a3e712713bd428f1709dee684708f2..9ae88e37d9bcabca49bd561ec408999f070a71ba 100644 (file)
@@ -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 (file)
index de60bad..0000000
+++ /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<String, String> parameters) {
-               getProviders().add(authenticationProvider);
-               if (log.isDebugEnabled())
-                       log.debug("Registered authentication provider " + parameters);
-       }
-
-       public void unregister(AuthenticationProvider authenticationProvider,
-                       Map<String, String> 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 (file)
index 0000000..bd14659
--- /dev/null
@@ -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<Object> providers = new ArrayList<Object>();
+       private List<Object> defaultProviders = new ArrayList<Object>();
+
+       public void register(Object authenticationProvider,
+                       Map<String, String> parameters) {
+               providers.add(authenticationProvider);
+               if (log.isDebugEnabled())
+                       log.debug("Registered authentication provider " + parameters);
+       }
+
+       public void unregister(Object authenticationProvider,
+                       Map<String, String> parameters) {
+               providers.remove(authenticationProvider);
+               if (log.isDebugEnabled())
+                       log.debug("Unregistered authentication provider " + parameters);
+       }
+
+       public List<Object> getProviders() {
+               return providers;
+       }
+
+       public void setDefaultProviders(
+                       List<Object> defaultProviders) {
+               this.defaultProviders = defaultProviders;
+       }
+
+       public void afterPropertiesSet() throws Exception {
+               providers.addAll(defaultProviders);
+       }
+
+}
index 34e4375d8f643157cde4a35b256ddfbdf8972393..8e330cb11a1a0062da588fb03f1ab4d2a72e6534 100644 (file)
@@ -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 (file)
index 0000000..c83f3b5
--- /dev/null
@@ -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);
+       }
+
+}
index 1ab93edbb18f5678bfadd79f88c2b11ba8e7657c..73ec76a8f7c72b83c18a1de5b7390fa6a7b68bd9 100644 (file)
@@ -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<AdminPrincipal> adminPrincipals = subject
+                               .getPrincipals(AdminPrincipal.class);
+               Set<AnonymousPrincipal> anonymousPrincipals = subject
+                               .getPrincipals(AnonymousPrincipal.class);
+               Set<SimpleCredentials> 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 (file)
index 0000000..54ef836
--- /dev/null
@@ -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;
+       }
+
+}
index 0e8dbab32abff9c117bf466460c9f88126f7579d..bfbed855860f4f37f57d1444600a6f7c2a3559fa 100644 (file)
@@ -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);
index 755daaafb373e3b1563426abbdcb06ae21b47be0..c926720f45a37cb31cfd44b643acceb28de482f1 100644 (file)
                        </beans:entry>\r
                </service-properties>\r
        </service>\r
-\r
-       <service ref="nodeJcrSession" interface="javax.jcr.Session">\r
-               <service-properties>\r
-                       <beans:entry value="node">\r
-                               <beans:key>\r
-                                       <util:constant\r
-                                               static-field="org.argeo.jcr.ArgeoJcrConstants.JCR_REPOSITORY_ALIAS" />\r
-                               </beans:key>\r
-                       </beans:entry>\r
-               </service-properties>\r
-       </service>\r
 </beans:beans>
\ No newline at end of file
index 7f10c14c9af4f4ce58a8dbd2d2acbe85d38a5b2d..ec8bfca746b45a413f836073bd35ff4c03027656 100644 (file)
@@ -28,9 +28,4 @@
                </property>
                <property name="systemExecutor" ref="systemExecutionService" />
        </bean>
-
-       <bean id="nodeJcrSession" class="org.argeo.jcr.ThreadBoundJcrSessionFactory">
-               <property name="repository" ref="nodeJcrRepository" />
-               <property name="workspace" value="${argeo.node.repo.workspace}" />
-       </bean>
 </beans>
\ No newline at end of file
index 9428c69593a5237f4f6237979f69e9b8e650bd60..1a37e3e855f5edb0cc5fc43570ec7921d21fa584 100644 (file)
@@ -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<Session> activeSessions = Collections
-                       .synchronizedList(new ArrayList<Session>());
 
        private ThreadLocal<Session> session = new ThreadLocal<Session>();
-       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<Thread> threads = Collections
+                       .synchronizedList(new ArrayList<Thread>());
+       private final Map<Long, Session> activeSessions = Collections
+                       .synchronizedMap(new HashMap<Long, Session>());
+       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<? extends Session> 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<Thread> 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
+                                       }
+                               }
+                       }
                }
-               
+
        }
 }