Improve Security
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 25 Apr 2010 20:10:01 +0000 (20:10 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 25 Apr 2010 20:10:01 +0000 (20:10 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@3496 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

24 files changed:
security/modules/org.argeo.security.manager.ldap/META-INF/MANIFEST.MF
security/modules/org.argeo.security.manager.ldap/META-INF/spring/ldap-osgi.xml
security/modules/org.argeo.security.manager.ldap/META-INF/spring/ldap.xml
security/modules/org.argeo.security.manager.ldap/ldap.properties
security/modules/org.argeo.security.services/META-INF/MANIFEST.MF
security/modules/org.argeo.security.services/META-INF/spring/osgi.xml
security/modules/org.argeo.security.services/META-INF/spring/services.xml
security/modules/org.argeo.security.services/security.properties [new file with mode: 0644]
security/modules/org.argeo.security.webapp/META-INF/MANIFEST.MF
security/modules/org.argeo.security.webapp/WEB-INF/applicationContext.xml
security/modules/org.argeo.security.webapp/WEB-INF/osgi.xml
security/modules/org.argeo.security.webapp/WEB-INF/security.xml
security/modules/org.argeo.security.webapp/WEB-INF/web.xml
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/ArgeoSecurityService.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/DefaultSecurityService.java
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/SystemAuthenticatedTaskExecutor.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/ldap/ArgeoSecurityDaoLdap.java
security/runtime/org.argeo.security.mvc/build.properties [new file with mode: 0644]
security/runtime/org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/ArgeoRememberMeServices.java [new file with mode: 0644]
server/modules/org.argeo.server.catalina/META-INF/MANIFEST.MF
server/runtime/org.argeo.server.jackrabbit/pom.xml
server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java [new file with mode: 0644]
server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/server/jcr/mvc/OpenSessionInViewJcrInterceptor.java [new file with mode: 0644]

index 6c29e734783b37d0dd1dd1a2d1c9e22f215952bf..3671ef8c9b8e7f6518937f67273b5bdfd1e25871 100644 (file)
@@ -9,11 +9,13 @@ Import-Package: com.sun.jndi.ldap;resolution:=optional,
  org.springframework.beans.factory.config,
  org.springframework.ldap.core.support,
  org.springframework.security,
+ org.springframework.security.adapters;specification-version="2.0.4.A",
  org.springframework.security.ldap,
  org.springframework.security.ldap.populator,
  org.springframework.security.providers,
  org.springframework.security.providers.ldap,
  org.springframework.security.providers.ldap.authenticator,
+ org.springframework.security.providers.rememberme;specification-version="2.0.4.A",
  org.springframework.security.userdetails,
  org.springframework.security.userdetails.ldap
 Bundle-Name: Security Manager LDAP
index 8c4cfb43fe962352209be24dc5bfaaee3edae768..d3623c3c46b34bd0ef840aa5dead5fa863294ef6 100644 (file)
@@ -12,6 +12,9 @@
 \r
        <service ref="securityDao" interface="org.argeo.security.ArgeoSecurityDao"\r
                context-class-loader="service-provider" />\r
+       <service ref="userDetailsManager"\r
+               interface="org.springframework.security.userdetails.UserDetailsService"\r
+               context-class-loader="service-provider" />\r
 \r
        <list id="userNatureMappers" interface="org.argeo.security.ldap.UserNatureMapper"\r
                cardinality="0..N" />\r
index bd0c9969e7d11d914472f9bdfe297c4e2ca97fde..3f6f3db5881efbd66c6e79a759f598865acfafaa 100644 (file)
@@ -8,7 +8,6 @@
        <bean
                class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
                <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
-               <property name="ignoreUnresolvablePlaceholders" value="true" />
                <property name="locations">
                        <value>osgibundle:ldap.properties
                        </value>
        <bean id="_authenticationManager" class="org.springframework.security.providers.ProviderManager">
                <property name="providers">
                        <list>
+                               <bean class="org.springframework.security.adapters.AuthByAdapterProvider">
+                                       <property name="key" value="${argeo.security.systemKey}" />
+                               </bean>
+                               <bean
+                                       class="org.springframework.security.providers.rememberme.RememberMeAuthenticationProvider">
+                                       <property name="key" value="${argeo.security.systemKey}" />
+                               </bean>
                                <ref bean="authenticationProvider" />
                        </list>
                </property>
                <property name="userNatureMappers" ref="userNatureMappers" />
        </bean>
 
+       <bean id="userDetailsManager" factory-bean="securityDao"
+               factory-method="getUserDetailsManager">
+       </bean>
+
        <bean id="ldapAuthenticator"
                class="org.springframework.security.providers.ldap.authenticator.PasswordComparisonAuthenticator">
                <constructor-arg ref="contextSource" />
index 1b24ee3d16d6551659255c792b9dbb6eff23b4a3..bb429829226011e6a49aec96fd5a16e305c3fdcf 100644 (file)
@@ -4,3 +4,5 @@ argeo.ldap.host=localhost
 argeo.ldap.port=10389
 argeo.ldap.manager.userdn=uid=admin,ou=system
 argeo.ldap.manager.password=secret
+
+argeo.security.systemKey=argeo
index 4135ebdc1657f3c97e861bace45cac1e56b5a1b6..249147d4fbd336d3e5733f29d916e3e1b9e0d563 100644 (file)
@@ -1,5 +1,7 @@
 Bundle-SymbolicName: org.argeo.security.services
 Bundle-Version: 0.1.3.SNAPSHOT
 Import-Package: org.argeo.security,
- org.argeo.security.core
+ org.argeo.security.core,
+ org.springframework.beans.factory.config;specification-version="2.5.6.SEC01",
+ org.springframework.security;specification-version="2.0.4.A"
 Bundle-Name: Security Services
index e7e64a9fb5e536e4319875eb7db5d804e3b8b72f..8913486fc10129adb534c9b2480c83c22bd44bc6 100644 (file)
@@ -8,6 +8,6 @@
 \r
        <service ref="securityService" interface="org.argeo.security.ArgeoSecurityService" />\r
 \r
-       <reference id="securityDao" interface="org.argeo.security.ArgeoSecurityDao"\r
-                />\r
+       <reference id="securityDao" interface="org.argeo.security.ArgeoSecurityDao" />\r
+       <reference id="authenticationManager" interface="org.springframework.security.AuthenticationManager" />\r
 </beans:beans>
\ No newline at end of file
index dbf6489264c8e6002e1d896a904146528060d834..97fe92eda942ce6d0212323677f0bab8d20200eb 100644 (file)
@@ -4,7 +4,18 @@
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
 
+       <bean
+               class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+               <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
+               <property name="locations">
+                       <value>osgibundle:security.properties
+                       </value>
+               </property>
+       </bean>
+
        <bean id="securityService" class="org.argeo.security.core.DefaultSecurityService">
                <property name="securityDao" ref="securityDao" />
+               <property name="authenticationManager" ref="authenticationManager" />
+               <property name="systemAuthenticationKey" value="${argeo.security.systemKey}" />
        </bean>
 </beans>
\ No newline at end of file
diff --git a/security/modules/org.argeo.security.services/security.properties b/security/modules/org.argeo.security.services/security.properties
new file mode 100644 (file)
index 0000000..ae7aa87
--- /dev/null
@@ -0,0 +1 @@
+argeo.security.systemKey=argeo
index 52d2cd87391ee0ca340164d6d9df7b07165ba598..0b06dfbe372c4d4883fb50669a4dce1ca7421b12 100644 (file)
@@ -13,7 +13,9 @@ Import-Package: javax.servlet,
  org.springframework.security,
  org.springframework.security.config,
  org.springframework.security.ui,
+ org.springframework.security.ui.rememberme;specification-version="2.0.4.A",
  org.springframework.security.ui.webapp,
+ org.springframework.security.userdetails;specification-version="2.0.4.A",
  org.springframework.web.context,
  org.springframework.web.context.support,
  org.springframework.web.filter,
index ee1621d6128adf03dd9eae220f118ac60bbbc608..899afa3128f1cd43d1751959f8a851b7072e3faf 100644 (file)
@@ -15,6 +15,5 @@
                class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer"
                lazy-init="false">
                <property name="contextOverride" value="true" />
-               <property name="ignoreUnresolvablePlaceholders" value="true" />
        </bean>
 </beans>
\ No newline at end of file
index 197bd8fdc1fff90e6f2b78236d8b92f0205f03f9..39c74c8f0bb1ce78c1cddde4f22cbc099627c8bd 100644 (file)
@@ -8,13 +8,17 @@
 \r
        <reference id="_authenticationManager"\r
                interface="org.springframework.security.AuthenticationManager" />\r
+       <reference id="userDetailsService"\r
+               interface="org.springframework.security.userdetails.UserDetailsService" />\r
 \r
        <reference id="securityService" interface="org.argeo.security.ArgeoSecurityService" />\r
 \r
        <list id="objectFactories" interface="org.argeo.server.json.JsonObjectFactory"\r
                cardinality="0..N" />\r
 \r
-<!-- \r
-       <service ref="authenticationProcessingFilterEntryPoint"\r
-               interface="org.springframework.security.ui.AuthenticationEntryPoint" /> -->\r
+       <!--\r
+               <service ref="authenticationProcessingFilterEntryPoint"\r
+               interface="org.springframework.security.ui.AuthenticationEntryPoint"\r
+               />\r
+       -->\r
 </beans:beans>
\ No newline at end of file
index b423327ff500eb92e69301cc61efba8ea3718cad..66e62cfad3c27d801fe47d7e66a464556f768bb9 100644 (file)
                        logout-success-url="/getCredentials.ria" />
                <security:anonymous username="anonymous"
                        granted-authority="ROLE_ANONYMOUS" />
+               <security:remember-me key="argeo" services-ref="rememberMeServices" />
        </security:http>
 
+       <bean id="rememberMeServices" class="org.argeo.security.mvc.ArgeoRememberMeServices">
+               <property name="alwaysRemember" value="true" />
+               <property name="userDetailsService" ref="userDetailsService" />
+               <property name="key" value="${argeo.security.systemKey}" />
+       </bean>
+
+
        <bean id="authenticationProcessingFilter"
                class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
                <security:custom-filter position="AUTHENTICATION_PROCESSING_FILTER" />
index 668da78992ccc1cefd4684c0741d6d863a509a16..23312871858edbeff8a910ce1b0e578080fe0266 100644 (file)
@@ -50,4 +50,8 @@
                <url-pattern>/*</url-pattern>
        </filter-mapping>
 
+       <context-param>
+               <param-name>argeo.security.systemKey</param-name>
+               <param-value>argeo</param-value>
+       </context-param>
 </web-app>
index 73f2908bdc8d0b9cf9440a759f561988930518d3..71d6b2f4101880f09fc26c6bb8aee91366b55199 100644 (file)
@@ -2,7 +2,7 @@ package org.argeo.security;
 
 public interface ArgeoSecurityService {
        public void newUser(ArgeoUser argeoUser);
-       
+
        public void updateUser(ArgeoUser user);
 
        public void updateUserPassword(String username, String password);
@@ -12,4 +12,6 @@ public interface ArgeoSecurityService {
        public void newRole(String role);
 
        public ArgeoSecurityDao getSecurityDao();
+
+       public Runnable wrapWithSystemAuthentication(final Runnable runnable);
 }
index ef64337ebed1d99762cf642a0c496a8892447980..a4dd7a2029c6b0410dff75164186426b607c6a09 100644 (file)
@@ -6,10 +6,19 @@ import org.argeo.security.ArgeoSecurityDao;
 import org.argeo.security.ArgeoSecurityService;
 import org.argeo.security.ArgeoUser;
 import org.argeo.security.SimpleArgeoUser;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.security.Authentication;
+import org.springframework.security.AuthenticationManager;
+import org.springframework.security.context.SecurityContext;
+import org.springframework.security.context.SecurityContextHolder;
 
 public class DefaultSecurityService implements ArgeoSecurityService {
        private ArgeoSecurity argeoSecurity = new DefaultArgeoSecurity();
        private ArgeoSecurityDao securityDao;
+       private AuthenticationManager authenticationManager;
+
+       private String systemAuthenticationKey;
 
        public ArgeoSecurityDao getSecurityDao() {
                return securityDao;
@@ -48,6 +57,39 @@ public class DefaultSecurityService implements ArgeoSecurityService {
                securityDao.update(simpleArgeoUser);
        }
 
+       public TaskExecutor createSystemAuthenticatedTaskExecutor() {
+               return new SimpleAsyncTaskExecutor() {
+                       private static final long serialVersionUID = -8126773862193265020L;
+
+                       @Override
+                       public Thread createThread(Runnable runnable) {
+                               return super
+                                               .createThread(wrapWithSystemAuthentication(runnable));
+                       }
+
+               };
+       }
+
+       /**
+        * Wraps another runnable, adding security context <br/>
+        * TODO: secure the call to this method with Java Security
+        */
+       public Runnable wrapWithSystemAuthentication(final Runnable runnable) {
+               return new Runnable() {
+
+                       public void run() {
+                               SecurityContext securityContext = SecurityContextHolder
+                                               .getContext();
+                               Authentication auth = authenticationManager
+                                               .authenticate(new InternalAuthentication(
+                                                               systemAuthenticationKey));
+                               securityContext.setAuthentication(auth);
+
+                               runnable.run();
+                       }
+               };
+       }
+
        public void setArgeoSecurity(ArgeoSecurity argeoSecurity) {
                this.argeoSecurity = argeoSecurity;
        }
@@ -56,4 +98,13 @@ public class DefaultSecurityService implements ArgeoSecurityService {
                this.securityDao = dao;
        }
 
+       public void setAuthenticationManager(
+                       AuthenticationManager authenticationManager) {
+               this.authenticationManager = authenticationManager;
+       }
+
+       public void setSystemAuthenticationKey(String systemAuthenticationKey) {
+               this.systemAuthenticationKey = systemAuthenticationKey;
+       }
+
 }
diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java
new file mode 100644 (file)
index 0000000..99ac3ad
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.security.core;
+
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.GrantedAuthorityImpl;
+import org.springframework.security.adapters.PrincipalSpringSecurityUserToken;
+
+public class InternalAuthentication extends PrincipalSpringSecurityUserToken {
+       private static final long serialVersionUID = -6783376375615949315L;
+       private final static String SYSTEM_USERNAME = "system";
+       private final static String SYSTEM_ROLE = "ROLE_SYSTEM";
+
+       public InternalAuthentication(String key) {
+               super(
+                               key,
+                               SYSTEM_USERNAME,
+                               key,
+                               new GrantedAuthority[] { new GrantedAuthorityImpl(SYSTEM_ROLE) },
+                               SYSTEM_USERNAME);
+       }
+
+}
diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/SystemAuthenticatedTaskExecutor.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/SystemAuthenticatedTaskExecutor.java
new file mode 100644 (file)
index 0000000..421a7dc
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.security.core;
+
+import org.argeo.security.ArgeoSecurityService;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+
+public class SystemAuthenticatedTaskExecutor extends SimpleAsyncTaskExecutor {
+       private static final long serialVersionUID = 453384889461147359L;
+
+       private ArgeoSecurityService securityService;
+
+       @Override
+       public Thread createThread(Runnable runnable) {
+               return super.createThread(securityService
+                               .wrapWithSystemAuthentication(runnable));
+       }
+
+       public void setSecurityService(ArgeoSecurityService securityService) {
+               this.securityService = securityService;
+       }
+
+}
index c9ba367c6ec58d450d5328bf719d81bf20cc9b88..15cd1360b915b1d80c54a7b70d25e28c2e73f75d 100644 (file)
@@ -272,4 +272,9 @@ public class ArgeoSecurityDaoLdap implements ArgeoSecurityDao, InitializingBean
        public void setGroupClasses(String[] groupClasses) {
                this.groupClasses = groupClasses;
        }
+
+       public UserDetailsManager getUserDetailsManager() {
+               return userDetailsManager;
+       }
+
 }
