X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Finternal%2Fkernel%2FNodeHttp.java;h=e9219849523eaf0c5bb23cbf21bdd3ad76239ddc;hb=1f4ff4da0e5d85821b005267dfa9eece9f8ca9bb;hp=928afd3a302836d28bf6d45b6ce8a3e0441b348f;hpb=04dade003a08c5915280ae653639b0af7cddeb0c;p=lgpl%2Fargeo-commons.git 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 index 928afd3a3..e92198495 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java @@ -1,284 +1,310 @@ package org.argeo.cms.internal.kernel; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Properties; -import java.util.StringTokenizer; -import javax.servlet.FilterChain; -import javax.servlet.Servlet; +import javax.jcr.Repository; 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.apache.jackrabbit.server.SessionProvider; +import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; +import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet; import org.argeo.cms.CmsException; -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.argeo.cms.internal.http.CmsSessionProvider; +import org.argeo.cms.internal.http.DataHttpContext; +import org.argeo.cms.internal.http.HttpUtils; +import org.argeo.cms.internal.http.LinkServlet; +import org.argeo.cms.internal.http.PrivateHttpContext; +import org.argeo.cms.internal.http.RobotServlet; +import org.argeo.node.NodeConstants; import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.service.http.HttpService; 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 { +public class NodeHttp implements KernelConstants { 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"; + public final static String DEFAULT_SERVICE = "HTTP"; - private final AuthenticationManager authenticationManager; - private final BundleContext bundleContext; - private ExtendedHttpService httpService; + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - // FIXME Make it more unique - private String httpAuthRealm = "Argeo"; + private ServiceTracker repositories; + private final ServiceTracker httpServiceTracker; - // Filters - // private final RootFilter rootFilter; + private String httpRealm = "Argeo"; + private String webDavConfig = HttpUtils.WEBDAV_CONFIG; + private final boolean cleanState; - // remoting - private OpenInViewSessionProvider sessionProvider; - private WebdavServlet publicWebdavServlet; - private WebdavServlet privateWebdavServlet; - private RemotingServlet publicRemotingServlet; - private RemotingServlet privateRemotingServlet; + public NodeHttp(boolean cleanState) { + this.cleanState = cleanState; + httpServiceTracker = new PrepareHttpStc(); + // httpServiceTracker.open(); + KernelUtils.asyncOpen(httpServiceTracker); + } - NodeHttp(BundleContext bundleContext, JackrabbitNode node, - NodeSecurity authenticationManager) { - this.bundleContext = bundleContext; - this.authenticationManager = authenticationManager; + public void destroy() { + if (repositories != null) + repositories.close(); + } - // Equinox dependency - ServiceTracker st = new ServiceTracker( - bundleContext, ExtendedHttpService.class, null); - st.open(); + public void registerRepositoryServlets(HttpService httpService, String alias, Repository repository) { + if (httpService == null) + throw new CmsException("No HTTP service available"); try { - httpService = st.waitForService(1000); - } catch (InterruptedException e) { - httpService = null; + registerWebdavServlet(httpService, alias, repository); + registerRemotingServlet(httpService, alias, repository); + if (NodeConstants.HOME.equals(alias)) + registerFilesServlet(httpService, alias, repository); + if (log.isTraceEnabled()) + log.trace("Registered servlets for repository '" + alias + "'"); + } catch (Exception e) { + throw new CmsException("Could not register servlets for repository '" + alias + "'", e); } - - 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() { + public static void unregisterRepositoryServlets(HttpService httpService, String alias) { + if (httpService == null) + return; 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); + httpService.unregister(webdavPath(alias)); + httpService.unregister(remotingPath(alias)); + if (NodeConstants.HOME.equals(alias)) + httpService.unregister(filesPath(alias)); + if (log.isTraceEnabled()) + log.trace("Unregistered servlets for repository '" + alias + "'"); } catch (Exception e) { - throw new CmsException("Cannot publish HTTP services to OSGi", e); + log.error("Could not unregister servlets for repository '" + alias + "'", e); } } - private void registerWebdavServlet(String pathPrefix, String alias, - Boolean anonymous, WebdavServlet webdavServlet) + void registerWebdavServlet(HttpService httpService, String alias, Repository repository) + throws NamespaceException, ServletException { + // WebdavServlet webdavServlet = new WebdavServlet(repository, new + // OpenInViewSessionProvider(alias)); + WebdavServlet webdavServlet = new WebdavServlet(repository, new CmsSessionProvider(alias)); + String path = webdavPath(alias); + Properties ip = new Properties(); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + httpService.registerServlet(path, webdavServlet, ip, new DataHttpContext(httpRealm)); + } + + void registerFilesServlet(HttpService httpService, String alias, Repository repository) 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); + WebdavServlet filesServlet = new WebdavServlet(repository, new CmsSessionProvider(alias)); + String path = filesPath(alias); + Properties ip = new Properties(); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + httpService.registerServlet(path, filesServlet, ip, new PrivateHttpContext(httpRealm, true)); } - private void registerRemotingServlet(String pathPrefix, String alias, - Boolean anonymous, RemotingServlet remotingServlet) + void registerRemotingServlet(HttpService httpService, String alias, Repository repository) throws NamespaceException, ServletException { - String path = pathPrefix + "/" + alias; - Properties initParameters = new Properties(); - initParameters.setProperty( - RemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + RemotingServlet remotingServlet = new RemotingServlet(repository, new CmsSessionProvider(alias)); + String path = remotingPath(alias); + Properties ip = new Properties(); + ip.setProperty(JcrRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + ip.setProperty(JcrRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER, "Negotiate"); // 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); + Path tmpDir; + try { + tmpDir = Files.createTempDirectory("remoting_" + alias); + } catch (IOException e) { + throw new CmsException("Cannot create temp directory for remoting servlet", e); + } + ip.setProperty(RemotingServlet.INIT_PARAM_HOME, tmpDir.toString()); + ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting_" + alias); + ip.setProperty(RemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, HttpUtils.DEFAULT_PROTECTED_HANDLERS); + ip.setProperty(RemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false"); + httpService.registerServlet(path, remotingServlet, ip, new PrivateHttpContext(httpRealm)); } - private Boolean isSessionAuthenticated(HttpSession httpSession) { - SecurityContext contextFromSession = (SecurityContext) httpSession - .getAttribute(SPRING_SECURITY_CONTEXT_KEY); - return contextFromSession != null; + static String webdavPath(String alias) { + return NodeConstants.PATH_DATA + "/" + alias; } - private void requestBasicAuth(HttpSession httpSession, - HttpServletResponse response) { - response.setStatus(401); - response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\"" - + httpAuthRealm + "\""); - httpSession.setAttribute(ATTR_AUTH, Boolean.TRUE); + static String remotingPath(String alias) { + return NodeConstants.PATH_JCR + "/" + alias; } - 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"); + static String filesPath(String alias) { + return NodeConstants.PATH_FILES; } - /** Intercepts all requests. Authenticates. */ - class RootFilter extends HttpFilter { + class RepositoriesStc extends ServiceTracker { + private final HttpService httpService; + + private final BundleContext bc; + + public RepositoriesStc(BundleContext bc, HttpService httpService) { + super(bc, Repository.class, null); + this.httpService = httpService; + this.bc = bc; + } @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; + public Repository addingService(ServiceReference reference) { + Repository repository = bc.getService(reference); + Object jcrRepoAlias = reference.getProperty(NodeConstants.CN); + if (jcrRepoAlias != null) { + String alias = jcrRepoAlias.toString(); + registerRepositoryServlets(httpService, alias, repository); } + return repository; + } - // 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; - } + @Override + public void modifiedService(ServiceReference reference, Repository service) { + } - Boolean doBasicAuth = true; - if (doBasicAuth) { - requestBasicAuth(httpSession, response); - // skip filter chain - return; + @Override + public void removedService(ServiceReference reference, Repository service) { + Object jcrRepoAlias = reference.getProperty(NodeConstants.CN); + if (jcrRepoAlias != null) { + String alias = jcrRepoAlias.toString(); + unregisterRepositoryServlets(httpService, alias); } + } + } - // TODO Login page + private class PrepareHttpStc extends ServiceTracker { + public PrepareHttpStc() { + super(bc, HttpService.class, null); + } - // Anonymous - KernelUtils.anonymousLogin(authenticationManager); - filterChain.doFilter(request, response); + @Override + public HttpService addingService(ServiceReference reference) { + long begin = System.currentTimeMillis(); + if (log.isTraceEnabled()) + log.trace("HTTP prepare starts..."); + HttpService httpService = addHttpService(reference); + if (log.isTraceEnabled()) + log.trace("HTTP prepare duration: " + (System.currentTimeMillis() - begin) + "ms"); + return httpService; } - } - /** 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; + public void removedService(ServiceReference reference, HttpService service) { + repositories.close(); + repositories = null; + } + + private HttpService addHttpService(ServiceReference sr) { + HttpService httpService = bc.getService(sr); + // TODO find constants + Object httpPort = sr.getProperty("http.port"); + Object httpsPort = sr.getProperty("https.port"); + + try { + httpService.registerServlet("/!", new LinkServlet(), null, null); + httpService.registerServlet("/robots.txt", new RobotServlet(), null, null); + // httpService.registerServlet("/html", new HtmlServlet(), null, null); + } catch (Exception e) { + throw new CmsException("Cannot register filters", e); } + // track repositories + if (repositories != null) + throw new CmsException("An http service is already configured"); + repositories = new RepositoriesStc(bc, httpService); + // repositories.open(); + if (cleanState) + KernelUtils.asyncOpen(repositories); + log.info(httpPortsMsg(httpPort, httpsPort)); + // httpAvailable = true; + // checkReadiness(); + + bc.registerService(NodeHttp.class, NodeHttp.this, null); + return httpService; + } - KernelUtils.anonymousLogin(authenticationManager); - filterChain.doFilter(request, response); + private String httpPortsMsg(Object httpPort, Object httpsPort) { + return "HTTP " + httpPort + (httpsPort != null ? " - HTTPS " + httpsPort : ""); } } - /** Intercepts all requests. Authenticates. */ - class DavFilter extends HttpFilter { + private static class WebdavServlet extends SimpleWebdavServlet { + private static final long serialVersionUID = -4687354117811443881L; + private final Repository repository; + + public WebdavServlet(Repository repository, SessionProvider sessionProvider) { + this.repository = repository; + setSessionProvider(sessionProvider); + } + + public Repository getRepository() { + return repository; + } @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; - } + protected void service(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException, IOException { + WebdavServlet.super.service(request, response); + // try { + // Subject subject = subjectFromRequest(request); + // // TODO make it stronger, with eTags. + // // if (CurrentUser.isAnonymous(subject) && + // // request.getMethod().equals("GET")) { + // // response.setHeader("Cache-Control", "no-transform, public, + // // max-age=300, s-maxage=900"); + // // } + // + // Subject.doAs(subject, new PrivilegedExceptionAction() { + // @Override + // public Void run() throws Exception { + // WebdavServlet.super.service(request, response); + // return null; + // } + // }); + // } catch (PrivilegedActionException e) { + // throw new CmsException("Cannot process webdav request", + // e.getException()); + // } + } - // 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; - } + } + + private static class RemotingServlet extends JcrRemotingServlet { + private final Log log = LogFactory.getLog(RemotingServlet.class); + private static final long serialVersionUID = 4605238259548058883L; + 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; + } - requestBasicAuth(httpSession, response); + @Override + protected void service(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException, IOException { + if (log.isTraceEnabled()) + HttpUtils.logRequest(log, request); + RemotingServlet.super.service(request, response); } }