Remoting working
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 26 Jan 2015 21:26:25 +0000 (21:26 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 26 Jan 2015 21:26:25 +0000 (21:26 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@7707 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

14 files changed:
dep/org.argeo.security.dep.node/pom.xml
org.argeo.cms/src/org/argeo/cms/internal/kernel/HttpFilter.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttpFilter.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/webdav-config.xml [new file with mode: 0644]
org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/ScopedSessionProvider.java [new file with mode: 0644]
org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/servlet/OpenInViewSessionProvider.java [new file with mode: 0644]
org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/servlet/RemotingServlet.java [new file with mode: 0644]
org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/servlet/WebdavServlet.java [new file with mode: 0644]
org.argeo.server.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java [new file with mode: 0644]
pom.xml

index 27efb4fa1f72ae746fa060aa0a763ed8a9ae0ab1..acef7d17c9b6ccc98b7b545994f6ff4d5b7921d4 100644 (file)
                </dependency>
 
                <!-- Default JCR repositories configurations -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.node.repo.jackrabbit</artifactId>
-                       <version>2.1.13-SNAPSHOT</version>
-               </dependency>
+               <!-- <dependency> -->
+               <!-- <groupId>org.argeo.commons</groupId> -->
+               <!-- <artifactId>org.argeo.node.repo.jackrabbit</artifactId> -->
+               <!-- <version>2.1.13-SNAPSHOT</version> -->
+               <!-- </dependency> -->
 
                <!-- OSGi Boot (and Equinox) -->
                <dependency>
                </dependency>
 
                <!-- Security (Jackrabbit) -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.security.dao.jackrabbit</artifactId>
-                       <version>2.1.13-SNAPSHOT</version>
-               </dependency>
+               <!-- <dependency> -->
+               <!-- <groupId>org.argeo.commons</groupId> -->
+               <!-- <artifactId>org.argeo.security.dao.jackrabbit</artifactId> -->
+               <!-- <version>2.1.13-SNAPSHOT</version> -->
+               <!-- </dependency> -->
        </dependencies>
        <profiles>
                <profile>
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/HttpFilter.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/HttpFilter.java
new file mode 100644 (file)
index 0000000..7c2151e
--- /dev/null
@@ -0,0 +1,38 @@
+package org.argeo.cms.internal.kernel;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/** Abstract base class for http filters. */
+abstract class HttpFilter implements Filter {
+       protected abstract void doFilter(HttpSession httpSession,
+                       HttpServletRequest request, HttpServletResponse response,
+                       FilterChain filterChain) throws IOException, ServletException;
+
+       @Override
+       public void doFilter(ServletRequest servletRequest,
+                       ServletResponse servletResponse, FilterChain filterChain)
+                       throws IOException, ServletException {
+               HttpServletRequest request = (HttpServletRequest) servletRequest;
+               doFilter(request.getSession(), request,
+                               (HttpServletResponse) servletResponse, filterChain);
+       }
+
+       @Override
+       public void destroy() {
+       }
+
+       @Override
+       public void init(FilterConfig arg0) throws ServletException {
+       }
+
+}
index c813a9a37b207ee88cd6db20485419743ad147c2..c38a4b29a73744eb4d553941bd646a0285066828 100644 (file)
@@ -27,9 +27,9 @@ final class Kernel {
        private final BundleContext bundleContext;
 
        private JackrabbitNode node;
-       private OsgiJackrabbitRepositoryFactory repositoryFactory;
+       private RepositoryFactory repositoryFactory;
        private NodeSecurity nodeSecurity;
-       private NodeHttpFilter httpFilter;
+       private NodeHttp nodeHttp;
 
        Kernel(BundleContext bundleContext) {
                this.bundleContext = bundleContext;
@@ -45,14 +45,14 @@ final class Kernel {
                        node = new JackrabbitNode(bundleContext);
                        repositoryFactory = new OsgiJackrabbitRepositoryFactory();
                        nodeSecurity = new NodeSecurity(bundleContext, node);
-                       httpFilter = new NodeHttpFilter(bundleContext, nodeSecurity);
+                       nodeHttp = new NodeHttp(bundleContext, node, nodeSecurity);
 
                        // Publish services to OSGi register
                        nodeSecurity.publish();
                        node.publish();
                        bundleContext.registerService(RepositoryFactory.class,
                                        repositoryFactory, null);
-                       httpFilter.publish();
+                       nodeHttp.publish();
                } catch (Exception e) {
                        log.error("Cannot initialize Argeo CMS", e);
                        throw new ArgeoException("Cannot initialize", e);
@@ -67,7 +67,7 @@ final class Kernel {
        void destroy() {
                long begin = System.currentTimeMillis();
 
-               httpFilter = null;
+               nodeHttp = null;
                nodeSecurity.destroy();
                node.destroy();
 
index d36f7a51dd7a22ed3306a274bed16b24f2416a84..4d53917b48d26f257e42234769d153e9f849129f 100644 (file)
@@ -12,7 +12,22 @@ interface KernelConstants {
 
        final static String[] DEFAULT_CNDS = { "/org/argeo/jcr/argeo.cnd",
                        "/org/argeo/cms/cms.cnd" };
-       
+
        // Security
        final static String DEFAULT_SECURITY_KEY = "argeo";
+       final static String ANONYMOUS_USER = "anonymous";
+       final static String ADMIN_USER = "root";
+
+       // Roles
+       final static String ROLE_USER = "ROLE_USER";
+       final static String ROLE_ADMIN = "ROLE_ADMIN";
+       final static String ROLE_ANONYMOUS = "ROLE_ANONYMOUS";
+
+       // DAV
+       final static String WEBDAV_CONFIG = "/org/argeo/cms/internal/kernel/webdav-config.xml";
+       final static String PATH_WEBDAV_PUBLIC = "/data/public";
+       final static String PATH_WEBDAV_PRIVATE = "/data/files";
+       final static String PATH_REMOTING_PUBLIC = "/data/pub";
+       final static String PATH_REMOTING_PRIVATE = "/data/jcr";
+       final static String PATH_WORKBENCH_PUBLIC = "/ui/public";
 }
index c3297cba87dd5d814ad4385624bf4a065e5aa73d..ba2a352a74dd334f14bd55edc1b22bb0fcfbc95b 100644 (file)
@@ -2,14 +2,27 @@ package org.argeo.cms.internal.kernel;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Dictionary;
+import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Properties;
 
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.logging.Log;
 import org.argeo.cms.CmsException;
 import org.osgi.framework.BundleContext;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
 
-class KernelUtils {
+class KernelUtils implements KernelConstants {
        final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
 
        static Dictionary<String, ?> asDictionary(Properties props) {
@@ -20,8 +33,7 @@ class KernelUtils {
                return hashtable;
        }
 
-       static Dictionary<String, ?> asDictionary(ClassLoader cl,
-                       String resource) {
+       static Dictionary<String, ?> asDictionary(ClassLoader cl, String resource) {
                Properties props = new Properties();
                try {
                        props.load(cl.getResourceAsStream(resource));
@@ -37,6 +49,36 @@ class KernelUtils {
                                .substring("file:".length())).getAbsoluteFile();
        }
 
+       // Security
+       static void anonymousLogin(AuthenticationManager authenticationManager) {
+               try {
+                       List<SimpleGrantedAuthority> anonAuthorities = Collections
+                                       .singletonList(new SimpleGrantedAuthority(ROLE_ANONYMOUS));
+                       UserDetails anonUser = new User(ANONYMOUS_USER, "", true, true,
+                                       true, true, anonAuthorities);
+                       AnonymousAuthenticationToken anonToken = new AnonymousAuthenticationToken(
+                                       DEFAULT_SECURITY_KEY, anonUser, anonAuthorities);
+                       Authentication authentication = authenticationManager
+                                       .authenticate(anonToken);
+                       SecurityContextHolder.getContext()
+                                       .setAuthentication(authentication);
+               } catch (Exception e) {
+                       throw new CmsException("Cannot authenticate", e);
+               }
+       }
+
+       // HTTP
+       static void logRequestHeaders(Log log, HttpServletRequest request) {
+               if (!log.isDebugEnabled())
+                       return;
+               for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames
+                               .hasMoreElements();) {
+                       String headerName = headerNames.nextElement();
+                       Object headerValue = request.getHeader(headerName);
+                       log.debug(headerName + ": " + headerValue);
+               }
+       }
+
        private KernelUtils() {
 
        }
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java
new file mode 100644 (file)
index 0000000..c0383f5
--- /dev/null
@@ -0,0 +1,289 @@
+package org.argeo.cms.internal.kernel;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import javax.servlet.FilterChain;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.internal.kernel.NodeHttp.AnonymousFilter;
+import org.argeo.cms.internal.kernel.NodeHttp.DavFilter;
+import org.argeo.jackrabbit.servlet.OpenInViewSessionProvider;
+import org.argeo.jackrabbit.servlet.RemotingServlet;
+import org.argeo.jackrabbit.servlet.WebdavServlet;
+import org.argeo.jcr.ArgeoJcrConstants;
+import org.eclipse.equinox.http.servlet.ExtendedHttpService;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.http.NamespaceException;
+import org.osgi.util.tracker.ServiceTracker;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/**
+ * Intercepts and enriches http access, mainly focusing on security and
+ * transactionality.
+ */
+class NodeHttp implements KernelConstants, ArgeoJcrConstants {
+       private final static Log log = LogFactory.getLog(NodeHttp.class);
+
+       private final static String ATTR_AUTH = "auth";
+       private final static String HEADER_AUTHORIZATION = "Authorization";
+       private final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+       static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
+
+       private final AuthenticationManager authenticationManager;
+       private final BundleContext bundleContext;
+       private ExtendedHttpService httpService;
+
+       // FIXME Make it more unique
+       private String httpAuthRealm = "Argeo";
+
+       // Filters
+       private final RootFilter rootFilter;
+
+       // remoting
+       private OpenInViewSessionProvider sessionProvider;
+       private WebdavServlet publicWebdavServlet;
+       private WebdavServlet privateWebdavServlet;
+       private RemotingServlet publicRemotingServlet;
+       private RemotingServlet privateRemotingServlet;
+
+       NodeHttp(BundleContext bundleContext, JackrabbitNode node,
+                       NodeSecurity authenticationManager) {
+               this.bundleContext = bundleContext;
+               this.authenticationManager = authenticationManager;
+
+               // Equinox dependency
+               ServiceTracker<ExtendedHttpService, ExtendedHttpService> st = new ServiceTracker<ExtendedHttpService, ExtendedHttpService>(
+                               bundleContext, ExtendedHttpService.class, null);
+               st.open();
+               try {
+                       httpService = st.waitForService(1000);
+               } catch (InterruptedException e) {
+                       httpService = null;
+               }
+
+               if (httpService == null)
+                       throw new CmsException("Could not find "
+                                       + ExtendedHttpService.class + " service.");
+
+               // Filters
+               rootFilter = new RootFilter();
+
+               // DAV
+               sessionProvider = new OpenInViewSessionProvider();
+               publicWebdavServlet = new WebdavServlet(node, sessionProvider);
+               privateWebdavServlet = new WebdavServlet(node, sessionProvider);
+               publicRemotingServlet = new RemotingServlet(node, sessionProvider);
+               privateRemotingServlet = new RemotingServlet(node, sessionProvider);
+       }
+
+       void publish() {
+               try {
+                       registerWebdavServlet(PATH_WEBDAV_PUBLIC, ALIAS_NODE, true,
+                                       publicWebdavServlet);
+                       registerWebdavServlet(PATH_WEBDAV_PRIVATE, ALIAS_NODE, false,
+                                       privateWebdavServlet);
+                       registerRemotingServlet(PATH_REMOTING_PUBLIC, ALIAS_NODE, true,
+                                       publicRemotingServlet);
+                       registerRemotingServlet(PATH_REMOTING_PRIVATE, ALIAS_NODE, false,
+                                       privateRemotingServlet);
+
+                       // httpService.registerFilter("/", rootFilter, null, null);
+               } catch (Exception e) {
+                       throw new CmsException("Cannot publish HTTP services to OSGi", e);
+               }
+       }
+
+       private void registerWebdavServlet(String pathPrefix, String alias,
+                       Boolean anonymous, WebdavServlet webdavServlet)
+                       throws NamespaceException, ServletException {
+               String path = pathPrefix + "/" + alias;
+               Properties initParameters = new Properties();
+               initParameters.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG,
+                               KernelConstants.WEBDAV_CONFIG);
+               initParameters.setProperty(
+                               WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
+               httpService.registerFilter(path, anonymous ? new AnonymousFilter()
+                               : new DavFilter(), null, null);
+               // Cast to servlet because of a weird behaviour in Eclipse
+               httpService.registerServlet(path, (Servlet) webdavServlet,
+                               initParameters, null);
+       }
+
+       private void registerRemotingServlet(String pathPrefix, String alias,
+                       Boolean anonymous, RemotingServlet remotingServlet)
+                       throws NamespaceException, ServletException {
+               String path = pathPrefix + "/" + alias;
+               Properties initParameters = new Properties();
+               initParameters.setProperty(
+                               RemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
+
+               // Looks like a bug in Jackrabbit remoting init
+               initParameters.setProperty(RemotingServlet.INIT_PARAM_HOME,
+                               KernelUtils.getOsgiInstanceDir(bundleContext)
+                                               + "/tmp/jackrabbit");
+               initParameters.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY,
+                               "remoting");
+               // Cast to servlet because of a weird behaviour in Eclipse
+               httpService.registerFilter(path, anonymous ? new AnonymousFilter()
+                               : new DavFilter(), null, null);
+               httpService.registerServlet(path, (Servlet) remotingServlet,
+                               initParameters, null);
+       }
+
+       private Boolean isSessionAuthenticated(HttpSession httpSession) {
+               SecurityContext contextFromSession = (SecurityContext) httpSession
+                               .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
+               return contextFromSession != null;
+       }
+
+       private void requestBasicAuth(HttpSession httpSession,
+                       HttpServletResponse response) {
+               response.setStatus(401);
+               response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\""
+                               + httpAuthRealm + "\"");
+               httpSession.setAttribute(ATTR_AUTH, Boolean.TRUE);
+       }
+
+       private UsernamePasswordAuthenticationToken basicAuth(String authHeader) {
+               if (authHeader != null) {
+                       StringTokenizer st = new StringTokenizer(authHeader);
+                       if (st.hasMoreTokens()) {
+                               String basic = st.nextToken();
+                               if (basic.equalsIgnoreCase("Basic")) {
+                                       try {
+                                               String credentials = new String(Base64.decodeBase64(st
+                                                               .nextToken()), "UTF-8");
+                                               log.debug("Credentials: " + credentials);
+                                               int p = credentials.indexOf(":");
+                                               if (p != -1) {
+                                                       String login = credentials.substring(0, p).trim();
+                                                       String password = credentials.substring(p + 1)
+                                                                       .trim();
+
+                                                       return new UsernamePasswordAuthenticationToken(
+                                                                       login, password);
+                                               } else {
+                                                       throw new CmsException(
+                                                                       "Invalid authentication token");
+                                               }
+                                       } catch (Exception e) {
+                                               throw new CmsException(
+                                                               "Couldn't retrieve authentication", e);
+                                       }
+                               }
+                       }
+               }
+               throw new CmsException("Couldn't retrieve authentication");
+       }
+
+       /** Intercepts all requests. Authenticates. */
+       class RootFilter extends HttpFilter {
+
+               @Override
+               public void doFilter(HttpSession httpSession,
+                               HttpServletRequest request, HttpServletResponse response,
+                               FilterChain filterChain) throws IOException, ServletException {
+
+                       // Authenticate from session
+                       if (isSessionAuthenticated(httpSession)) {
+                               filterChain.doFilter(request, response);
+                               return;
+                       }
+
+                       // TODO Kerberos
+
+                       // TODO Certificate
+
+                       // Process basic auth
+                       String basicAuth = request.getHeader(HEADER_AUTHORIZATION);
+                       if (basicAuth != null) {
+                               UsernamePasswordAuthenticationToken token = basicAuth(basicAuth);
+                               Authentication auth = authenticationManager.authenticate(token);
+                               SecurityContextHolder.getContext().setAuthentication(auth);
+                               httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
+                                               SecurityContextHolder.getContext());
+                               httpSession.setAttribute(ATTR_AUTH, Boolean.FALSE);
+                               filterChain.doFilter(request, response);
+                               return;
+                       }
+
+                       Boolean doBasicAuth = true;
+                       if (doBasicAuth) {
+                               requestBasicAuth(httpSession, response);
+                               // skip filter chain
+                               return;
+                       }
+
+                       // TODO Login page
+
+                       // Anonymous
+                       KernelUtils.anonymousLogin(authenticationManager);
+                       filterChain.doFilter(request, response);
+               }
+       }
+
+       /** Intercepts all requests. Authenticates. */
+       class AnonymousFilter extends HttpFilter {
+               @Override
+               public void doFilter(HttpSession httpSession,
+                               HttpServletRequest request, HttpServletResponse response,
+                               FilterChain filterChain) throws IOException, ServletException {
+
+                       // Authenticate from session
+                       if (isSessionAuthenticated(httpSession)) {
+                               filterChain.doFilter(request, response);
+                               return;
+                       }
+
+                       KernelUtils.anonymousLogin(authenticationManager);
+                       filterChain.doFilter(request, response);
+               }
+       }
+
+       /** Intercepts all requests. Authenticates. */
+       class DavFilter extends HttpFilter {
+
+               @Override
+               public void doFilter(HttpSession httpSession,
+                               HttpServletRequest request, HttpServletResponse response,
+                               FilterChain filterChain) throws IOException, ServletException {
+
+                       // Authenticate from session
+                       if (isSessionAuthenticated(httpSession)) {
+                               filterChain.doFilter(request, response);
+                               return;
+                       }
+
+                       // Process basic auth
+                       String basicAuth = request.getHeader(HEADER_AUTHORIZATION);
+                       if (basicAuth != null) {
+                               UsernamePasswordAuthenticationToken token = basicAuth(basicAuth);
+                               Authentication auth = authenticationManager.authenticate(token);
+                               SecurityContextHolder.getContext().setAuthentication(auth);
+                               httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
+                                               SecurityContextHolder.getContext());
+                               httpSession.setAttribute(ATTR_AUTH, Boolean.FALSE);
+                               filterChain.doFilter(request, response);
+                               return;
+                       }
+
+                       requestBasicAuth(httpSession, response);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttpFilter.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttpFilter.java
deleted file mode 100644 (file)
index a32228a..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.util.StringTokenizer;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsException;
-import org.eclipse.equinox.http.servlet.ExtendedHttpService;
-import org.osgi.framework.BundleContext;
-import org.osgi.service.http.HttpContext;
-import org.osgi.util.tracker.ServiceTracker;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContext;
-import org.springframework.security.core.context.SecurityContextHolder;
-
-class NodeHttpFilter implements Filter {
-       private final static Log log = LogFactory.getLog(NodeHttpFilter.class);
-
-       private final static String ATTR_AUTH = "auth";
-       private final static String HEADER_AUTHORIZATION = "Authorization";
-
-       static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
-
-       private ExtendedHttpService httpService;
-       private final AuthenticationManager authenticationManager;
-
-       private Boolean basicAuthEnabled = false;
-
-       NodeHttpFilter(BundleContext bundleContext,
-                       AuthenticationManager authenticationManager) {
-               this.authenticationManager = authenticationManager;
-
-               // Equinox dependency
-               ServiceTracker<ExtendedHttpService, ExtendedHttpService> st = new ServiceTracker<ExtendedHttpService, ExtendedHttpService>(
-                               bundleContext, ExtendedHttpService.class, null);
-               st.open();
-               try {
-                       httpService = st.waitForService(1000);
-               } catch (InterruptedException e) {
-                       httpService = null;
-               }
-
-               if (httpService == null)
-                       throw new CmsException("Could not find "
-                                       + ExtendedHttpService.class + " service.");
-       }
-
-       void publish() {
-               try {
-                       HttpContext httpContext = httpService.createDefaultHttpContext();
-                       httpService.registerFilter("/", this, null, httpContext);
-               } catch (Exception e) {
-                       throw new CmsException("Cannot register HTTP filter", e);
-               }
-       }
-
-       @Override
-       public void doFilter(ServletRequest servletRequest,
-                       ServletResponse servletResponse, FilterChain filterChain)
-                       throws IOException, ServletException {
-               HttpServletRequest request = (HttpServletRequest) servletRequest;
-               HttpSession httpSession = request.getSession();
-
-               // Authenticate from session
-               SecurityContext contextFromSession = (SecurityContext) httpSession
-                               .getAttribute(SPRING_SECURITY_CONTEXT_KEY);
-               if (contextFromSession != null) {
-                       filterChain.doFilter(servletRequest, servletResponse);
-                       return;
-               }
-
-               if (basicAuthEnabled) {
-                       // Basic auth
-                       String basicAuth = request.getHeader(HEADER_AUTHORIZATION);
-
-                       // for (Enumeration<String> headerNames = request.getHeaderNames();
-                       // headerNames
-                       // .hasMoreElements();) {
-                       // String headerName = headerNames.nextElement();
-                       // Object headerValue = request.getHeader(headerName);
-                       // log.debug(headerName + ": " + headerValue);
-                       // }
-
-                       if (basicAuth == null) {
-                               HttpServletResponse response = (HttpServletResponse) servletResponse;
-                               response.setStatus(401);
-                               response.setHeader("WWW-Authenticate", "basic realm=\"Auth ("
-                                               + httpSession.getCreationTime() + ")\"");
-                               httpSession.setAttribute(ATTR_AUTH, Boolean.TRUE);
-                               return;
-                       } else {
-                               UsernamePasswordAuthenticationToken token = basicAuth(basicAuth);
-                               Authentication auth = authenticationManager.authenticate(token);
-                               SecurityContextHolder.getContext().setAuthentication(auth);
-                               httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
-                                               SecurityContextHolder.getContext());
-                               httpSession.setAttribute(ATTR_AUTH, Boolean.FALSE);
-                       }
-               }
-               // Assume authentication has been done and continue
-               filterChain.doFilter(servletRequest, servletResponse);
-       }
-
-       @Override
-       public void init(FilterConfig filterConfig) throws ServletException {
-       }
-
-       @Override
-       public void destroy() {
-       }
-
-       private UsernamePasswordAuthenticationToken basicAuth(String authHeader) {
-               if (authHeader != null) {
-                       StringTokenizer st = new StringTokenizer(authHeader);
-                       if (st.hasMoreTokens()) {
-                               String basic = st.nextToken();
-                               if (basic.equalsIgnoreCase("Basic")) {
-                                       try {
-                                               String credentials = new String(Base64.decodeBase64(st
-                                                               .nextToken()), "UTF-8");
-                                               log.debug("Credentials: " + credentials);
-                                               int p = credentials.indexOf(":");
-                                               if (p != -1) {
-                                                       String login = credentials.substring(0, p).trim();
-                                                       String password = credentials.substring(p + 1)
-                                                                       .trim();
-
-                                                       return new UsernamePasswordAuthenticationToken(
-                                                                       login, password);
-                                               } else {
-                                                       throw new CmsException(
-                                                                       "Invalid authentication token");
-                                               }
-                                       } catch (Exception e) {
-                                               throw new CmsException(
-                                                               "Couldn't retrieve authentication", e);
-                                       }
-                               }
-                       }
-               }
-               throw new CmsException("Couldn't retrieve authentication");
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/webdav-config.xml b/org.argeo.cms/src/org/argeo/cms/internal/kernel/webdav-config.xml
new file mode 100644 (file)
index 0000000..da4e18b
--- /dev/null
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<!--
+<!DOCTYPE config [
+        <!ELEMENT config (iomanager , propertymanager, (collection | noncollection)? , filter?, mimetypeproperties?) >
+
+        <!ELEMENT iomanager (class, iohandler*) >
+        <!ELEMENT iohandler (class) >
+
+        <!ELEMENT propertymanager (class, propertyhandler*) >
+        <!ELEMENT propertyhandler (class) >
+
+        <!ELEMENT collection (nodetypes) >
+        <!ELEMENT noncollection (nodetypes) >
+
+        <!ELEMENT filter (class, namespaces?, nodetypes?) >
+
+        <!ELEMENT class >
+        <!ATTLIST class
+            name  CDATA #REQUIRED
+        >
+        <!ELEMENT namespaces (prefix | uri)* >
+        <!ELEMENT prefix (CDATA) >
+        <!ELEMENT uri (CDATA) >
+
+        <!ELEMENT nodetypes (nodetype)* >
+        <!ELEMENT nodetype (CDATA) >
+
+        <!ELEMENT mimetypeproperties (mimemapping*, defaultmimetype) >
+
+        <!ELEMENT mimemapping >
+        <!ATTLIST mimemapping
+            extension  CDATA #REQUIRED
+            mimetype  CDATA #REQUIRED
+        >
+
+        <!ELEMENT defaultmimetype (CDATA) >
+]>
+-->
+
+<config>
+    <!--
+     Defines the IOManager implementation that is responsible for passing
+     import/export request to the individual IO-handlers.
+    -->
+    <iomanager>
+        <!-- class element defines the manager to be used. The specified class
+             must implement the IOManager interface.
+             Note, that the handlers are being added and called in the order
+             they appear in the configuration.
+        -->
+        <class name="org.apache.jackrabbit.server.io.IOManagerImpl" />
+        <iohandler>
+            <class name="org.apache.jackrabbit.server.io.VersionHandler" />
+        </iohandler>
+        <iohandler>
+            <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
+        </iohandler>
+<!--         <iohandler> -->
+<!--             <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
+<!--         </iohandler> -->
+<!--         <iohandler> -->
+<!--             <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
+<!--         </iohandler> -->
+        <iohandler>
+            <class name="org.apache.jackrabbit.server.io.DirListingExportHandler" />
+        </iohandler>
+        <iohandler>
+            <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
+        </iohandler>
+    </iomanager>
+    <!--
+     Example config for iomanager that populates its list of handlers with
+     default values. Therefore the 'iohandler' elements are omited.
+    -->
+    <!--
+    <iomanager>
+        <class name="org.apache.jackrabbit.server.io.DefaultIOManager" />
+    </iomanager>
+    -->
+    <!--
+     Defines the PropertyManager implementation that is responsible for export
+     and import of resource properties.
+    -->
+    <propertymanager>
+        <!-- class element defines the manager to be used. The specified class
+             must implement the PropertyManager interface.
+             Note, that the handlers are being added and called in the order
+             they appear in the configuration.
+        -->
+        <class name="org.apache.jackrabbit.server.io.PropertyManagerImpl" />
+        <propertyhandler>
+            <class name="org.apache.jackrabbit.server.io.VersionHandler" />
+        </propertyhandler>
+        <propertyhandler>
+            <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
+        </propertyhandler>
+<!--         <propertyhandler> -->
+<!--             <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
+<!--         </propertyhandler> -->
+<!--         <propertyhandler> -->
+<!--             <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
+<!--         </propertyhandler> -->
+        <propertyhandler>
+            <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
+        </propertyhandler>
+    </propertymanager>
+    <!--
+     Define nodetypes, that should never by displayed as 'collection'
+    -->
+    <noncollection>
+        <nodetypes>
+            <nodetype>nt:file</nodetype>
+            <nodetype>nt:resource</nodetype>
+        </nodetypes>
+    </noncollection>
+    <!--
+     Example: Defines nodetypes, that should always be displayed as 'collection'.
+    -->
+    <!--
+    <collection>
+        <nodetypes>
+            <nodetype>nt:folder</nodetype>
+            <nodetype>rep:root</nodetype>
+        </nodetypes>
+    </collection>
+    -->
+    <!--
+     Filter that allows to prevent certain items from being displayed.
+     Please note, that this has an effect on PROPFIND calls only and does not
+     provide limited access to those items matching any of the filters.
+
+     However specifying a filter may cause problems with PUT or MKCOL if the
+     resource to be created is being filtered out, thus resulting in inconsistent
+     responses (e.g. PUT followed by PROPFIND on parent).
+     -->
+    <filter>
+        <!-- class element defines the resource filter to be used. The specified class
+             must implement the ItemFilter interface -->
+        <class name="org.apache.jackrabbit.webdav.simple.DefaultItemFilter" />
+        <!--
+         Nodetype names to be used to filter child nodes.
+         A child node can be filtered if the declaring nodetype of its definition
+         is one of the nodetype names specified in the nodetypes Element.
+         E.g. defining 'rep:root' as filtered nodetype whould result in jcr:system
+         being hidden but no other child node of the root node, since those
+         are defined by the nodetype nt:unstructered.
+        -->
+        <!--
+        <nodetypes>
+            <nodetype>rep:root</nodetype>
+        </nodetypes>
+        -->
+        <!--
+         Namespace prefixes or uris. Items having a name that matches any of the
+         entries will be filtered.
+        -->
+        <namespaces>
+            <prefix>rep</prefix>
+            <prefix>jcr</prefix>
+            <!--
+            <uri>internal</uri>
+            <uri>http://www.jcp.org/jcr/1.0</uri>
+            -->
+        </namespaces>
+    </filter>
+    
+    <!--
+     Optional 'mimetypeproperties' element.
+     It defines additional or replaces existing mappings for the MimeResolver
+     instance created by the ResourceConfig.
+     The default mappings are defined in org.apache.jackrabbit.server.io.mimetypes.properties.
+     If the default mime type defined by MimeResolver is 'application/octet-stream'.
+    -->
+    <!--
+    <mimetypeproperties>
+        <mimemapping extension="rtf" mimetype="application/rtf" />
+        <mimemapping extension="ott" mimetype="application/vnd.oasis.opendocument.text-template" />
+        <defaultmimetype>text/html</defaultmimetype>
+    </mimetypeproperties>
+    -->
+</config>
diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/ScopedSessionProvider.java b/org.argeo.security.core/src/org/argeo/security/jcr/jackrabbit/ScopedSessionProvider.java
new file mode 100644 (file)
index 0000000..635f71e
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.jcr.jackrabbit;
+
+import java.io.Serializable;
+
+import javax.jcr.LoginException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.server.SessionProvider;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoJcrConstants;
+import org.argeo.jcr.JcrUtils;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/**
+ * Session provider assuming a single workspace and a short life cycle,
+ * typically a Spring bean of scope (web) 'session'.
+ */
+public class ScopedSessionProvider implements SessionProvider, Serializable {
+       private static final long serialVersionUID = 6589775984177317058L;
+       private static final Log log = LogFactory
+                       .getLog(ScopedSessionProvider.class);
+       private transient HttpSession httpSession = null;
+       private transient Session jcrSession = null;
+
+       private transient String currentRepositoryName = null;
+       private transient String currentWorkspaceName = null;
+       private transient String currentJcrUser = null;
+
+       // private transient String anonymousUserId = "anonymous";
+
+       public Session getSession(HttpServletRequest request, Repository rep,
+                       String workspace) throws LoginException, ServletException,
+                       RepositoryException {
+
+               Authentication authentication = SecurityContextHolder.getContext()
+                               .getAuthentication();
+               if (authentication == null)
+                       throw new ArgeoException(
+                                       "Request not authenticated by Spring Security");
+               String springUser = authentication.getName();
+
+               // HTTP
+               String requestJcrRepository = (String) request
+                               .getAttribute(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
+
+               // HTTP session
+               if (httpSession != null
+                               && !httpSession.getId().equals(request.getSession().getId()))
+                       throw new ArgeoException(
+                                       "Only session scope is supported in this mode");
+               if (httpSession == null)
+                       httpSession = request.getSession();
+
+               // Initializes current values
+               if (currentRepositoryName == null)
+                       currentRepositoryName = requestJcrRepository;
+               if (currentWorkspaceName == null)
+                       currentWorkspaceName = workspace;
+               if (currentJcrUser == null)
+                       currentJcrUser = springUser;
+
+               // logout if there was a change in session coordinates
+               if (jcrSession != null)
+                       if (!currentRepositoryName.equals(requestJcrRepository)) {
+                               if (log.isDebugEnabled())
+                                       log.debug(getHttpSessionId() + " Changed from repository '"
+                                                       + currentRepositoryName + "' to '"
+                                                       + requestJcrRepository
+                                                       + "', logging out cached JCR session.");
+                               logout();
+                       } else if (!currentWorkspaceName.equals(workspace)) {
+                               if (log.isDebugEnabled())
+                                       log.debug(getHttpSessionId() + " Changed from workspace '"
+                                                       + currentWorkspaceName + "' to '" + workspace
+                                                       + "', logging out cached JCR session.");
+                               logout();
+                       } else if (!currentJcrUser.equals(springUser)) {
+                               if (log.isDebugEnabled())
+                                       log.debug(getHttpSessionId() + " Changed from user '"
+                                                       + currentJcrUser + "' to '" + springUser
+                                                       + "', logging out cached JCR session.");
+                               logout();
+                       }
+
+               // login if needed
+               if (jcrSession == null)
+                       try {
+                               Session session = login(rep, workspace);
+                               if (!session.getUserID().equals(springUser)) {
+                                       JcrUtils.logoutQuietly(session);
+                                       throw new ArgeoException("Spring Security user '"
+                                                       + springUser + "' not in line with JCR user '"
+                                                       + session.getUserID() + "'");
+                               }
+                               currentRepositoryName = requestJcrRepository;
+                               // do not use workspace variable which may be null
+                               currentWorkspaceName = session.getWorkspace().getName();
+                               currentJcrUser = session.getUserID();
+
+                               jcrSession = session;
+                               return jcrSession;
+                       } catch (RepositoryException e) {
+                               throw new ArgeoException("Cannot open session to workspace "
+                                               + workspace, e);
+                       }
+
+               // returns cached session
+               return jcrSession;
+       }
+
+       protected Session login(Repository repository, String workspace)
+                       throws RepositoryException {
+               Session session = repository.login(workspace);
+               if (log.isDebugEnabled())
+                       log.debug(getHttpSessionId() + " User '" + session.getUserID()
+                                       + "' logged in workspace '"
+                                       + session.getWorkspace().getName() + "' of repository '"
+                                       + currentRepositoryName + "'");
+               return session;
+       }
+
+       public void releaseSession(Session session) {
+               if (log.isTraceEnabled())
+                       log.trace(getHttpSessionId() + " Releasing JCR session " + session);
+       }
+
+       protected void logout() {
+               JcrUtils.logoutQuietly(jcrSession);
+               jcrSession = null;
+       }
+
+       protected final String getHttpSessionId() {
+               return httpSession != null ? httpSession.getId() : "<null>";
+       }
+
+       public void init() {
+       }
+
+       public void destroy() {
+               logout();
+               if (getHttpSessionId() != null)
+                       if (log.isDebugEnabled())
+                               log.debug(getHttpSessionId()
+                                               + " Cleaned up provider for web session ");
+               httpSession = null;
+       }
+
+}
diff --git a/org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/servlet/OpenInViewSessionProvider.java b/org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/servlet/OpenInViewSessionProvider.java
new file mode 100644 (file)
index 0000000..519a218
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jackrabbit.servlet;
+
+import java.io.Serializable;
+
+import javax.jcr.LoginException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.server.SessionProvider;
+import org.argeo.jcr.JcrUtils;
+
+/**
+ * Implements an open session in view patter: a new JCR session is created for
+ * each request
+ */
+public class OpenInViewSessionProvider implements SessionProvider, Serializable {
+       private static final long serialVersionUID = 2270957712453841368L;
+
+       private final static Log log = LogFactory
+                       .getLog(OpenInViewSessionProvider.class);
+
+       public Session getSession(HttpServletRequest request, Repository rep,
+                       String workspace) throws LoginException, ServletException,
+                       RepositoryException {
+               return login(request, rep, workspace);
+       }
+
+       protected Session login(HttpServletRequest request, Repository repository,
+                       String workspace) throws RepositoryException {
+               if (log.isTraceEnabled())
+                       log.trace("Login to workspace "
+                                       + (workspace == null ? "<default>" : workspace)
+                                       + " in web session " + request.getSession().getId());
+               return repository.login(workspace);
+       }
+
+       public void releaseSession(Session session) {
+               JcrUtils.logoutQuietly(session);
+               if (log.isTraceEnabled())
+                       log.trace("Logged out remote JCR session " + session);
+       }
+
+       public void init() {
+       }
+
+       public void destroy() {
+       }
+
+}
diff --git a/org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/servlet/RemotingServlet.java b/org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/servlet/RemotingServlet.java
new file mode 100644 (file)
index 0000000..01bbb35
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jackrabbit.servlet;
+
+import javax.jcr.Repository;
+
+import org.apache.jackrabbit.server.SessionProvider;
+import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet;
+
+/** Provides remote access to a JCR repository */
+public class RemotingServlet extends JcrRemotingServlet {
+       public final static String INIT_PARAM_RESOURCE_PATH_PREFIX = JcrRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX;
+       public final static String INIT_PARAM_HOME = JcrRemotingServlet.INIT_PARAM_HOME;
+       public final static String INIT_PARAM_TMP_DIRECTORY = JcrRemotingServlet.INIT_PARAM_TMP_DIRECTORY;
+
+       private static final long serialVersionUID = 3131835511468341309L;
+
+       private final Repository repository;
+       private final SessionProvider sessionProvider;
+
+       public RemotingServlet(Repository repository,
+                       SessionProvider sessionProvider) {
+               this.repository = repository;
+               this.sessionProvider = sessionProvider;
+       }
+
+       @Override
+       protected Repository getRepository() {
+               return repository;
+       }
+
+       @Override
+       protected SessionProvider getSessionProvider() {
+               return sessionProvider;
+       }
+
+}
diff --git a/org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/servlet/WebdavServlet.java b/org.argeo.server.jackrabbit/src/org/argeo/jackrabbit/servlet/WebdavServlet.java
new file mode 100644 (file)
index 0000000..c2346f0
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jackrabbit.servlet;
+
+import java.io.IOException;
+
+import javax.jcr.Repository;
+import javax.servlet.ServletException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.server.SessionProvider;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavResource;
+import org.apache.jackrabbit.webdav.WebdavRequest;
+import org.apache.jackrabbit.webdav.WebdavResponse;
+import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet;
+
+/** WebDav servlet whose repository is injected */
+public class WebdavServlet extends SimpleWebdavServlet {
+       public final static String INIT_PARAM_RESOURCE_CONFIG = SimpleWebdavServlet.INIT_PARAM_RESOURCE_CONFIG;
+       public final static String INIT_PARAM_RESOURCE_PATH_PREFIX = SimpleWebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX;
+
+       private static final long serialVersionUID = -369787931175177080L;
+
+       private final static Log log = LogFactory.getLog(WebdavServlet.class);
+
+       private final Repository repository;
+
+       public WebdavServlet(Repository repository, SessionProvider sessionProvider) {
+               this.repository = repository;
+               setSessionProvider(sessionProvider);
+       }
+
+       public Repository getRepository() {
+               return repository;
+       }
+
+       @Override
+       protected boolean execute(WebdavRequest request, WebdavResponse response,
+                       int method, DavResource resource) throws ServletException,
+                       IOException, DavException {
+               if (log.isTraceEnabled())
+                       log.trace(request.getMethod() + "\t" + request.getPathInfo());
+               boolean res = super.execute(request, response, method, resource);
+               return res;
+       }
+
+}
diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java b/org.argeo.server.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java
new file mode 100644 (file)
index 0000000..6185413
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.proxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.JcrUtils;
+
+/** Wraps a proxy via HTTP */
+public class ResourceProxyServlet extends HttpServlet {
+       private static final long serialVersionUID = -8886549549223155801L;
+
+       private final static Log log = LogFactory
+                       .getLog(ResourceProxyServlet.class);
+
+       private ResourceProxy proxy;
+
+       private String contentTypeCharset = "UTF-8";
+
+       @Override
+       protected void doGet(HttpServletRequest request,
+                       HttpServletResponse response) throws ServletException, IOException {
+               String path = request.getPathInfo();
+
+               if (log.isTraceEnabled()) {
+                       log.trace("path=" + path);
+                       log.trace("UserPrincipal = " + request.getUserPrincipal().getName());
+                       log.trace("SessionID = " + request.getSession().getId());
+                       log.trace("ContextPath = " + request.getContextPath());
+                       log.trace("ServletPath = " + request.getServletPath());
+                       log.trace("PathInfo = " + request.getPathInfo());
+                       log.trace("Method = " + request.getMethod());
+                       log.trace("User-Agent = " + request.getHeader("User-Agent"));
+               }
+
+               Node node = null;
+               try {
+                       node = proxy.proxy(path);
+                       if (node == null)
+                               response.sendError(404);
+                       else
+                               processResponse(node, response);
+               } finally {
+                       if (node != null)
+                               try {
+                                       JcrUtils.logoutQuietly(node.getSession());
+                               } catch (RepositoryException e) {
+                                       // silent
+                               }
+               }
+
+       }
+
+       /** Retrieve the content of the node. */
+       protected void processResponse(Node node, HttpServletResponse response) {
+               Binary binary = null;
+               InputStream in = null;
+               try {
+                       String fileName = node.getName();
+                       String ext = FilenameUtils.getExtension(fileName);
+
+                       // TODO use a more generic / standard approach
+                       // see http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml
+                       String contentType;
+                       if ("xml".equals(ext))
+                               contentType = "text/xml;charset=" + contentTypeCharset;
+                       else if ("jar".equals(ext))
+                               contentType = "application/java-archive";
+                       else if ("zip".equals(ext))
+                               contentType = "application/zip";
+                       else if ("gz".equals(ext))
+                               contentType = "application/x-gzip";
+                       else if ("bz2".equals(ext))
+                               contentType = "application/x-bzip2";
+                       else if ("tar".equals(ext))
+                               contentType = "application/x-tar";
+                       else if ("rpm".equals(ext))
+                               contentType = "application/x-redhat-package-manager";
+                       else
+                               contentType = "application/octet-stream";
+                       contentType = contentType + ";name=\"" + fileName + "\"";
+                       response.setHeader("Content-Disposition", "attachment; filename=\""
+                                       + fileName + "\"");
+                       response.setHeader("Expires", "0");
+                       response.setHeader("Cache-Control", "no-cache, must-revalidate");
+                       response.setHeader("Pragma", "no-cache");
+
+                       response.setContentType(contentType);
+
+                       try {
+                               binary = node.getNode(Property.JCR_CONTENT)
+                                               .getProperty(Property.JCR_DATA).getBinary();
+                       } catch (PathNotFoundException e) {
+                               log.error("Node " + node + " as no data under content");
+                               throw e;
+                       }
+                       in = binary.getStream();
+                       IOUtils.copy(in, response.getOutputStream());
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot download " + node, e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+                       JcrUtils.closeQuietly(binary);
+               }
+       }
+
+       public void setProxy(ResourceProxy resourceProxy) {
+               this.proxy = resourceProxy;
+       }
+
+}
diff --git a/pom.xml b/pom.xml
index 33c70f142686ee658f55461de26286be5978e43b..6f0d352ed2e37d1868f36d035901866dc574f11f 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>org.argeo.commons</groupId>
        <artifactId>argeo-commons</artifactId>
                <module>org.argeo.security.auth.ldap</module>
                <module>org.argeo.security.dao.ldap</module>
                <module>org.argeo.security.dao.cli</module>
-               <module>org.argeo.security.dao.jackrabbit</module>
+               <!-- <module>org.argeo.security.dao.jackrabbit</module> -->
                <!-- Server -->
                <module>org.argeo.server.core</module>
-               <module>org.argeo.server.jcr.mvc</module>
+               <!-- <module>org.argeo.server.jcr.mvc</module> -->
                <module>org.argeo.server.jackrabbit</module>
                <!-- Eclipse -->
                <module>org.argeo.eclipse.ui</module>
                <module>org.argeo.eclipse.ui.rap</module>
                <!-- Argeo Node -->
-               <module>org.argeo.node.repo.jackrabbit</module>
+               <!-- <module>org.argeo.node.repo.jackrabbit</module> -->
                <module>org.argeo.cms</module>
                <!-- Workbench -->
                <module>org.argeo.eclipse.ui.workbench</module>