diff --git a/security/runtime/org.argeo.security.mvc/build.properties b/security/runtime/org.argeo.security.mvc/build.properties
new file mode 100644 (file)
index 0000000..a740a34
--- /dev/null
@@ -0,0 +1,2 @@
+additional.bundles = org.springframework.beans
+source.. = src/main/java/
diff --git a/security/runtime/org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/ArgeoRememberMeServices.java b/security/runtime/org.argeo.security.mvc/src/main/java/org/argeo/security/mvc/ArgeoRememberMeServices.java
new file mode 100644 (file)
index 0000000..e71b86e
--- /dev/null
@@ -0,0 +1,53 @@
+package org.argeo.security.mvc;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices;
+
+public class ArgeoRememberMeServices extends TokenBasedRememberMeServices {
+       public final static String DEFAULT_COOKIE_NAME = "ARGEO_SECURITY";
+
+       public ArgeoRememberMeServices() {
+               setCookieName(DEFAULT_COOKIE_NAME);
+       }
+
+       /**
+        * Sets a "cancel cookie" (with maxAge = 0) on the response to disable
+        * persistent logins.
+        * 
+        * @param request
+        * @param response
+        */
+       protected void cancelCookie(HttpServletRequest request,
+                       HttpServletResponse response) {
+               Cookie cookie = new Cookie(getCookieName(), null);
+               cookie.setMaxAge(0);
+               cookie.setPath("/");
+
+               response.addCookie(cookie);
+       }
+
+       /**
+        * Sets the cookie on the response
+        * 
+        * @param tokens
+        *            the tokens which will be encoded to make the cookie value.
+        * @param maxAge
+        *            the value passed to {@link Cookie#setMaxAge(int)}
+        * @param request
+        *            the request
+        * @param response
+        *            the response to add the cookie to.
+        */
+       protected void setCookie(String[] tokens, int maxAge,
+                       HttpServletRequest request, HttpServletResponse response) {
+               String cookieValue = encodeCookie(tokens);
+               Cookie cookie = new Cookie(getCookieName(), cookieValue);
+               cookie.setMaxAge(maxAge);
+               cookie.setPath("/");
+               response.addCookie(cookie);
+       }
+
+}
index 8e17b15d5df1639a5ca0186fddcefaa3e0f10a30..6885d9f540631136d173d0f5a0548c6e41f5d9b5 100644 (file)
@@ -12,6 +12,7 @@ Import-Package: org.apache.commons.logging.impl;resolution:=optional,
  org.springframework.security;resolution:=optional,
  org.springframework.security.context;resolution:=optional,
  org.springframework.security.providers;resolution:=optional,
