X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;ds=inline;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Finternal%2Fkernel%2FNodeHttp.java;fp=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Finternal%2Fkernel%2FNodeHttp.java;h=f17c982417542c33d16764fee9dd1b9de2051585;hb=6a62c05a487ba34946b1924a039603e68b1d54e6;hp=0000000000000000000000000000000000000000;hpb=84478b9e5a30d3d3d581b0bd929622567d5c7e4e;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 new file mode 100644 index 000000000..f17c98241 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java @@ -0,0 +1,342 @@ +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 javax.jcr.Repository; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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.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; + +/** + * Intercepts and enriches http access, mainly focusing on security and + * transactionality. + */ +public class NodeHttp implements KernelConstants { + private final static Log log = LogFactory.getLog(NodeHttp.class); + + public final static String DEFAULT_SERVICE = "HTTP"; + + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + private ServiceTracker repositories; + private final ServiceTracker httpServiceTracker; + + private static String httpRealm = "Argeo"; + + public NodeHttp() { + httpServiceTracker = new PrepareHttpStc(); + // httpServiceTracker.open(); + KernelUtils.asyncOpen(httpServiceTracker); + } + + public void destroy() { + if (repositories != null) + repositories.close(); + } + + public static void registerRepositoryServlets(HttpService httpService, String alias, Repository repository) { + if (httpService == null) + throw new CmsException("No HTTP service available"); + try { + registerWebdavServlet(httpService, alias, repository); + registerRemotingServlet(httpService, alias, repository); + if (NodeConstants.HOME.equals(alias)) + registerFilesServlet(httpService, alias, repository); + if (log.isDebugEnabled()) + log.debug("Registered servlets for repository '" + alias + "'"); + } catch (Exception e) { + throw new CmsException("Could not register servlets for repository '" + alias + "'", e); + } + } + + public static void unregisterRepositoryServlets(HttpService httpService, String alias) { + if (httpService == null) + return; + try { + httpService.unregister(webdavPath(alias)); + httpService.unregister(remotingPath(alias)); + if (NodeConstants.HOME.equals(alias)) + httpService.unregister(filesPath(alias)); + if (log.isDebugEnabled()) + log.debug("Unregistered servlets for repository '" + alias + "'"); + } catch (Exception e) { + log.error("Could not unregister servlets for repository '" + alias + "'", e); + } + } + + static 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, HttpUtils.WEBDAV_CONFIG); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + httpService.registerServlet(path, webdavServlet, ip, new DataHttpContext(httpRealm)); + } + + static void registerFilesServlet(HttpService httpService, String alias, Repository repository) + throws NamespaceException, ServletException { + WebdavServlet filesServlet = new WebdavServlet(repository, new CmsSessionProvider(alias)); + String path = filesPath(alias); + Properties ip = new Properties(); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, HttpUtils.WEBDAV_CONFIG); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + httpService.registerServlet(path, filesServlet, ip, new PrivateHttpContext(httpRealm, true)); + } + + static void registerRemotingServlet(HttpService httpService, String alias, Repository repository) + throws NamespaceException, ServletException { + 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 + 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)); + } + + static String webdavPath(String alias) { + return NodeConstants.PATH_DATA + "/" + alias; + } + + static String remotingPath(String alias) { + return NodeConstants.PATH_JCR + "/" + alias; + } + + static String filesPath(String alias) { + return NodeConstants.PATH_FILES; + } + + // private Subject subjectFromRequest(HttpServletRequest request, + // HttpServletResponse response) { + // Authorization authorization = (Authorization) + // request.getAttribute(HttpContext.AUTHORIZATION); + // if (authorization == null) + // throw new CmsException("Not authenticated"); + // try { + // LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, + // new HttpRequestCallbackHandler(request, response)); + // lc.login(); + // return lc.getSubject(); + // } catch (LoginException e) { + // throw new CmsException("Cannot login", e); + // } + // } + + static 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 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; + } + + @Override + public void modifiedService(ServiceReference reference, Repository service) { + } + + @Override + public void removedService(ServiceReference reference, Repository service) { + Object jcrRepoAlias = reference.getProperty(NodeConstants.CN); + if (jcrRepoAlias != null) { + String alias = jcrRepoAlias.toString(); + unregisterRepositoryServlets(httpService, alias); + } + } + } + + private class PrepareHttpStc extends ServiceTracker { + // private DataHttp dataHttp; + // private NodeHttp nodeHttp; + + public PrepareHttpStc() { + super(bc, HttpService.class, null); + } + + @Override + public HttpService addingService(ServiceReference reference) { + long begin = System.currentTimeMillis(); + log.debug("HTTP prepare starts..."); + HttpService httpService = addHttpService(reference); + log.debug("HTTP prepare duration: " + (System.currentTimeMillis() - begin) + "ms"); + return httpService; + } + + @Override + public void removedService(ServiceReference reference, HttpService service) { + // if (dataHttp != null) + // dataHttp.destroy(); + // dataHttp = null; + // if (nodeHttp != null) + // nodeHttp.destroy(); + // nodeHttp = null; + // destroy(); + 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); + } 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(); + KernelUtils.asyncOpen(repositories); + log.info(httpPortsMsg(httpPort, httpsPort)); + // httpAvailable = true; + // checkReadiness(); + + bc.registerService(NodeHttp.class, NodeHttp.this, null); + return httpService; + } + + private String httpPortsMsg(Object httpPort, Object httpsPort) { + return "HTTP " + httpPort + (httpsPort != null ? " - HTTPS " + httpsPort : ""); + } + } + + 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 + 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()); + // } + } + + } + + 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; + } + + @Override + protected void service(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException, IOException { + // try { + // Subject subject = subjectFromRequest(request, response); + // Subject.doAs(subject, new PrivilegedExceptionAction() { + // @Override + // public Void run() throws Exception { + if (log.isTraceEnabled()) + HttpUtils.logRequest(log, request); + RemotingServlet.super.service(request, response); + // return null; + // } + // }); + // } catch (PrivilegedActionException e) { + // throw new CmsException("Cannot process JCR remoting request", + // e.getException()); + // } + } + } + +}