From: Mathieu Baudier Date: Sat, 1 Jan 2022 08:58:34 +0000 (+0100) Subject: Introduce CMS Servlet. X-Git-Tag: argeo-commons-2.3.5~110 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=7064547ae5d85225546f1b8f15d6b5c82f30fe22;p=lgpl%2Fargeo-commons.git Introduce CMS Servlet. --- diff --git a/dep/org.argeo.dep.cms.node/pom.xml b/dep/org.argeo.dep.cms.node/pom.xml index 3348ecd70..7f1ca14ce 100644 --- a/dep/org.argeo.dep.cms.node/pom.xml +++ b/dep/org.argeo.dep.cms.node/pom.xml @@ -23,6 +23,11 @@ + + org.argeo.commons + org.argeo.cms.servlet + 2.3-SNAPSHOT + org.argeo.commons org.argeo.cms.jcr diff --git a/org.argeo.cms.servlet/.classpath b/org.argeo.cms.servlet/.classpath new file mode 100644 index 000000000..e801ebfb4 --- /dev/null +++ b/org.argeo.cms.servlet/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.servlet/.project b/org.argeo.cms.servlet/.project new file mode 100644 index 000000000..b1a25fcc8 --- /dev/null +++ b/org.argeo.cms.servlet/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.servlet + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml b/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml new file mode 100644 index 000000000..30e52311c --- /dev/null +++ b/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml b/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml new file mode 100644 index 000000000..7540a2cdb --- /dev/null +++ b/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms.servlet/bnd.bnd b/org.argeo.cms.servlet/bnd.bnd new file mode 100644 index 000000000..c8251a7fe --- /dev/null +++ b/org.argeo.cms.servlet/bnd.bnd @@ -0,0 +1,10 @@ +Import-Package:\ +org.osgi.service.http;version=0.0.0,\ +org.osgi.service.http.whiteboard;version=0.0.0,\ +org.osgi.framework.namespace;version=0.0.0,\ +org.argeo.api,\ +* + +Service-Component:\ +OSGI-INF/pkgServletContext.xml,\ +OSGI-INF/pkgServlet.xml diff --git a/org.argeo.cms.servlet/build.properties b/org.argeo.cms.servlet/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/org.argeo.cms.servlet/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.argeo.cms.servlet/pom.xml b/org.argeo.cms.servlet/pom.xml new file mode 100644 index 000000000..a60b42d04 --- /dev/null +++ b/org.argeo.cms.servlet/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + org.argeo.commons + 2.3-SNAPSHOT + argeo-commons + .. + + org.argeo.cms.servlet + jar + CMS Servlet + CMS components depending on the Servlet APIs + + + org.argeo.commons + org.argeo.cms + 2.3-SNAPSHOT + + + \ No newline at end of file diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java new file mode 100644 index 000000000..ff341a25a --- /dev/null +++ b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java @@ -0,0 +1,96 @@ +package org.argeo.cms.servlet; + +import java.io.IOException; +import java.net.URL; +import java.security.PrivilegedAction; +import java.util.Map; + +import javax.security.auth.Subject; +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.argeo.cms.servlet.internal.HttpUtils; +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 session is not + * pre-authenticated. + */ +public class CmsServletContext extends ServletContextHelper { + private final static Log log = LogFactory.getLog(CmsServletContext.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(new ServletHttpRequest(request), new ServletHttpResponse(response))); + lc.login(); + } catch (LoginException e) { + lc = processUnauthorized(request, response); + if (log.isTraceEnabled()) + HttpUtils.logResponseHeaders(log, response); + if (lc == null) + return false; + } + + Subject subject = lc.getSubject(); + // log.debug("SERVLET CONTEXT: "+subject); + Subject.doAs(subject, new PrivilegedAction() { + + @Override + public Void run() { + // TODO also set login context in order to log out ? + ServletAuthUtils.configureRequestSecurity(request); + return null; + } + + }); + return true; + } + + @Override + public void finishSecurity(HttpServletRequest request, HttpServletResponse response) { + ServletAuthUtils.clearRequestSecurity(request); + } + + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + // anonymous + try { + LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS, + new HttpRequestCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(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.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java new file mode 100644 index 000000000..3bea0b4de --- /dev/null +++ b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java @@ -0,0 +1,39 @@ +package org.argeo.cms.servlet; + +import javax.security.auth.login.LoginContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.auth.SpnegoLoginModule; +import org.argeo.cms.servlet.internal.HttpUtils; + +/** Servlet context forcing authentication. */ +public class PrivateWwwAuthServletContext extends CmsServletContext { + // TODO make it configurable + private final String httpAuthRealm = "Argeo"; + private final boolean forceBasic = false; + + @Override + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + askForWwwAuth(request, response); + return null; + } + + protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) { + // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic + // realm=\"" + httpAuthRealm + "\""); + if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO + response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Negotiate"); + else + response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Basic realm=\"" + httpAuthRealm + "\""); + + // response.setDateHeader("Date", System.currentTimeMillis()); + // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * + // 60 * 60 * 1000)); + // response.setHeader("Accept-Ranges", "bytes"); + // response.setHeader("Connection", "Keep-Alive"); + // response.setHeader("Keep-Alive", "timeout=5, max=97"); + // response.setContentType("text/html; charset=UTF-8"); + response.setStatus(401); + } +} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletAuthUtils.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletAuthUtils.java new file mode 100644 index 000000000..67db467ce --- /dev/null +++ b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletAuthUtils.java @@ -0,0 +1,66 @@ +package org.argeo.cms.servlet; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.Supplier; + +import javax.security.auth.Subject; +import javax.servlet.http.HttpServletRequest; + +import org.argeo.api.cms.CmsSession; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.osgi.CmsOsgiUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.http.HttpContext; + +/** Authentications utilities when using servlets. */ +public class ServletAuthUtils { + private static BundleContext bundleContext = FrameworkUtil.getBundle(ServletAuthUtils.class).getBundleContext(); + + /** + * Execute this supplier, using the CMS class loader as context classloader. + * Useful to log in to JCR. + */ + public final static T doAs(Supplier supplier, HttpServletRequest req) { + ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(ServletAuthUtils.class.getClassLoader()); + try { + return Subject.doAs( + Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())), + new PrivilegedAction() { + + @Override + public T run() { + return supplier.get(); + } + + }); + } finally { + Thread.currentThread().setContextClassLoader(currentContextCl); + } + } + + public final static void configureRequestSecurity(HttpServletRequest req) { + if (req.getAttribute(AccessControlContext.class.getName()) != null) + throw new IllegalStateException("Request already authenticated."); + AccessControlContext acc = AccessController.getContext(); + req.setAttribute(HttpContext.REMOTE_USER, CurrentUser.getUsername()); + req.setAttribute(AccessControlContext.class.getName(), acc); + } + + public final static void clearRequestSecurity(HttpServletRequest req) { + if (req.getAttribute(AccessControlContext.class.getName()) == null) + throw new IllegalStateException("Cannot clear non-authenticated request."); + req.setAttribute(HttpContext.REMOTE_USER, null); + req.setAttribute(AccessControlContext.class.getName(), null); + } + + public static CmsSession getCmsSession(HttpServletRequest req) { + Subject subject = Subject + .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())); + CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, subject); + return cmsSession; + } +} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java new file mode 100644 index 000000000..523f7b757 --- /dev/null +++ b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java @@ -0,0 +1,83 @@ +package org.argeo.cms.servlet; + +import java.util.Locale; +import java.util.Objects; + +import javax.servlet.http.HttpServletRequest; + +import org.argeo.cms.auth.HttpRequest; +import org.argeo.cms.auth.HttpSession; + +public class ServletHttpRequest implements HttpRequest { + private final HttpServletRequest request; + + public ServletHttpRequest(HttpServletRequest request) { + Objects.requireNonNull(request); + this.request = request; + } + + @Override + public HttpSession getSession() { + return new ServletHttpSession(); + } + + @Override + public HttpSession createSession() { + request.getSession(true); + return new ServletHttpSession(); + } + + @Override + public Locale getLocale() { + return request.getLocale(); + } + + @Override + public Object getAttribute(String key) { + return request.getAttribute(key); + } + + @Override + public void setAttribute(String key, Object object) { + request.setAttribute(key, object); + } + + @Override + public String getHeader(String key) { + return request.getHeader(key); + } + + @Override + public String getRemoteAddr() { + return request.getRemoteAddr(); + } + + @Override + public int getLocalPort() { + return request.getLocalPort(); + } + + @Override + public int getRemotePort() { + return request.getRemotePort(); + } + + private class ServletHttpSession implements HttpSession { + + @Override + public boolean isValid() { + try {// test http session + request.getSession(false).getCreationTime(); + return true; + } catch (IllegalStateException ise) { + return false; + } + } + + @Override + public String getId() { + return request.getSession(false).getId(); + } + + } +} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java new file mode 100644 index 000000000..6cbe2c44e --- /dev/null +++ b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java @@ -0,0 +1,22 @@ +package org.argeo.cms.servlet; + +import java.util.Objects; + +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.auth.HttpResponse; + +public class ServletHttpResponse implements HttpResponse { + private final HttpServletResponse response; + + public ServletHttpResponse(HttpServletResponse response) { + Objects.requireNonNull(response); + this.response = response; + } + + @Override + public void setHeader(String keys, String value) { + response.setHeader(keys, value); + } + +} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java new file mode 100644 index 000000000..5f8c8c560 --- /dev/null +++ b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java @@ -0,0 +1,70 @@ +package org.argeo.cms.servlet.internal; + +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; + +public class HttpUtils { + public final static String HEADER_AUTHORIZATION = "Authorization"; + public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; + + static boolean isBrowser(String userAgent) { + return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") + || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") + || userAgent.contains("opera") || userAgent.contains("browser"); + } + + public static void logResponseHeaders(Log log, HttpServletResponse response) { + if (!log.isDebugEnabled()) + return; + for (String headerName : response.getHeaderNames()) { + Object headerValue = response.getHeader(headerName); + log.debug(headerName + ": " + headerValue); + } + } + + public static void logRequestHeaders(Log log, HttpServletRequest request) { + if (!log.isDebugEnabled()) + return; + for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { + String headerName = headerNames.nextElement(); + Object headerValue = request.getHeader(headerName); + log.debug(headerName + ": " + headerValue); + } + log.debug(request.getRequestURI() + "\n"); + } + + public static void logRequest(Log log, HttpServletRequest request) { + log.debug("contextPath=" + request.getContextPath()); + log.debug("servletPath=" + request.getServletPath()); + log.debug("requestURI=" + request.getRequestURI()); + log.debug("queryString=" + request.getQueryString()); + StringBuilder buf = new StringBuilder(); + // headers + Enumeration en = request.getHeaderNames(); + while (en.hasMoreElements()) { + String header = en.nextElement(); + Enumeration values = request.getHeaders(header); + while (values.hasMoreElements()) + buf.append(" " + header + ": " + values.nextElement()); + buf.append('\n'); + } + + // attributed + Enumeration an = request.getAttributeNames(); + while (an.hasMoreElements()) { + String attr = an.nextElement(); + Object value = request.getAttribute(attr); + buf.append(" " + attr + ": " + value); + buf.append('\n'); + } + log.debug("\n" + buf); + } + + private HttpUtils() { + + } +} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java new file mode 100644 index 000000000..c762b67ec --- /dev/null +++ b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java @@ -0,0 +1,133 @@ +package org.argeo.cms.servlet.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Collection; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.argeo.cms.osgi.PublishNamespace; +import org.argeo.osgi.util.FilterRequirement; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.Version; +import org.osgi.framework.VersionRange; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.framework.wiring.FrameworkWiring; +import org.osgi.resource.Requirement; + +public class PkgServlet extends HttpServlet { + private static final long serialVersionUID = 7660824185145214324L; + + private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String pathInfo = req.getPathInfo(); + + String pkg, versionStr, file; + String[] parts = pathInfo.split("/"); + // first is always empty + if (parts.length == 4) { + pkg = parts[1]; + versionStr = parts[2]; + file = parts[3]; + } else if (parts.length == 3) { + pkg = parts[1]; + versionStr = null; + file = parts[2]; + } else { + throw new IllegalArgumentException("Unsupported path length " + pathInfo); + } + + FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class); + String filter; + if (versionStr == null) { + filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"; + } else { + if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range + VersionRange versionRange = new VersionRange(versionStr); + filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")" + + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")"; + + } else { + Version version = new Version(versionStr); + filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")(" + + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))"; + } + } + Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter); + Collection packages = frameworkWiring.findProviders(requirement); + if (packages.isEmpty()) { + resp.sendError(404); + return; + } + + // TODO verify that it works with multiple versions + SortedMap sorted = new TreeMap<>(); + for (BundleCapability capability : packages) { + sorted.put(capability.getRevision().getVersion(), capability); + } + + Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle(); + String entryPath = '/' + pkg.replace('.', '/') + '/' + file; + URL internalURL = bundle.getResource(entryPath); + if (internalURL == null) { + resp.sendError(404); + return; + } + + // Resource found, we now check whether it can be published + boolean publish = false; + BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); + capabilities: for (BundleCapability bundleCapability : bundleWiring + .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) { + Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG); + if (publishedPkg != null) { + if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) { + Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE); + if (publishedFile == null) { + publish = true; + break capabilities; + } else { + String[] publishedFiles = publishedFile.toString().split(","); + for (String pattern : publishedFiles) { + if (pattern.startsWith("*.")) { + String ext = pattern.substring(1); + if (file.endsWith(ext)) { + publish = true; + break capabilities; + } + } else { + if (publishedFile.equals(file)) { + publish = true; + break capabilities; + } + } + } + } + } + } + } + + if (!publish) { + resp.sendError(404); + return; + } + + try (InputStream in = internalURL.openStream()) { + IOUtils.copy(in, resp.getOutputStream()); + } + } + +} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java new file mode 100644 index 000000000..288ee268c --- /dev/null +++ b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java @@ -0,0 +1,24 @@ +package org.argeo.cms.servlet.internal; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class RobotServlet extends HttpServlet { + private static final long serialVersionUID = 7935661175336419089L; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + PrintWriter writer = response.getWriter(); + writer.append("User-agent: *\n"); + writer.append("Disallow:\n"); + response.setHeader("Content-Type", "text/plain"); + writer.flush(); + } + +} diff --git a/org.argeo.cms.swt/pom.xml b/org.argeo.cms.swt/pom.xml index c6e772c89..b969259e7 100644 --- a/org.argeo.cms.swt/pom.xml +++ b/org.argeo.cms.swt/pom.xml @@ -20,6 +20,11 @@ org.argeo.cms 2.3-SNAPSHOT + + org.argeo.commons + org.argeo.cms.servlet + 2.3-SNAPSHOT + diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java index 8e12a8986..e4d08d728 100644 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java +++ b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java @@ -16,7 +16,7 @@ import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; -import javax.servlet.http.HttpServletRequest; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.api.NodeConstants; @@ -25,6 +25,8 @@ import org.argeo.api.cms.CmsView; import org.argeo.cms.CmsMsg; import org.argeo.cms.LocaleUtils; import org.argeo.cms.auth.HttpRequestCallback; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; import org.argeo.cms.swt.CmsStyles; import org.argeo.cms.swt.CmsSwtUtils; import org.argeo.eclipse.ui.specific.UiContext; @@ -309,8 +311,8 @@ public class CmsLogin implements CmsStyles, CallbackHandler { else if (callback instanceof PasswordCallback && passwordT != null) ((PasswordCallback) callback).setPassword(passwordT.getTextChars()); else if (callback instanceof HttpRequestCallback) { - ((HttpRequestCallback) callback).setRequest(UiContext.getHttpRequest()); - ((HttpRequestCallback) callback).setResponse(UiContext.getHttpResponse()); + ((HttpRequestCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest())); + ((HttpRequestCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse())); } else if (callback instanceof LanguageCallback) { Locale toUse = null; if (localeChoice != null) diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java index 504ed4978..c20068fa7 100644 --- a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java +++ b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java @@ -29,6 +29,8 @@ import org.argeo.cms.CmsException; import org.argeo.cms.auth.CurrentUser; import org.argeo.cms.auth.HttpRequestCallback; import org.argeo.cms.auth.HttpRequestCallbackHandler; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; import org.argeo.cms.swt.CmsStyles; import org.argeo.cms.swt.CmsSwtUtils; import org.argeo.eclipse.ui.specific.UiContext; @@ -84,7 +86,8 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implement LoginContext lc; try { lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, - new HttpRequestCallbackHandler(UiContext.getHttpRequest(), UiContext.getHttpResponse())); + new HttpRequestCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()), + new ServletHttpResponse(UiContext.getHttpResponse()))); lc.login(); } catch (LoginException e) { try { @@ -291,8 +294,10 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implement // handle HTTP context for (Callback callback : callbacks) { if (callback instanceof HttpRequestCallback) { - ((HttpRequestCallback) callback).setRequest(UiContext.getHttpRequest()); - ((HttpRequestCallback) callback).setResponse(UiContext.getHttpResponse()); + ((HttpRequestCallback) callback) + .setRequest(new ServletHttpRequest(UiContext.getHttpRequest())); + ((HttpRequestCallback) callback) + .setResponse(new ServletHttpResponse(UiContext.getHttpResponse())); } } } diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java index 4bdc5a0aa..d7050e954 100644 --- a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java +++ b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java @@ -25,6 +25,8 @@ import org.argeo.cms.LocaleUtils; import org.argeo.cms.auth.CurrentUser; import org.argeo.cms.auth.HttpRequestCallbackHandler; import org.argeo.cms.osgi.CmsOsgiUtils; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; import org.argeo.cms.swt.CmsSwtUtils; import org.argeo.cms.swt.SimpleSwtUxContext; import org.argeo.cms.swt.dialogs.CmsFeedback; @@ -84,7 +86,8 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL LoginContext lc; try { lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, - new HttpRequestCallbackHandler(UiContext.getHttpRequest(), UiContext.getHttpResponse())); + new HttpRequestCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()), + new ServletHttpResponse(UiContext.getHttpResponse()))); lc.login(); } catch (LoginException e) { try { diff --git a/org.argeo.cms/OSGI-INF/pkgServlet.xml b/org.argeo.cms/OSGI-INF/pkgServlet.xml deleted file mode 100644 index 30e52311c..000000000 --- a/org.argeo.cms/OSGI-INF/pkgServlet.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms/OSGI-INF/pkgServletContext.xml b/org.argeo.cms/OSGI-INF/pkgServletContext.xml deleted file mode 100644 index 7540a2cdb..000000000 --- a/org.argeo.cms/OSGI-INF/pkgServletContext.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index dc2cca306..61cb6cbab 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -3,13 +3,8 @@ Bundle-Activator: org.argeo.cms.internal.kernel.Activator Import-Package: org.apache.commons.httpclient.cookie;resolution:=optional,\ !com.sun.security.jgss,\ org.osgi.*;version=0.0.0,\ -org.osgi.service.http.whiteboard;version=0.0.0,\ -org.osgi.framework.namespace;version=0.0.0,\ -org.apache.log4j.*;resolution:=optional,\ * -Service-Component:\ -OSGI-INF/cmsUserManager.xml,\ -OSGI-INF/pkgServletContext.xml,\ -OSGI-INF/pkgServlet.xml +#Service-Component:\ +#OSGI-INF/cmsUserManager.xml,\ diff --git a/org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java index 1d24be7ad..c5d067c08 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java @@ -7,7 +7,6 @@ import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; -import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -49,7 +48,7 @@ public class AnonymousLoginModule implements LoginModule { public boolean commit() throws LoginException { UserAdmin userAdmin = bc.getService(bc.getServiceReference(UserAdmin.class)); Authorization authorization = userAdmin.getAuthorization(null); - HttpServletRequest request = (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST); + HttpRequest request = (HttpRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST); Locale locale = Locale.getDefault(); if (request != null) locale = request.getLocale(); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java index e854f5adf..62888b153 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java @@ -10,8 +10,6 @@ import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; import javax.security.auth.Subject; import javax.security.auth.x500.X500Principal; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import org.argeo.api.NodeConstants; import org.argeo.api.cms.CmsSession; @@ -124,12 +122,12 @@ class CmsAuthUtils { } @SuppressWarnings("unused") - synchronized static void registerSessionAuthorization(HttpServletRequest request, Subject subject, + synchronized static void registerSessionAuthorization(HttpRequest request, Subject subject, Authorization authorization, Locale locale) { // synchronized in order to avoid multiple registrations // TODO move it to a service in order to avoid static synchronization if (request != null) { - HttpSession httpSession = request.getSession(false); + HttpSession httpSession = request.getSession(); assert httpSession != null; String httpSessId = httpSession.getId(); boolean anonymous = authorization.getName() == null; diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpRequest.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpRequest.java new file mode 100644 index 000000000..447225515 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpRequest.java @@ -0,0 +1,25 @@ +package org.argeo.cms.auth; + +import java.util.Locale; + +/** Transitional interface to decouple from the Servlet API. */ +public interface HttpRequest { + HttpSession getSession(); + + HttpSession createSession(); + + Locale getLocale(); + + Object getAttribute(String key); + + void setAttribute(String key, Object object); + + String getHeader(String key); + + String getRemoteAddr(); + + int getLocalPort(); + + int getRemotePort(); + +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallback.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallback.java index 920f04e34..38e12c0c6 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallback.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallback.java @@ -1,29 +1,26 @@ package org.argeo.cms.auth; import javax.security.auth.callback.Callback; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; /** Retrieves credentials from an HTTP request. */ public class HttpRequestCallback implements Callback { - private HttpServletRequest request; - private HttpServletResponse response; + private HttpRequest request; + private HttpResponse response; private HttpSession httpSession; - public HttpServletRequest getRequest() { + public HttpRequest getRequest() { return request; } - public void setRequest(HttpServletRequest request) { + public void setRequest(HttpRequest request) { this.request = request; } - public HttpServletResponse getResponse() { + public HttpResponse getResponse() { return response; } - public void setResponse(HttpServletResponse response) { + public void setResponse(HttpResponse response) { this.response = response; } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallbackHandler.java index df971e687..934fdd96b 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallbackHandler.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallbackHandler.java @@ -6,22 +6,19 @@ import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.LanguageCallback; import javax.security.auth.callback.UnsupportedCallbackException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; /** * Callback handler populating {@link HttpRequestCallback}s with the provided * {@link HttpServletRequest}, and ignoring any other callback. */ public class HttpRequestCallbackHandler implements CallbackHandler { - final private HttpServletRequest request; - final private HttpServletResponse response; + final private HttpRequest request; + final private HttpResponse response; final private HttpSession httpSession; - public HttpRequestCallbackHandler(HttpServletRequest request, HttpServletResponse response) { + public HttpRequestCallbackHandler(HttpRequest request, HttpResponse response) { this.request = request; - this.httpSession = request.getSession(false); + this.httpSession = request.getSession(); this.response = response; } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpResponse.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpResponse.java new file mode 100644 index 000000000..67bad55c4 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpResponse.java @@ -0,0 +1,7 @@ +package org.argeo.cms.auth; + +/** Transitional interface to decouple from the Servlet API. */ +public interface HttpResponse { + void setHeader(String keys, String value); + +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpSession.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpSession.java new file mode 100644 index 000000000..c7e52c128 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpSession.java @@ -0,0 +1,8 @@ +package org.argeo.cms.auth; + +/** Transitional interface to decouple from the Servlet API. */ +public interface HttpSession { + boolean isValid(); + + String getId(); +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java index c2dfead78..8cc3941bc 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java @@ -13,9 +13,6 @@ import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -34,8 +31,8 @@ public class HttpSessionLoginModule implements LoginModule { private CallbackHandler callbackHandler = null; private Map sharedState = null; - private HttpServletRequest request = null; - private HttpServletResponse response = null; + private HttpRequest request = null; + private HttpResponse response = null; private BundleContext bc; @@ -72,8 +69,8 @@ public class HttpSessionLoginModule implements LoginModule { return false; // TODO factorize with below String httpSessionId = httpSession.getId(); - if (log.isTraceEnabled()) - log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId); +// if (log.isTraceEnabled()) +// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId); CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId); if (cmsSession != null) { authorization = cmsSession.getAuthorization(); @@ -84,16 +81,16 @@ public class HttpSessionLoginModule implements LoginModule { } else { authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION); if (authorization == null) {// search by session ID - HttpSession httpSession = request.getSession(false); + HttpSession httpSession = request.getSession(); if (httpSession == null) { // TODO make sure this is always safe if (log.isTraceEnabled()) log.trace("Create http session"); - httpSession = request.getSession(true); + httpSession = request.createSession(); } String httpSessionId = httpSession.getId(); - if (log.isTraceEnabled()) - log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId); +// if (log.isTraceEnabled()) +// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId); CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId); if (cmsSession != null) { authorization = cmsSession.getAuthorization(); @@ -159,7 +156,7 @@ public class HttpSessionLoginModule implements LoginModule { return true; } - private void extractHttpAuth(final HttpServletRequest httpRequest) { + private void extractHttpAuth(final HttpRequest httpRequest) { String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION); extractHttpAuth(authHeader); } @@ -206,7 +203,7 @@ public class HttpSessionLoginModule implements LoginModule { // } } - private void extractClientCertificate(HttpServletRequest req) { + private void extractClientCertificate(HttpRequest req) { X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate"); if (null != certs && certs.length > 0) {// Servlet container verified the client certificate String certDn = certs[0].getSubjectX500Principal().getName(); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java index ec6b5a30c..f5e4085c3 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java @@ -9,7 +9,6 @@ import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; -import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -43,7 +42,7 @@ public class IdentLoginModule implements LoginModule { } catch (UnsupportedCallbackException e) { return false; } - HttpServletRequest request = httpCallback.getRequest(); + HttpRequest request = httpCallback.getRequest(); if (request == null) return false; IdentClient identClient = Activator.getIdentClient(request.getRemoteAddr()); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java index 240564f9e..5d46839e0 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java @@ -12,7 +12,6 @@ import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import javax.security.auth.x500.X500Principal; -import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -68,7 +67,7 @@ public class SingleUserLoginModule implements LoginModule { authorizationName = principal.getName(); } - HttpServletRequest request = (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST); + HttpRequest request = (HttpRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST); Locale locale = Locale.getDefault(); if (request != null) locale = request.getLocale(); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java index 27de54be3..568e2f6e0 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java @@ -132,4 +132,8 @@ public class SpnegoLoginModule implements LoginModule { return null; } + + public static boolean hasAcceptorCredentials() { + return Activator.getAcceptorCredentials() != null; + } } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java index 092a06b77..d526f4fc2 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -23,7 +23,6 @@ import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.auth.login.CredentialNotFoundException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; -import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -253,7 +252,7 @@ public class UserAdminLoginModule implements LoginModule { } // Log and monitor new login - HttpServletRequest request = (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST); + HttpRequest request = (HttpRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST); CmsAuthUtils.addAuthorization(subject, authorization); // Unlock keyring (underlying login to the JCR repository) diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java index 5753decf9..5485fc5ee 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java @@ -62,7 +62,7 @@ public class CmsUserManagerImpl implements CmsUserManager { // private Map serviceProperties; private WorkTransaction userTransaction; - private Map> userDirectories = Collections + private Map> userDirectories = Collections .synchronizedMap(new LinkedHashMap<>()); @Override @@ -481,11 +481,11 @@ public class CmsUserManagerImpl implements CmsUserManager { this.userTransaction = userTransaction; } - public void addUserDirectory(UserDirectory userDirectory, Map properties) { + public void addUserDirectory(UserDirectory userDirectory, Map properties) { userDirectories.put(userDirectory, new Hashtable<>(properties)); } - public void removeUserDirectory(UserDirectory userDirectory, Map properties) { + public void removeUserDirectory(UserDirectory userDirectory, Map properties) { userDirectories.remove(userDirectory); } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/HttpUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/http/HttpUtils.java deleted file mode 100644 index b5d355387..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/HttpUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.argeo.cms.internal.http; - -import java.util.Enumeration; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.logging.Log; - -public class HttpUtils { - public final static String HEADER_AUTHORIZATION = "Authorization"; - public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - - static boolean isBrowser(String userAgent) { - return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") - || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") - || userAgent.contains("opera") || userAgent.contains("browser"); - } - - public static void logResponseHeaders(Log log, HttpServletResponse response) { - if (!log.isDebugEnabled()) - return; - for (String headerName : response.getHeaderNames()) { - Object headerValue = response.getHeader(headerName); - log.debug(headerName + ": " + headerValue); - } - } - - public static void logRequestHeaders(Log log, HttpServletRequest request) { - if (!log.isDebugEnabled()) - return; - for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { - String headerName = headerNames.nextElement(); - Object headerValue = request.getHeader(headerName); - log.debug(headerName + ": " + headerValue); - } - log.debug(request.getRequestURI() + "\n"); - } - - public static void logRequest(Log log, HttpServletRequest request) { - log.debug("contextPath=" + request.getContextPath()); - log.debug("servletPath=" + request.getServletPath()); - log.debug("requestURI=" + request.getRequestURI()); - log.debug("queryString=" + request.getQueryString()); - StringBuilder buf = new StringBuilder(); - // headers - Enumeration en = request.getHeaderNames(); - while (en.hasMoreElements()) { - String header = en.nextElement(); - Enumeration values = request.getHeaders(header); - while (values.hasMoreElements()) - buf.append(" " + header + ": " + values.nextElement()); - buf.append('\n'); - } - - // attributed - Enumeration an = request.getAttributeNames(); - while (an.hasMoreElements()) { - String attr = an.nextElement(); - Object value = request.getAttribute(attr); - buf.append(" " + attr + ": " + value); - buf.append('\n'); - } - log.debug("\n" + buf); - } - - private HttpUtils() { - - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java deleted file mode 100644 index 26046b246..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.argeo.cms.internal.http; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Collection; -import java.util.SortedMap; -import java.util.TreeMap; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.osgi.PublishNamespace; -import org.argeo.osgi.util.FilterRequirement; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.Version; -import org.osgi.framework.VersionRange; -import org.osgi.framework.namespace.PackageNamespace; -import org.osgi.framework.wiring.BundleCapability; -import org.osgi.framework.wiring.BundleWiring; -import org.osgi.framework.wiring.FrameworkWiring; -import org.osgi.resource.Requirement; - -public class PkgServlet extends HttpServlet { - private static final long serialVersionUID = 7660824185145214324L; - - private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext(); - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String pathInfo = req.getPathInfo(); - - String pkg, versionStr, file; - String[] parts = pathInfo.split("/"); - // first is always empty - if (parts.length == 4) { - pkg = parts[1]; - versionStr = parts[2]; - file = parts[3]; - } else if (parts.length == 3) { - pkg = parts[1]; - versionStr = null; - file = parts[2]; - } else { - throw new IllegalArgumentException("Unsupported path length " + pathInfo); - } - - FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class); - String filter; - if (versionStr == null) { - filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"; - } else { - if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range - VersionRange versionRange = new VersionRange(versionStr); - filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")" - + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")"; - - } else { - Version version = new Version(versionStr); - filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")(" - + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))"; - } - } - Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter); - Collection packages = frameworkWiring.findProviders(requirement); - if (packages.isEmpty()) { - resp.sendError(404); - return; - } - - // TODO verify that it works with multiple versions - SortedMap sorted = new TreeMap<>(); - for (BundleCapability capability : packages) { - sorted.put(capability.getRevision().getVersion(), capability); - } - - Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle(); - String entryPath = '/' + pkg.replace('.', '/') + '/' + file; - URL internalURL = bundle.getResource(entryPath); - if (internalURL == null) { - resp.sendError(404); - return; - } - - // Resource found, we now check whether it can be published - boolean publish = false; - BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); - capabilities: for (BundleCapability bundleCapability : bundleWiring - .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) { - Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG); - if (publishedPkg != null) { - if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) { - Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE); - if (publishedFile == null) { - publish = true; - break capabilities; - } else { - String[] publishedFiles = publishedFile.toString().split(","); - for (String pattern : publishedFiles) { - if (pattern.startsWith("*.")) { - String ext = pattern.substring(1); - if (file.endsWith(ext)) { - publish = true; - break capabilities; - } - } else { - if (publishedFile.equals(file)) { - publish = true; - break capabilities; - } - } - } - } - } - } - } - - if (!publish) { - resp.sendError(404); - return; - } - - try (InputStream in = internalURL.openStream()) { - IOUtils.copy(in, resp.getOutputStream()); - } - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/RobotServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/RobotServlet.java deleted file mode 100644 index 6d3d302b7..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/RobotServlet.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.argeo.cms.internal.http; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class RobotServlet extends HttpServlet { - private static final long serialVersionUID = 7935661175336419089L; - - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - PrintWriter writer = response.getWriter(); - writer.append("User-agent: *\n"); - writer.append("Disallow:\n"); - response.setHeader("Content-Type", "text/plain"); - writer.flush(); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java index ce8190518..03fb82faa 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java @@ -3,9 +3,9 @@ package org.argeo.cms.internal.http; import java.util.Locale; import javax.security.auth.Subject; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; +import org.argeo.cms.auth.HttpRequest; +import org.argeo.cms.auth.HttpSession; import org.argeo.cms.internal.auth.CmsSessionImpl; import org.osgi.service.useradmin.Authorization; @@ -15,24 +15,19 @@ public class WebCmsSessionImpl extends CmsSessionImpl { private HttpSession httpSession; public WebCmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale, - HttpServletRequest request) { - super(initialSubject, authorization, locale, request.getSession(false).getId()); - httpSession = request.getSession(false); + HttpRequest request) { + super(initialSubject, authorization, locale, request.getSession().getId()); + httpSession = request.getSession(); } @Override public boolean isValid() { if (isClosed()) return false; - try {// test http session - httpSession.getCreationTime(); - return true; - } catch (IllegalStateException ise) { - return false; - } + return httpSession.isValid(); } - public static CmsSessionImpl getCmsSession(HttpServletRequest request) { - return CmsSessionImpl.getByLocalId(request.getSession(false).getId()); + public static CmsSessionImpl getCmsSession(HttpRequest request) { + return CmsSessionImpl.getByLocalId(request.getSession().getId()); } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java index 86c6c9c31..11efa9e0f 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java @@ -34,10 +34,13 @@ import org.apache.commons.httpclient.params.HttpParams; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.api.NodeConstants; +import org.argeo.cms.CmsUserManager; +import org.argeo.cms.internal.auth.CmsUserManagerImpl; import org.argeo.cms.internal.http.client.HttpCredentialProvider; import org.argeo.cms.internal.http.client.SpnegoAuthScheme; import org.argeo.naming.DnsBrowser; import org.argeo.osgi.transaction.WorkControl; +import org.argeo.osgi.transaction.WorkTransaction; import org.argeo.osgi.useradmin.AbstractUserDirectory; import org.argeo.osgi.useradmin.AggregatingUserAdmin; import org.argeo.osgi.useradmin.LdapUserAdmin; @@ -52,6 +55,7 @@ import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedServiceFactory; import org.osgi.service.useradmin.Authorization; @@ -82,11 +86,25 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor private boolean singleUser = false; // private boolean systemRolesAvailable = false; + CmsUserManagerImpl userManager; + public NodeUserAdmin(String systemRolesBaseDn, String tokensBaseDn) { super(systemRolesBaseDn, tokensBaseDn); BundleContext bc = Activator.getBundleContext(); if (bc != null) { - tmTracker = new ServiceTracker<>(bc, WorkControl.class, null); + tmTracker = new ServiceTracker<>(bc, WorkControl.class, null) { + + @Override + public WorkControl addingService(ServiceReference reference) { + WorkControl workControl = super.addingService(reference); + userManager = new CmsUserManagerImpl(); + userManager.setUserAdmin(NodeUserAdmin.this); + // FIXME make it more robust + userManager.setUserTransaction((WorkTransaction) workControl); + bc.registerService(CmsUserManager.class, userManager, null); + return workControl; + } + }; tmTracker.open(); } else { tmTracker = null; @@ -128,7 +146,7 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor // OSGi LdapName baseDn = userDirectory.getBaseDn(); - Dictionary regProps = new Hashtable<>(); + Hashtable regProps = new Hashtable<>(); regProps.put(Constants.SERVICE_PID, pid); if (isSystemRolesBaseDn(baseDn)) regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); @@ -136,6 +154,7 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor // ServiceRegistration reg = // bc.registerService(UserDirectory.class, userDirectory, regProps); Activator.registerService(UserDirectory.class, userDirectory, regProps); + userManager.addUserDirectory(userDirectory, regProps); pidToBaseDn.put(pid, baseDn); // pidToServiceRegs.put(pid, reg); diff --git a/org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java deleted file mode 100644 index c88ee7f93..000000000 --- a/org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.argeo.cms.servlet; - -import java.io.IOException; -import java.net.URL; -import java.security.PrivilegedAction; -import java.util.Map; - -import javax.security.auth.Subject; -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.argeo.cms.internal.http.HttpUtils; -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 session is not - * pre-authenticated. - */ -public class CmsServletContext extends ServletContextHelper { - private final static Log log = LogFactory.getLog(CmsServletContext.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 (log.isTraceEnabled()) - HttpUtils.logResponseHeaders(log, response); - if (lc == null) - return false; - } - - Subject subject = lc.getSubject(); - //log.debug("SERVLET CONTEXT: "+subject); - Subject.doAs(subject, new PrivilegedAction() { - - @Override - public Void run() { - // TODO also set login context in order to log out ? - ServletAuthUtils.configureRequestSecurity(request); - return null; - } - - }); - return true; - } - - @Override - public void finishSecurity(HttpServletRequest request, HttpServletResponse response) { - ServletAuthUtils.clearRequestSecurity(request); - } - - 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/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java deleted file mode 100644 index e4547507a..000000000 --- a/org.argeo.cms/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.argeo.cms.servlet; - -import javax.security.auth.login.LoginContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.cms.internal.http.HttpUtils; - -/** Servlet context forcing authentication. */ -public class PrivateWwwAuthServletContext extends CmsServletContext { - // TODO make it configurable - private final String httpAuthRealm = "Argeo"; - private final boolean forceBasic = false; - - @Override - protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { - askForWwwAuth(request, response); - return null; - } - - protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) { - // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic - // realm=\"" + httpAuthRealm + "\""); - if (org.argeo.cms.internal.kernel.Activator.getAcceptorCredentials() != null && !forceBasic)// SPNEGO - response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Negotiate"); - else - response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Basic realm=\"" + httpAuthRealm + "\""); - - // response.setDateHeader("Date", System.currentTimeMillis()); - // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * - // 60 * 60 * 1000)); - // response.setHeader("Accept-Ranges", "bytes"); - // response.setHeader("Connection", "Keep-Alive"); - // response.setHeader("Keep-Alive", "timeout=5, max=97"); - // response.setContentType("text/html; charset=UTF-8"); - response.setStatus(401); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/servlet/ServletAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/servlet/ServletAuthUtils.java deleted file mode 100644 index 67db467ce..000000000 --- a/org.argeo.cms/src/org/argeo/cms/servlet/ServletAuthUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.argeo.cms.servlet; - -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.function.Supplier; - -import javax.security.auth.Subject; -import javax.servlet.http.HttpServletRequest; - -import org.argeo.api.cms.CmsSession; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.osgi.CmsOsgiUtils; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.service.http.HttpContext; - -/** Authentications utilities when using servlets. */ -public class ServletAuthUtils { - private static BundleContext bundleContext = FrameworkUtil.getBundle(ServletAuthUtils.class).getBundleContext(); - - /** - * Execute this supplier, using the CMS class loader as context classloader. - * Useful to log in to JCR. - */ - public final static T doAs(Supplier supplier, HttpServletRequest req) { - ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(ServletAuthUtils.class.getClassLoader()); - try { - return Subject.doAs( - Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())), - new PrivilegedAction() { - - @Override - public T run() { - return supplier.get(); - } - - }); - } finally { - Thread.currentThread().setContextClassLoader(currentContextCl); - } - } - - public final static void configureRequestSecurity(HttpServletRequest req) { - if (req.getAttribute(AccessControlContext.class.getName()) != null) - throw new IllegalStateException("Request already authenticated."); - AccessControlContext acc = AccessController.getContext(); - req.setAttribute(HttpContext.REMOTE_USER, CurrentUser.getUsername()); - req.setAttribute(AccessControlContext.class.getName(), acc); - } - - public final static void clearRequestSecurity(HttpServletRequest req) { - if (req.getAttribute(AccessControlContext.class.getName()) == null) - throw new IllegalStateException("Cannot clear non-authenticated request."); - req.setAttribute(HttpContext.REMOTE_USER, null); - req.setAttribute(AccessControlContext.class.getName(), null); - } - - public static CmsSession getCmsSession(HttpServletRequest req) { - Subject subject = Subject - .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())); - CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, subject); - return cmsSession; - } -} diff --git a/pom.xml b/pom.xml index bb8807177..123fb1c9c 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ org.argeo.transition org.argeo.cms + org.argeo.cms.servlet org.argeo.cms.swt org.argeo.cms.jcr