From: Mathieu Baudier Date: Fri, 5 Jun 2020 08:25:57 +0000 (+0200) Subject: Move all JCR servlets to OSGi WhiteBoard. X-Git-Tag: argeo-commons-2.1.89~131 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=26c4a74d21474d2c8097f3180fc642b169ad61ca;p=lgpl%2Fargeo-commons.git Move all JCR servlets to OSGi WhiteBoard. --- diff --git a/org.argeo.cms/OSGI-INF/dataServletContext.xml b/org.argeo.cms/OSGI-INF/dataServletContext.xml new file mode 100644 index 000000000..1c31adbbd --- /dev/null +++ b/org.argeo.cms/OSGI-INF/dataServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/jcrServletContext.xml b/org.argeo.cms/OSGI-INF/jcrServletContext.xml new file mode 100644 index 000000000..fb6f79e30 --- /dev/null +++ b/org.argeo.cms/OSGI-INF/jcrServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index 4eae650a3..c17664785 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -11,6 +11,8 @@ org.osgi.*;version=0.0.0,\ * Service-Component: OSGI-INF/cmsUserManager.xml,\ +OSGI-INF/jcrServletContext.xml,\ +OSGI-INF/dataServletContext.xml,\ OSGI-INF/filesServletContext.xml,\ OSGI-INF/filesServlet.xml diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsRemotingServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsRemotingServlet.java new file mode 100644 index 000000000..1bda6c7ee --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsRemotingServlet.java @@ -0,0 +1,44 @@ +package org.argeo.cms.internal.http; + +import java.util.Map; + +import javax.jcr.Repository; + +import org.apache.jackrabbit.server.SessionProvider; +import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; +import org.argeo.api.NodeConstants; + +/** A {@link JcrRemotingServlet} based on {@link CmsSessionProvider}. */ +public class CmsRemotingServlet extends JcrRemotingServlet { + private static final long serialVersionUID = 6459455509684213633L; + private Repository repository; + private SessionProvider sessionProvider; + + public CmsRemotingServlet() { + } + + public CmsRemotingServlet(String alias, Repository repository) { + this.repository = repository; + this.sessionProvider = new CmsSessionProvider(alias); + } + + @Override + public Repository getRepository() { + return repository; + } + + public void setRepository(Repository repository, Map properties) { + this.repository = repository; + String alias = properties.get(NodeConstants.CN); + if (alias != null) + sessionProvider = new CmsSessionProvider(alias); + else + throw new IllegalArgumentException("Only aliased repositories are supported"); + } + + @Override + protected SessionProvider getSessionProvider() { + return sessionProvider; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsServletContextHelper.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsServletContextHelper.java new file mode 100644 index 000000000..e2fd3d98f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsServletContextHelper.java @@ -0,0 +1,72 @@ +package org.argeo.cms.internal.http; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.api.NodeConstants; +import org.argeo.cms.auth.HttpRequestCallbackHandler; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.http.context.ServletContextHelper; + +/** + * Default servlet context degrading to anonymous if the the sesison is not + * pre-authenticated. + */ +public class CmsServletContextHelper extends ServletContextHelper { + private final static Log log = LogFactory.getLog(CmsServletContextHelper.class); + // use CMS bundle for resources + private Bundle bundle = FrameworkUtil.getBundle(getClass()); + + public void init(Map properties) { + + } + + public void destroy() { + + } + + @Override + public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException { + if (log.isTraceEnabled()) + HttpUtils.logRequestHeaders(log, request); + LoginContext lc; + try { + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request, response)); + lc.login(); + } catch (LoginException e) { + lc = processUnauthorized(request, response); + if (lc == null) + return false; + } + return true; + } + + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + // anonymous + try { + LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS, + new HttpRequestCallbackHandler(request, response)); + lc.login(); + return lc; + } catch (LoginException e1) { + if (log.isDebugEnabled()) + log.error("Cannot log in as anonymous", e1); + return null; + } + } + + @Override + public URL getResource(String name) { + return bundle.getResource(name); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java index a6ed7d884..f21f72441 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java @@ -43,6 +43,8 @@ public class CmsSessionProvider implements SessionProvider, Serializable { if (log.isTraceEnabled()) { log.trace("Get JCR session from " + cmsSession); } + if (cmsSession == null) + throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI()); Session session = cmsSession.getDataSession(alias, workspace, rep); cmsSessions.put(session, cmsSession); return session; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsWebDavServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsWebDavServlet.java index f60e3b604..d61e5f494 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsWebDavServlet.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsWebDavServlet.java @@ -7,10 +7,19 @@ import javax.jcr.Repository; import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet; import org.argeo.api.NodeConstants; +/** A {@link SimpleWebdavServlet} based on {@link CmsSessionProvider}. */ public class CmsWebDavServlet extends SimpleWebdavServlet { private static final long serialVersionUID = 7485800288686328063L; private Repository repository; + public CmsWebDavServlet() { + } + + public CmsWebDavServlet(String alias, Repository repository) { + this.repository = repository; + setSessionProvider(new CmsSessionProvider(alias)); + } + @Override public Repository getRepository() { return repository; @@ -21,6 +30,8 @@ public class CmsWebDavServlet extends SimpleWebdavServlet { String alias = properties.get(NodeConstants.CN); if (alias != null) setSessionProvider(new CmsSessionProvider(alias)); + else + throw new IllegalArgumentException("Only aliased repositories are supported"); } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java b/org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java index df469524d..edc6326d7 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java @@ -16,6 +16,7 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.service.http.HttpContext; +@Deprecated public class DataHttpContext implements HttpContext { private final static Log log = LogFactory.getLog(DataHttpContext.class); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateHttpContext.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateHttpContext.java index c3f2a1c80..793478c71 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateHttpContext.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateHttpContext.java @@ -5,6 +5,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** Requests authorisation */ +@Deprecated public class PrivateHttpContext extends DataHttpContext { public PrivateHttpContext(String httpAuthrealm, boolean forceBasic) { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateServletContextHelper.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateServletContextHelper.java index 62e74c34f..b5dc7ba6f 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateServletContextHelper.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateServletContextHelper.java @@ -1,53 +1,19 @@ package org.argeo.cms.internal.http; -import java.io.IOException; -import java.net.URL; -import java.util.Map; - import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.api.NodeConstants; -import org.argeo.cms.auth.HttpRequestCallbackHandler; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; -import org.osgi.service.http.context.ServletContextHelper; - -public class PrivateServletContextHelper extends ServletContextHelper { - private final static Log log = LogFactory.getLog(PrivateServletContextHelper.class); - +/** Servlet context forcing authentication. */ +public class PrivateServletContextHelper extends CmsServletContextHelper { // TODO make it configurable private final String httpAuthRealm = "Argeo"; private final boolean forceBasic = false; - // use CMS bundle for resources - private Bundle bundle = FrameworkUtil.getBundle(getClass()); - - public void init(Map properties) { - - } - - public void destroy() { - - } - @Override - public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException { - if (log.isTraceEnabled()) - HttpUtils.logRequestHeaders(log, request); - LoginContext lc; - try { - lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request, response)); - lc.login(); - } catch (LoginException e) { - askForWwwAuth(request, response); - return false; - } - return true; + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + askForWwwAuth(request, response); + return null; } protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) { @@ -68,10 +34,4 @@ public class PrivateServletContextHelper extends ServletContextHelper { // response.setContentType("text/html; charset=UTF-8"); } - - @Override - public URL getResource(String name) { - return bundle.getResource(name); - } - } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java index 34d6e1f24..51feed921 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java @@ -1,6 +1,7 @@ package org.argeo.cms.internal.kernel; import static org.argeo.api.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; +import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX; import java.io.File; import java.io.IOException; @@ -8,6 +9,8 @@ import java.io.InputStreamReader; import java.io.Reader; import java.lang.management.ManagementFactory; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -20,6 +23,7 @@ import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.security.auth.callback.CallbackHandler; +import javax.servlet.Servlet; import javax.transaction.UserTransaction; import org.apache.commons.logging.Log; @@ -35,6 +39,9 @@ import org.argeo.api.security.CryptoKeyring; import org.argeo.api.security.Keyring; import org.argeo.cms.ArgeoNames; import org.argeo.cms.CmsException; +import org.argeo.cms.internal.http.CmsRemotingServlet; +import org.argeo.cms.internal.http.CmsWebDavServlet; +import org.argeo.cms.internal.http.HttpUtils; import org.argeo.jcr.JcrUtils; import org.argeo.osgi.useradmin.UserAdminConf; import org.argeo.util.LangUtils; @@ -51,6 +58,8 @@ import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.cm.ManagedService; +import org.osgi.service.http.HttpService; +import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; import org.osgi.service.useradmin.Group; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.UserAdmin; @@ -68,7 +77,8 @@ public class CmsDeployment implements NodeDeployment { // private final boolean cleanState; - private NodeHttp nodeHttp; +// private NodeHttp nodeHttp; + private String webDavConfig = HttpUtils.WEBDAV_CONFIG; private boolean argeoDataModelExtensionsAvailable = false; @@ -86,19 +96,22 @@ public class CmsDeployment implements NodeDeployment { // NodeState nodeState = bc.getService(nodeStateSr); // cleanState = nodeState.isClean(); - nodeHttp = new NodeHttp(); +// nodeHttp = new NodeHttp(); dataModels = new DataModels(bc); initTrackers(); } private void initTrackers() { - ServiceTracker httpSt = new ServiceTracker(bc, NodeHttp.class, null) { + ServiceTracker httpSt = new ServiceTracker(bc, HttpService.class, null) { @Override - public NodeHttp addingService(ServiceReference reference) { + public HttpService addingService(ServiceReference sr) { httpAvailable = true; + Object httpPort = sr.getProperty("http.port"); + Object httpsPort = sr.getProperty("https.port"); + log.info(httpPortsMsg(httpPort, httpsPort)); checkReadiness(); - return super.addingService(reference); + return super.addingService(sr); } }; // httpSt.open(); @@ -161,6 +174,10 @@ public class CmsDeployment implements NodeDeployment { KernelUtils.asyncOpen(confAdminSt); } + private String httpPortsMsg(Object httpPort, Object httpsPort) { + return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : ""); + } + private void addStandardSystemRoles(UserAdmin userAdmin) { // we assume UserTransaction is already available (TODO make it more robust) UserTransaction userTransaction = bc.getService(bc.getServiceReference(UserTransaction.class)); @@ -195,8 +212,8 @@ public class CmsDeployment implements NodeDeployment { } public void shutdown() { - if (nodeHttp != null) - nodeHttp.destroy(); +// if (nodeHttp != null) +// nodeHttp.destroy(); try { for (ServiceReference sr : bc @@ -418,6 +435,9 @@ public class CmsDeployment implements NodeDeployment { classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() }; } bc.registerService(classes, localRepository, properties); + + // TODO make it configurable + registerRepositoryServlets(dataModelName, localRepository); if (log.isTraceEnabled()) log.trace("Published data model " + dataModelName); } @@ -431,6 +451,54 @@ public class CmsDeployment implements NodeDeployment { return availableSince != null; } + protected void registerRepositoryServlets(String alias, Repository repository) { + registerRemotingServlet(alias, repository); + registerWebdavServlet(alias, repository); + } + + protected void registerWebdavServlet(String alias, Repository repository) { + CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository); + Hashtable ip = new Hashtable<>(); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, + "/" + alias); + + ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*"); + ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, + "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + NodeConstants.PATH_DATA + ")"); + bc.registerService(Servlet.class, webdavServlet, ip); + } + + protected void registerRemotingServlet(String alias, Repository repository) { + CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository); + Hashtable ip = new Hashtable<>(); + ip.put(NodeConstants.CN, alias); + // Properties ip = new Properties(); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, + "/" + alias); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.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.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString()); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY, + "remoting_" + alias); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, + HttpUtils.DEFAULT_PROTECTED_HANDLERS); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false"); + + ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*"); + ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, + "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + NodeConstants.PATH_JCR + ")"); + bc.registerService(Servlet.class, remotingServlet, ip); + } + private class RepositoryContextStc extends ServiceTracker { public RepositoryContextStc() { @@ -446,6 +514,7 @@ public class CmsDeployment implements NodeDeployment { prepareNodeRepository(repoContext.getRepository()); // TODO separate home repository prepareHomeRepository(repoContext.getRepository()); + registerRepositoryServlets(cn, repoContext.getRepository()); nodeAvailable = true; checkReadiness(); } else { 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 87b688a6d..ded4ae10e 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 @@ -34,6 +34,7 @@ import org.osgi.util.tracker.ServiceTracker; * Intercepts and enriches http access, mainly focusing on security and * transactionality. */ +@Deprecated public class NodeHttp implements KernelConstants { private final static Log log = LogFactory.getLog(NodeHttp.class); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryServiceFactory.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryServiceFactory.java index 5189512e4..27e519922 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryServiceFactory.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryServiceFactory.java @@ -1,18 +1,29 @@ package org.argeo.cms.internal.kernel; +import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX; + +import java.io.IOException; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Dictionary; import java.util.HashMap; +import java.util.Hashtable; import java.util.Map; import javax.jcr.Repository; import javax.jcr.RepositoryFactory; +import javax.servlet.Servlet; +import javax.servlet.ServletException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.core.RepositoryContext; +import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; import org.argeo.api.NodeConstants; import org.argeo.cms.CmsException; +import org.argeo.cms.internal.http.CmsRemotingServlet; +import org.argeo.cms.internal.http.HttpUtils; import org.argeo.cms.internal.jcr.RepoConf; import org.argeo.cms.internal.jcr.RepositoryBuilder; import org.argeo.util.LangUtils; @@ -21,7 +32,10 @@ import org.osgi.framework.Constants; import org.osgi.framework.FrameworkUtil; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedServiceFactory; +import org.osgi.service.http.NamespaceException; +import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; +/** A {@link ManagedServiceFactory} creating or referencing JCR repositories. */ class RepositoryServiceFactory implements ManagedServiceFactory { private final static Log log = LogFactory.getLog(RepositoryServiceFactory.class); private final BundleContext bc = FrameworkUtil.getBundle(RepositoryServiceFactory.class).getBundleContext(); @@ -91,7 +105,8 @@ class RepositoryServiceFactory implements ManagedServiceFactory { // home if (cn.equals(NodeConstants.NODE_REPOSITORY)) { - Dictionary homeProps = LangUtils.dico(NodeConstants.CN, NodeConstants.EGO_REPOSITORY); + Dictionary homeProps = LangUtils.dico(NodeConstants.CN, + NodeConstants.EGO_REPOSITORY); EgoRepository homeRepository = new EgoRepository(repository, true); bc.registerService(Repository.class, homeRepository, homeProps); } @@ -126,4 +141,5 @@ class RepositoryServiceFactory implements ManagedServiceFactory { } } } + }