+ org.springframework.security.providers.rememberme;resolution:=optional,
  org.springframework.security.ui;resolution:=optional,
  org.springframework.security.ui.savedrequest;resolution:=optional,
  org.springframework.security.userdetails;resolution:=optional,
index 4b3c3254591a40b3dd9468386946a9a383d0e84a..58ff3063e56abd3045469bd888c11bee29cea607 100644 (file)
@@ -29,6 +29,7 @@
                                <version>${version.maven-bundle-plugin}</version>
                                <configuration>
                                        <instructions>
+                                               <Fragment-Host>org.argeo.dep.osgi.jackrabbit</Fragment-Host>
                                                <Export-Package>
                                                        org.argeo.jcr.*,
                                                        org.argeo.server.jackrabbit.*,
@@ -36,6 +37,7 @@
                                                </Export-Package>
                                                <Import-Package>
                                                        *,
+                                                       org.springframework.security.providers.jaas;resolution:="optional",
                                                        junit.framework;resolution:="optional"
                                                </Import-Package>
                                        </instructions>
                        <artifactId>org.springframework.web.servlet</artifactId>
                </dependency>
 
+               <dependency>
+                       <groupId>org.springframework.security</groupId>
+                       <artifactId>org.springframework.security</artifactId>
+               </dependency>
+
                <!-- Logging -->
                <dependency>
                        <groupId>org.slf4j</groupId>
diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java
new file mode 100644 (file)
index 0000000..9996b7b
--- /dev/null
@@ -0,0 +1,105 @@
+package org.argeo.jcr;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+
+public class ThreadBoundJcrSessionFactory implements FactoryBean,
+               DisposableBean {
+       private final static Log log = LogFactory
+                       .getLog(ThreadBoundJcrSessionFactory.class);
+
+       private Repository repository;
+       private List<Session> activeSessions = Collections
+                       .synchronizedList(new ArrayList<Session>());
+
+       private ThreadLocal<Session> session = new ThreadLocal<Session>();
+       private boolean destroying = false;
+       private final Session proxiedSession;
+
+       public ThreadBoundJcrSessionFactory() {
+               Class<?>[] interfaces = { Session.class };
+               proxiedSession = (Session) Proxy.newProxyInstance(getClass()
+                               .getClassLoader(), interfaces, new InvocationHandler() {
+
+                       public Object invoke(Object proxy, Method method, Object[] args)
+                                       throws Throwable {
+                               Session threadSession = session.get();
+                               if (threadSession == null) {
+                                       if ("logout".equals(method.getName()))// no need to login
+                                               return Void.TYPE;
+                                       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());
+                               }
+                               return ret;
+                       }
+               });
+       }
+
+       protected Session login() {
+               try {
+                       Session sess = repository.login();
+                       if (log.isTraceEnabled())
+                               log.trace("Log in to JCR session " + sess + "; userId="
+                                               + sess.getUserID());
+                       // Thread.dumpStack();
+                       activeSessions.add(sess);
+                       return sess;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot log in to repository", e);
+               }
+       }
+
+       public Object getObject() {
+               return proxiedSession;
+       }
+
+       public void destroy() throws Exception {
+               if (log.isDebugEnabled())
+                       log.debug("Cleaning up " + activeSessions.size()
+                                       + " active JCR sessions...");
+
+               destroying = true;
+               for (Session sess : activeSessions) {
+                       sess.logout();
+               }
+               activeSessions.clear();
+       }
+
+       public Class<? extends Session> getObjectType() {
+               return Session.class;
+       }
+
+       public boolean isSingleton() {
+               return true;
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+}
diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/server/jcr/mvc/OpenSessionInViewJcrInterceptor.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/server/jcr/mvc/OpenSessionInViewJcrInterceptor.java
new file mode 100644 (file)
index 0000000..ea02ae3
--- /dev/null
@@ -0,0 +1,50 @@
+package org.argeo.server.jcr.mvc;
+
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.context.request.WebRequestInterceptor;
+
+public class OpenSessionInViewJcrInterceptor implements WebRequestInterceptor {
+       private final static Log log = LogFactory
+                       .getLog(OpenSessionInViewJcrInterceptor.class);
+
+       private Session session;
+
+       public void preHandle(WebRequest request) throws Exception {
+               if (log.isTraceEnabled())
+                       log.trace("preHandle: " + request);
+               // Authentication auth = SecurityContextHolder.getContext()
+               // .getAuthentication();
+               // if (auth != null)
+               // log.debug("auth=" + auth + ", authenticated="
+               // + auth.isAuthenticated() + ", name=" + auth.getName());
+               // else
+               // log.debug("No auth");
+
+               // FIXME: find a safer way to initialize
+               // FIXME: not really needed to initialize here
+               //session.getRepository();
+       }
+
+       public void postHandle(WebRequest request, ModelMap model) throws Exception {
+               // if (log.isDebugEnabled())
+               // log.debug("postHandle: " + request);
+       }
+
+       public void afterCompletion(WebRequest request, Exception ex)
+                       throws Exception {
+               if (log.isTraceEnabled())
+                       log.trace("afterCompletion: " + request);
+               // FIXME: only close session that were open
+               session.logout();
+       }
+
+       public void setSession(Session session) {
+               this.session = session;
+       }
+
+}