Start supporting JDK HTTP server API
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 17 Jul 2022 13:32:57 +0000 (15:32 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 17 Jul 2022 13:32:57 +0000 (15:32 +0200)
24 files changed:
org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCli.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/StaticCmsLaunch.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java [new file with mode: 0644]
org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java
org.argeo.cms.lib.jetty/build.properties
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java [new file with mode: 0644]
org.argeo.cms/bnd.bnd
org.argeo.cms/build.properties
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteCmsSessionImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/client/HttpCredentialProvider.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoCredentials.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java
org.argeo.cms/src/org/argeo/cms/runtime/CmsCli.java [deleted file]
org.argeo.cms/src/org/argeo/cms/runtime/StaticCmsLaunch.java [deleted file]
org.argeo.util/src/org/argeo/util/dav/DavServerHandler.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/http/HttpServerUtils.java [new file with mode: 0644]

diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCli.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCli.java
new file mode 100644 (file)
index 0000000..516c768
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.cms.cli;
+
+import org.argeo.api.cli.CommandsCli;
+
+public class CmsCli extends CommandsCli {
+
+       public CmsCli(String commandName) {
+               super(commandName);
+               addCommand("launch", new StaticCmsLaunch());
+       }
+
+       @Override
+       public String getDescription() {
+               return "Static CMS utilities.";
+       }
+
+       public static void main(String[] args) {
+               mainImpl(new CmsCli("cms"), args);
+       }
+
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/StaticCmsLaunch.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/StaticCmsLaunch.java
new file mode 100644 (file)
index 0000000..9b53598
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.cms.cli;
+
+import java.lang.management.ManagementFactory;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.runtime.StaticCms;
+
+public class StaticCmsLaunch implements DescribedCommand<String> {
+       private Option dataOption;
+
+       @Override
+       public Options getOptions() {
+               Options options = new Options();
+               dataOption = Option.builder().longOpt("data").hasArg().required()
+                               .desc("path to the writable data area (mandatory)").build();
+               options.addOption(dataOption);
+               return options;
+       }
+
+       @Override
+       public String apply(List<String> args) {
+               CommandLine cl = toCommandLine(args);
+               String dataPath = cl.getOptionValue(dataOption);
+
+               Path instancePath = Paths.get(dataPath);
+               System.setProperty("osgi.instance.area", instancePath.toUri().toString());
+               System.setProperty("argeo.http.port", "0");
+
+               StaticCms staticCms = new StaticCms();
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown"));
+               staticCms.start();
+
+               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+               System.out.println("Static CMS available in " + jvmUptime + " ms.");
+
+               staticCms.waitForStop();
+
+               return null;
+       }
+
+       @Override
+       public String getDescription() {
+               return "Launch a static CMS";
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java
new file mode 100644 (file)
index 0000000..c81bad7
--- /dev/null
@@ -0,0 +1,50 @@
+package org.argeo.cms.servlet.httpserver;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpPrincipal;
+
+public class HttpContextServlet extends HttpServlet {
+       private static final long serialVersionUID = 2321612280413662738L;
+
+       private final HttpContext httpContext;
+
+       public HttpContextServlet(HttpContext httpContext) {
+               this.httpContext = httpContext;
+       }
+
+       @Override
+       protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               try (ServletHttpExchange httpExchange = new ServletHttpExchange(httpContext, req, resp)) {
+                       Authenticator authenticator = httpContext.getAuthenticator();
+                       if (authenticator != null) {
+                               Authenticator.Result authenticationResult = authenticator.authenticate(httpExchange);
+                               if (authenticationResult instanceof Authenticator.Success) {
+                                       HttpPrincipal httpPrincipal = ((Authenticator.Success) authenticationResult).getPrincipal();
+                                       httpExchange.setPrincipal(httpPrincipal);
+                               } else if (authenticationResult instanceof Authenticator.Retry) {
+                                       resp.setStatus(((Authenticator.Retry) authenticationResult).getResponseCode());
+                                       return;
+                               } else if (authenticationResult instanceof Authenticator.Failure) {
+                                       resp.setStatus(((Authenticator.Failure) authenticationResult).getResponseCode());
+                                       return;
+                               } else {
+                                       throw new UnsupportedOperationException(
+                                                       "Authentication result " + authenticationResult.getClass().getName() + " is not supported");
+                               }
+                       }
+
+                       HttpHandler httpHandler = httpContext.getHandler();
+                       httpHandler.handle(httpExchange);
+               }
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java
new file mode 100644 (file)
index 0000000..5a29fbe
--- /dev/null
@@ -0,0 +1,187 @@
+package org.argeo.cms.servlet.httpserver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.net.ssl.SSLSession;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpPrincipal;
+import com.sun.net.httpserver.HttpsExchange;
+
+public class ServletHttpExchange extends HttpsExchange {
+       private final HttpContext httpContext;
+       private final HttpServletRequest httpServletRequest;
+       private final HttpServletResponse httpServletResponse;
+
+       private final Headers requestHeaders;
+       private final Headers responseHeaders;
+
+       private InputStream filteredIn;
+       private OutputStream filteredOut;
+
+       private HttpPrincipal principal;
+
+       public ServletHttpExchange(HttpContext httpContext, HttpServletRequest httpServletRequest,
+                       HttpServletResponse httpServletResponse) {
+               this.httpContext = httpContext;
+               this.httpServletRequest = httpServletRequest;
+               this.httpServletResponse = httpServletResponse;
+
+               // request headers
+               requestHeaders = new Headers();
+               for (Enumeration<String> headerNames = httpServletRequest.getHeaderNames(); headerNames.hasMoreElements();) {
+                       String headerName = headerNames.nextElement();
+                       List<String> values = new ArrayList<>();
+                       for (Enumeration<String> headerValues = httpServletRequest.getHeaders(headerName); headerValues
+                                       .hasMoreElements();)
+                               values.add(headerValues.nextElement());
+                       requestHeaders.put(headerName, values);
+               }
+
+               responseHeaders = new Headers();
+       }
+
+       @Override
+       public SSLSession getSSLSession() {
+               Object obj = httpServletRequest.getAttribute("javax.net.ssl.session");
+               if (obj == null || !(obj instanceof SSLSession))
+                       throw new IllegalStateException("SSL session not found");
+               return (SSLSession) obj;
+       }
+
+       @Override
+       public Headers getRequestHeaders() {
+               return requestHeaders;
+       }
+
+       @Override
+       public Headers getResponseHeaders() {
+               return responseHeaders;
+       }
+
+       @Override
+       public URI getRequestURI() {
+               return URI.create(httpServletRequest.getRequestURI());
+       }
+
+       @Override
+       public String getRequestMethod() {
+               return httpServletRequest.getMethod();
+       }
+
+       @Override
+       public HttpContext getHttpContext() {
+               return httpContext;
+       }
+
+       @Override
+       public void close() {
+               try {
+                       httpServletRequest.getInputStream().close();
+               } catch (IOException e) {
+                       // TODO use proper logging
+                       e.printStackTrace();
+               }
+               try {
+                       httpServletResponse.getOutputStream().close();
+               } catch (IOException e) {
+                       // TODO use proper logging
+                       e.printStackTrace();
+               }
+
+       }
+
+       @Override
+       public InputStream getRequestBody() {
+               try {
+                       if (filteredIn != null)
+                               return filteredIn;
+                       else
+                               return httpServletRequest.getInputStream();
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot get request body", e);
+               }
+       }
+
+       @Override
+       public OutputStream getResponseBody() {
+               try {
+                       if (filteredOut != null)
+                               return filteredOut;
+                       else
+                               return httpServletResponse.getOutputStream();
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot get response body", e);
+               }
+       }
+
+       @Override
+       public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
+               for (String headerName : responseHeaders.keySet()) {
+                       for (String headerValue : responseHeaders.get(headerName)) {
+                               httpServletResponse.addHeader(headerName, headerValue);
+                       }
+               }
+               // TODO deal with content length etc.
+               httpServletResponse.setStatus(rCode);
+       }
+
+       @Override
+       public InetSocketAddress getRemoteAddress() {
+               return new InetSocketAddress(httpServletRequest.getRemoteHost(), httpServletRequest.getRemotePort());
+       }
+
+       @Override
+       public int getResponseCode() {
+               return httpServletResponse.getStatus();
+       }
+
+       @Override
+       public InetSocketAddress getLocalAddress() {
+               return new InetSocketAddress(httpServletRequest.getLocalName(), httpServletRequest.getLocalPort());
+       }
+
+       @Override
+       public String getProtocol() {
+               return httpServletRequest.getProtocol();
+       }
+
+       @Override
+       public Object getAttribute(String name) {
+               return httpServletRequest.getAttribute(name);
+       }
+
+       @Override
+       public void setAttribute(String name, Object value) {
+               httpServletRequest.setAttribute(name, value);
+       }
+
+       @Override
+       public void setStreams(InputStream i, OutputStream o) {
+               if (i != null)
+                       filteredIn = i;
+               if (o != null)
+                       filteredOut = o;
+
+       }
+
+       @Override
+       public HttpPrincipal getPrincipal() {
+               return principal;
+       }
+
+       void setPrincipal(HttpPrincipal principal) {
+               this.principal = principal;
+       }
+
+}
index d73bdfd1b327e525d57dbe3a151023fa77d889d1..1b1b429615134f522c4a774b2b9f3dc69ce25365 100644 (file)
@@ -25,7 +25,7 @@ public class EquinoxJettyServer extends CmsJettyServer {
        private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
 
        @Override
-       protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException {
+       protected void addServlets(ServletContextHandler rootContextHandler) throws ServletException {
                ServletHolder holder = new ServletHolder(new InternalHttpServiceServlet());
                holder.setInitOrder(0);
                holder.setInitParameter(Constants.SERVICE_VENDOR, "Eclipse.org"); //$NON-NLS-1$
@@ -33,10 +33,10 @@ public class EquinoxJettyServer extends CmsJettyServer {
 
                // holder.setInitParameter(JettyConstants.CONTEXT_PATH,
                // httpContext.getContextPath());
-               servletContextHandler.addServlet(holder, "/*");
+               rootContextHandler.addServlet(holder, "/*");
 
                // post-start
-               SessionHandler sessionManager = servletContextHandler.getSessionHandler();
+               SessionHandler sessionManager = rootContextHandler.getSessionHandler();
                sessionManager.addEventListener((HttpSessionIdListener) holder.getServlet());
        }
 
index 34d2e4d2dad529ceaeb953bfcdb63c51d69ffed2..62f58d95a0244aa9c2b000186e43df7c0fde6314 100644 (file)
@@ -2,3 +2,7 @@ source.. = src/
 output.. = bin/
 bin.includes = META-INF/,\
                .
+additional.bundles = org.eclipse.jetty.io,\
+                     org.eclipse.jetty.security,\
+                     org.argeo.ext.slf4j,\
+                     org.slf4j.api
index 39fc66d9d8218d673317e00534288b627e19b2cc..7f33beeb695d45110be5dd6550fe7a952ad02f7a 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.cms.jetty;
 
+import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 
@@ -9,189 +10,71 @@ import javax.websocket.DeploymentException;
 import javax.websocket.server.ServerContainer;
 import javax.websocket.server.ServerEndpointConfig;
 
-import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
 import org.argeo.cms.CmsDeployProperty;
 import org.argeo.cms.websocket.javax.server.CmsWebSocketConfigurator;
 import org.argeo.cms.websocket.javax.server.TestEndpoint;
-import org.eclipse.jetty.http.UriCompliance;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.server.HttpConnectionFactory;
-import org.eclipse.jetty.server.SecureRequestCustomizer;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.SslConnectionFactory;
 import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-
 import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
 import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
 
-public class CmsJettyServer {
-       private static final CmsLog log = CmsLog.getLog(CmsJettyServer.class);
-
-       private static final int DEFAULT_IDLE_TIMEOUT = 30000;
+public class CmsJettyServer extends JettyHttpServer {
        private static final String CONTEXT_TEMPDIR = "javax.servlet.context.tempdir";
-
        // Equinox compatibility
        private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
+//     private static final CmsLog log = CmsLog.getLog(CmsJettyServer.class);
 
-       private Server server;
+//     private Server server;
+//     private Path tempDir;
+//
+//     private ServerConnector httpConnector;
+//     private ServerConnector httpsConnector;
        private Path tempDir;
 
-       private ServerConnector httpConnector;
-       private ServerConnector httpsConnector;
-
        // WebSocket
        private ServerContainer wsServerContainer;
        private ServerEndpointConfig.Configurator wsEndpointConfigurator;
 
        private CmsState cmsState;
 
+       protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException {
+       }
+
+       @Override
        public void start() {
                try {
                        tempDir = Files.createTempDirectory("jetty");
-
-                       server = new Server(new QueuedThreadPool(10, 1));
-
-                       configure();
-                       // context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint);
-                       // Required to serve rwt-resources. It is important that this is last.
-//             ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
-//             context.addServlet(holderPwd, "/");
-
-                       if (httpConnector != null) {
-                               httpConnector.open();
-                               server.addConnector(httpConnector);
-                       }
-
-                       if (httpsConnector != null) {
-                               httpsConnector.open();
-                               server.addConnector(httpsConnector);
-                       }
-
-                       // holder
-
-                       // context
-                       ServletContextHandler httpContext = createHttpContext();
-                       // httpContext.addServlet(holder, "/*");
-                       addServlets(httpContext);
-                       enableWebSocket(httpContext);
-                       server.setHandler(httpContext);
-
-                       //
-                       // START
-                       server.start();
-                       //
-
-                       Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
-
-                       log.info(httpPortsMsg());
-               } catch (Exception e) {
-                       throw new IllegalStateException("Cannot start Jetty HTTPS server", e);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot create temp dir", e);
                }
+               super.start();
        }
 
-       protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException {
-       }
+       protected ServletContextHandler createRootContextHandler() {
+               ServletContextHandler servletContextHandler = new ServletContextHandler();
+               servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER,
+                               Thread.currentThread().getContextClassLoader());
+               servletContextHandler.setClassLoader(this.getClass().getClassLoader());
+               servletContextHandler.setContextPath("/");
 
-       public Integer getHttpPort() {
-               if (httpConnector == null)
-                       return null;
-               return httpConnector.getLocalPort();
-       }
-
-       public Integer getHttpsPort() {
-               if (httpsConnector == null)
-                       return null;
-               return httpsConnector.getLocalPort();
-       }
-
-       public void stop() {
-               try {
-                       // serverConnector.close();
-                       server.stop();
-                       // TODO delete temp dir
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
+               servletContextHandler.setAttribute(CONTEXT_TEMPDIR, tempDir.toAbsolutePath().toFile());
+               SessionHandler handler = new SessionHandler();
+               handler.setMaxInactiveInterval(-1);
+               servletContextHandler.setSessionHandler(handler);
 
+               return servletContextHandler;
        }
 
-       protected void configure() {
-               HttpConfiguration http_config = new HttpConfiguration();
-
-               String httpPortStr = getFrameworkProp(CmsDeployProperty.HTTP_PORT);
-               String httpsPortStr = getFrameworkProp(CmsDeployProperty.HTTPS_PORT);
-
-               /// TODO make it more generic
-               String httpHost = getFrameworkProp(CmsDeployProperty.HOST);
-//             String httpsHost = getFrameworkProp(
-//                             JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
-
-               // try {
-               if (httpPortStr != null || httpsPortStr != null) {
-                       boolean httpEnabled = httpPortStr != null;
-                       // props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
-                       boolean httpsEnabled = httpsPortStr != null;
-                       // props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
-                       if (httpsEnabled) {
-                               int httpsPort = Integer.parseInt(httpsPortStr);
-                               http_config.setSecureScheme("https");
-                               http_config.setSecurePort(httpsPort);
-                       }
-
-                       if (httpEnabled) {
-                               int httpPort = Integer.parseInt(httpPortStr);
-                               httpConnector = new ServerConnector(server, new HttpConnectionFactory(http_config));
-                               httpConnector.setPort(httpPort);
-                               httpConnector.setHost(httpHost);
-                               httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT);
-                       }
-
-                       if (httpsEnabled) {
-
-                               SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
-                               // sslContextFactory.setKeyStore(KeyS)
-
-                               sslContextFactory.setKeyStoreType(getFrameworkProp(CmsDeployProperty.SSL_KEYSTORETYPE));
-                               sslContextFactory.setKeyStorePath(getFrameworkProp(CmsDeployProperty.SSL_KEYSTORE));
-                               sslContextFactory.setKeyStorePassword(getFrameworkProp(CmsDeployProperty.SSL_PASSWORD));
-                               // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
-                               sslContextFactory.setProtocol("TLS");
-
-                               sslContextFactory.setTrustStoreType(getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORETYPE));
-                               sslContextFactory.setTrustStorePath(getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORE));
-                               sslContextFactory.setTrustStorePassword(getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
-
-                               String wantClientAuth = getFrameworkProp(CmsDeployProperty.SSL_WANTCLIENTAUTH);
-                               if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true)))
-                                       sslContextFactory.setWantClientAuth(true);
-                               String needClientAuth = getFrameworkProp(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
-                               if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true)))
-                                       sslContextFactory.setNeedClientAuth(true);
-
-                               // HTTPS Configuration
-                               HttpConfiguration https_config = new HttpConfiguration(http_config);
-                               https_config.addCustomizer(new SecureRequestCustomizer());
-                               https_config.setUriCompliance(UriCompliance.LEGACY);
-
-                               // HTTPS connector
-                               httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
-                                               new HttpConnectionFactory(https_config));
-                               int httpsPort = Integer.parseInt(httpsPortStr);
-                               httpsConnector.setPort(httpsPort);
-                               httpsConnector.setHost(httpHost);
-                       }
-
-               }
+       @Override
+       protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
+               addServlets(servletContextHandler);
+               enableWebSocket(servletContextHandler);
 
        }
 
        protected void enableWebSocket(ServletContextHandler servletContextHandler) {
-               String webSocketEnabled = getFrameworkProp(CmsDeployProperty.WEBSOCKET_ENABLED);
+               String webSocketEnabled = getDeployProperty(CmsDeployProperty.WEBSOCKET_ENABLED);
                // web socket
                if (webSocketEnabled != null && webSocketEnabled.equals(Boolean.toString(true))) {
                        JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
@@ -216,27 +99,7 @@ public class CmsJettyServer {
                }
        }
 
-       protected ServletContextHandler createHttpContext() {
-               ServletContextHandler httpContext = new ServletContextHandler();
-               httpContext.setAttribute(INTERNAL_CONTEXT_CLASSLOADER, Thread.currentThread().getContextClassLoader());
-               httpContext.setClassLoader(this.getClass().getClassLoader());
-               httpContext.setContextPath("/");
-
-               httpContext.setAttribute(CONTEXT_TEMPDIR, tempDir.toAbsolutePath().toFile());
-               SessionHandler handler = new SessionHandler();
-               handler.setMaxInactiveInterval(-1);
-               httpContext.setSessionHandler(handler);
-
-               return httpContext;
-       }
-
-       private String httpPortsMsg() {
-
-               return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ")
-                               + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
-       }
-
-       private String getFrameworkProp(CmsDeployProperty deployProperty) {
+       protected String getDeployProperty(CmsDeployProperty deployProperty) {
                return cmsState.getDeployProperty(deployProperty.getProperty());
        }
 
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java
new file mode 100644 (file)
index 0000000..2aa4abc
--- /dev/null
@@ -0,0 +1,147 @@
+package org.argeo.cms.jetty;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.argeo.cms.servlet.httpserver.HttpContextServlet;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.Filter;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+/** Trivial implementation of @{HttpContext}. */
+class JettyHttpContext extends HttpContext {
+       private final JettyHttpServer httpServer;
+       private final String path;
+       private final ContextHandler contextHandler;
+       private final ContextAttributes attributes;
+       private final List<Filter> filters = new ArrayList<>();
+
+       private HttpHandler handler;
+       private Authenticator authenticator;
+
+       public JettyHttpContext(JettyHttpServer httpServer, String path) {
+               this.httpServer = httpServer;
+               this.path = path;
+
+               ServletContextHandler servletContextHandler = new ServletContextHandler();
+               servletContextHandler.setContextPath(path);
+               HttpContextServlet servlet = new HttpContextServlet(this);
+               servletContextHandler.addServlet(new ServletHolder(servlet), "/*");
+               contextHandler = servletContextHandler;
+
+               attributes = new ContextAttributes();
+       }
+
+       @Override
+       public HttpHandler getHandler() {
+               return handler;
+       }
+
+       @Override
+       public void setHandler(HttpHandler handler) {
+               if (this.handler != null)
+                       throw new IllegalArgumentException("Handler is already set");
+               Objects.requireNonNull(handler);
+               this.handler = handler;
+
+               if (httpServer.isStarted())
+                       try {
+                               contextHandler.start();
+                       } catch (Exception e) {
+                               throw new IllegalStateException("Cannot start context handler", e);
+                       }
+       }
+
+       @Override
+       public String getPath() {
+               return path;
+       }
+
+       @Override
+       public HttpServer getServer() {
+               return httpServer;
+       }
+
+       @Override
+       public Map<String, Object> getAttributes() {
+               return attributes;
+       }
+
+       @Override
+       public List<Filter> getFilters() {
+               return filters;
+       }
+
+       @Override
+       public Authenticator setAuthenticator(Authenticator auth) {
+               Authenticator previousAuthenticator = authenticator;
+               this.authenticator = auth;
+               return previousAuthenticator;
+       }
+
+       @Override
+       public Authenticator getAuthenticator() {
+               return authenticator;
+       }
+
+       public Handler getContextHandler() {
+               return contextHandler;
+       }
+
+       private class ContextAttributes extends AbstractMap<String, Object> {
+               @Override
+               public Set<Entry<String, Object>> entrySet() {
+                       Set<Entry<String, Object>> entries = new HashSet<>();
+                       for (Enumeration<String> keys = contextHandler.getAttributeNames(); keys.hasMoreElements();) {
+                               entries.add(new ContextAttributeEntry(keys.nextElement()));
+                       }
+                       return entries;
+               }
+
+               @Override
+               public Object put(String key, Object value) {
+                       Object previousValue = get(key);
+                       contextHandler.setAttribute(key, value);
+                       return previousValue;
+               }
+
+               private class ContextAttributeEntry implements Map.Entry<String, Object> {
+                       private final String key;
+
+                       public ContextAttributeEntry(String key) {
+                               this.key = key;
+                       }
+
+                       @Override
+                       public String getKey() {
+                               return key;
+                       }
+
+                       @Override
+                       public Object getValue() {
+                               return contextHandler.getAttribute(key);
+                       }
+
+                       @Override
+                       public Object setValue(Object value) {
+                               Object previousValue = getValue();
+                               contextHandler.setAttribute(key, value);
+                               return previousValue;
+                       }
+
+               }
+       }
+}
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java
new file mode 100644 (file)
index 0000000..ea97057
--- /dev/null
@@ -0,0 +1,326 @@
+package org.argeo.cms.jetty;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import javax.servlet.ServletException;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.util.http.HttpServerUtils;
+import org.eclipse.jetty.http.UriCompliance;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.ExecutorThreadPool;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+
+public class JettyHttpServer extends HttpsServer {
+       private final static CmsLog log = CmsLog.getLog(JettyHttpServer.class);
+
+       private static final int DEFAULT_IDLE_TIMEOUT = 30000;
+
+       private Server server;
+
+       protected ServerConnector httpConnector;
+       protected ServerConnector httpsConnector;
+
+       private InetSocketAddress address;
+
+       private ThreadPoolExecutor executor;
+
+       private HttpsConfigurator httpsConfigurator;
+
+       private final Map<String, JettyHttpContext> contexts = new TreeMap<>();
+
+       protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
+
+       private boolean started;
+
+       @Override
+       public void bind(InetSocketAddress addr, int backlog) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public void start() {
+               try {
+
+                       ThreadPool threadPool = null;
+                       if (executor != null) {
+                               threadPool = new ExecutorThreadPool(executor);
+                       } else {
+                               // TODO make it configurable
+                               threadPool = new QueuedThreadPool(10, 1);
+                       }
+
+                       server = new Server(threadPool);
+
+                       configureConnectors();
+
+                       if (httpConnector != null) {
+                               httpConnector.open();
+                               server.addConnector(httpConnector);
+                       }
+
+                       if (httpsConnector != null) {
+                               httpsConnector.open();
+                               server.addConnector(httpsConnector);
+                       }
+
+                       // holder
+
+                       // context
+                       ServletContextHandler rootContextHandler = createRootContextHandler();
+                       // httpContext.addServlet(holder, "/*");
+                       if (rootContextHandler != null)
+                               configureRootContextHandler(rootContextHandler);
+//                     server.setHandler(rootContextHandler);
+
+                       ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
+                       if (rootContextHandler != null && !contexts.containsKey("/"))
+                               contextHandlers.addHandler(rootContextHandler);
+                       for (String contextPath : contexts.keySet()) {
+                               JettyHttpContext ctx = contexts.get(contextPath);
+                               contextHandlers.addHandler(ctx.getContextHandler());
+                       }
+
+                       server.setHandler(contextHandlerCollection);
+
+                       //
+                       // START
+                       server.start();
+                       //
+
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
+
+                       log.info(httpPortsMsg());
+                       started = true;
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot start Jetty HTTPS server", e);
+               }
+       }
+
+       @Override
+       public void stop(int delay) {
+               // TODO wait for processing to complete
+               stop();
+
+       }
+
+       public void stop() {
+               try {
+                       // serverConnector.close();
+                       server.stop();
+                       // TODO delete temp dir
+                       started = false;
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+
+       }
+
+       @Override
+       public void setExecutor(Executor executor) {
+               if (!(executor instanceof ThreadPoolExecutor))
+                       throw new IllegalArgumentException("Only " + ThreadPoolExecutor.class.getName() + " are supported");
+               this.executor = (ThreadPoolExecutor) executor;
+       }
+
+       @Override
+       public Executor getExecutor() {
+               return executor;
+       }
+
+       @Override
+       public synchronized HttpContext createContext(String path, HttpHandler handler) {
+               HttpContext httpContext = createContext(path);
+               httpContext.setHandler(handler);
+               return httpContext;
+       }
+
+       @Override
+       public synchronized HttpContext createContext(String path) {
+               if (contexts.containsKey(path))
+                       throw new IllegalArgumentException("Context " + path + " already exists");
+               JettyHttpContext httpContext = new JettyHttpContext(this, path);
+               contexts.put(path, httpContext);
+
+               contextHandlerCollection.addHandler(httpContext.getContextHandler());
+               return httpContext;
+       }
+
+       @Override
+       public synchronized void removeContext(String path) throws IllegalArgumentException {
+               if (!contexts.containsKey(path))
+                       throw new IllegalArgumentException("Context " + path + " does not exist");
+               JettyHttpContext httpContext = contexts.remove(path);
+               // TODO stop handler first?
+               contextHandlerCollection.removeHandler(httpContext.getContextHandler());
+       }
+
+       @Override
+       public synchronized void removeContext(HttpContext context) {
+               removeContext(context.getPath());
+       }
+
+       @Override
+       public InetSocketAddress getAddress() {
+               return address;
+       }
+
+       @Override
+       public void setHttpsConfigurator(HttpsConfigurator config) {
+               this.httpsConfigurator = config;
+       }
+
+       @Override
+       public HttpsConfigurator getHttpsConfigurator() {
+               return httpsConfigurator;
+       }
+
+       
+       
+       protected void configureConnectors() {
+               HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+               String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
+               String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
+
+               /// TODO make it more generic
+               String httpHost = getDeployProperty(CmsDeployProperty.HOST);
+//             String httpsHost = getFrameworkProp(
+//                             JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
+
+               // try {
+               if (httpPortStr != null || httpsPortStr != null) {
+                       boolean httpEnabled = httpPortStr != null;
+                       // props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
+                       boolean httpsEnabled = httpsPortStr != null;
+                       // props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
+                       if (httpsEnabled) {
+                               int httpsPort = Integer.parseInt(httpsPortStr);
+                               httpConfiguration.setSecureScheme("https");
+                               httpConfiguration.setSecurePort(httpsPort);
+                       }
+
+                       if (httpEnabled) {
+                               int httpPort = Integer.parseInt(httpPortStr);
+                               httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
+                               httpConnector.setPort(httpPort);
+                               httpConnector.setHost(httpHost);
+                               httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT);
+                       }
+
+                       if (httpsEnabled) {
+
+                               SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+                               // sslContextFactory.setKeyStore(KeyS)
+
+                               sslContextFactory.setKeyStoreType(getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
+                               sslContextFactory.setKeyStorePath(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
+                               sslContextFactory.setKeyStorePassword(getDeployProperty(CmsDeployProperty.SSL_PASSWORD));
+                               // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
+                               sslContextFactory.setProtocol("TLS");
+
+                               sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+                               sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
+                               sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
+
+                               String wantClientAuth = getDeployProperty(CmsDeployProperty.SSL_WANTCLIENTAUTH);
+                               if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true)))
+                                       sslContextFactory.setWantClientAuth(true);
+                               String needClientAuth = getDeployProperty(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
+                               if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true)))
+                                       sslContextFactory.setNeedClientAuth(true);
+
+                               // HTTPS Configuration
+                               HttpConfiguration https_config = new HttpConfiguration(httpConfiguration);
+                               https_config.addCustomizer(new SecureRequestCustomizer());
+                               https_config.setUriCompliance(UriCompliance.LEGACY);
+
+                               // HTTPS connector
+                               httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
+                                               new HttpConnectionFactory(https_config));
+                               int httpsPort = Integer.parseInt(httpsPortStr);
+                               httpsConnector.setPort(httpsPort);
+                               httpsConnector.setHost(httpHost);
+                       }
+
+               }
+
+       }
+
+       protected String getDeployProperty(CmsDeployProperty deployProperty) {
+               return System.getProperty(deployProperty.getProperty());
+       }
+
+       private String httpPortsMsg() {
+
+               return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ")
+                               + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
+       }
+
+       public Integer getHttpPort() {
+               if (httpConnector == null)
+                       return null;
+               return httpConnector.getLocalPort();
+       }
+
+       public Integer getHttpsPort() {
+               if (httpsConnector == null)
+                       return null;
+               return httpsConnector.getLocalPort();
+       }
+
+       protected ServletContextHandler createRootContextHandler() {
+               return null;
+       }
+
+       protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
+
+       }
+
+       
+       public boolean isStarted() {
+               return started;
+       }
+
+       public static void main(String... args) {
+               JettyHttpServer httpServer = new JettyHttpServer();
+               System.setProperty("argeo.http.port", "8080");
+               httpServer.createContext("/", (exchange) -> {
+                       exchange.getResponseBody().write("Hello World!".getBytes());
+               });
+               httpServer.start();
+               httpServer.createContext("/sub/context", (exchange) -> {
+                       final String key = "count";
+                       Integer count = (Integer) exchange.getHttpContext().getAttributes().get(key);
+                       if (count == null)
+                               exchange.getHttpContext().getAttributes().put(key, 0);
+                       else
+                               exchange.getHttpContext().getAttributes().put(key, count + 1);
+                       StringBuilder sb = new StringBuilder();
+                       sb.append("Subcontext:");
+                       sb.append(" " + key + "=" + exchange.getHttpContext().getAttributes().get(key));
+                       sb.append(" relativePath=" + HttpServerUtils.relativize(exchange));
+                       exchange.getResponseBody().write(sb.toString().getBytes());
+               });
+       }
+}
index d0047dcc6a0b9e320289df44ddf5473923f87454..12114c2a8edbf90375b1fd938f3679640f9966bf 100644 (file)
@@ -1,7 +1,6 @@
 Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
 
 Import-Package: \
-org.apache.commons.httpclient.cookie;resolution:=optional,\
 !com.sun.security.jgss,\
 org.osgi.*;version=0.0.0,\
 org.apache.xerces.jaxp;resolution:=optional,\
index 8fec5e54bb39dc088fa5f5755bd90116dc35dfa8..67b98f4d0034c2f7752e765fbee687477633e222 100644 (file)
@@ -2,17 +2,5 @@ output.. = bin/
 bin.includes = META-INF/,\
                .,\
                bin/,\
-               OSGI-INF/,\
-               OSGI-INF/simpleTransactionManager.xml,\
-               OSGI-INF/cmsState.xml,\
-               OSGI-INF/nodeUserAdmin.xml,\
-               OSGI-INF/deployConfig.xml,\
-               OSGI-INF/cmsDeployment.xml,\
-               OSGI-INF/cmsContext.xml,\
-               OSGI-INF/acrContentRepository.xml,\
-               OSGI-INF/uuidFactory.xml
+               OSGI-INF/
 source.. = src/
-additional.bundles = org.argeo.ext.slf4j,\
-                     org.slf4j.commons.logging,\
-                     org.slf4j.api,\
-                     org.apache.commons.codec
index 9e00ed458ac0011480f034589e3297b8e89eb7c5..711edba2f013b925d088889b7489a9bab9616b75 100644 (file)
@@ -24,7 +24,7 @@ import org.argeo.api.cms.CmsSessionId;
 import org.argeo.api.cms.DataAdminPrincipal;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.argeo.cms.internal.auth.ImpliedByPrincipal;
-import org.argeo.cms.internal.http.WebCmsSessionImpl;
+import org.argeo.cms.internal.auth.RemoteCmsSessionImpl;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.osgi.useradmin.AuthenticatingUser;
 import org.osgi.service.useradmin.Authorization;
@@ -151,7 +151,7 @@ class CmsAuthUtils {
                                                currentLocalSession.close();
                                                // new CMS session
                                                UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID();
-                                               cmsSession = new WebCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request);
+                                               cmsSession = new RemoteCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request);
                                                CmsContextImpl.getCmsContext().registerCmsSession(cmsSession);
                                        } else if (!authorization.getName().equals(currentLocalSession.getAuthorization().getName())) {
                                                throw new IllegalStateException("Inconsistent user " + authorization.getName()
@@ -176,7 +176,7 @@ class CmsAuthUtils {
                        } else {
                                // new CMS session
                                UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID();
-                               cmsSession = new WebCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request);
+                               cmsSession = new RemoteCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request);
                                CmsContextImpl.getCmsContext().registerCmsSession(cmsSession);
                        }
 
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteCmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteCmsSessionImpl.java
new file mode 100644 (file)
index 0000000..41ee779
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.cms.internal.auth;
+
+import java.util.Locale;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.osgi.service.useradmin.Authorization;
+
+/** CMS session implementation in a web context. */
+public class RemoteCmsSessionImpl extends CmsSessionImpl {
+       private static final long serialVersionUID = -5178883380637048025L;
+       private RemoteAuthSession httpSession;
+
+       public RemoteCmsSessionImpl(UUID uuid, Subject initialSubject, Authorization authorization, Locale locale,
+                       RemoteAuthRequest request) {
+               super(uuid, initialSubject, authorization, locale, request.getSession().getId());
+               httpSession = request.getSession();
+       }
+
+       @Override
+       public boolean isValid() {
+               if (isClosed())
+                       return false;
+               return httpSession.isValid();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java
new file mode 100644 (file)
index 0000000..54849fc
--- /dev/null
@@ -0,0 +1,114 @@
+package org.argeo.cms.internal.http;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.SpnegoLoginModule;
+
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpPrincipal;
+
+public class CmsAuthenticator extends Authenticator {
+       final static String HEADER_AUTHORIZATION = "Authorization";
+       final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+       private final static CmsLog log = CmsLog.getLog(CmsAuthenticator.class);
+
+       // TODO make it configurable
+       private final String httpAuthRealm = "Argeo";
+       private final boolean forceBasic = false;
+
+       @Override
+       public Result authenticate(HttpExchange exch) {
+//             if (log.isTraceEnabled())
+//                     HttpUtils.logRequestHeaders(log, request);
+               RemoteAuthHttpExchange remoteAuthHttpExchange = new RemoteAuthHttpExchange(exch);
+               ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
+               Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader());
+               LoginContext lc;
+               try {
+                       lc = CmsAuth.USER
+                                       .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthHttpExchange, remoteAuthHttpExchange));
+                       lc.login();
+               } catch (LoginException e) {
+                       // FIXME better analyse failure so as not to try endlessly
+                       if (authIsRequired(exch)) {
+                               return askForWwwAuth(exch);
+                       } else {
+                               lc = processUnauthorized(exch);
+//                     if (log.isTraceEnabled())
+//                             HttpUtils.logResponseHeaders(log, response);
+                       }
+                       if (lc == null)
+                               return new Authenticator.Failure(403);
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
+               }
+
+               Subject subject = lc.getSubject();
+
+//             Subject.doAs(subject, new PrivilegedAction<Void>() {
+//
+//                     @Override
+//                     public Void run() {
+//                             // TODO also set login context in order to log out ?
+//                             RemoteAuthUtils.configureRequestSecurity(new ServletHttpRequest(request));
+//                             return null;
+//                     }
+//
+//             });
+               String username = CurrentUser.getUsername(subject);
+               HttpPrincipal httpPrincipal = new HttpPrincipal(username, httpAuthRealm);
+               return new Authenticator.Success(httpPrincipal);
+       }
+
+       protected boolean authIsRequired(HttpExchange httpExchange) {
+               return false;
+       }
+
+       protected LoginContext processUnauthorized(HttpExchange httpExchange) {
+
+               RemoteAuthHttpExchange remoteAuthExchange = new RemoteAuthHttpExchange(httpExchange);
+               // anonymous
+               ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+               try {
+                       Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader());
+                       LoginContext lc = CmsAuth.ANONYMOUS
+                                       .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthExchange, remoteAuthExchange));
+                       lc.login();
+                       return lc;
+               } catch (LoginException e1) {
+                       if (log.isDebugEnabled())
+                               log.error("Cannot log in as anonymous", e1);
+                       return null;
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentContextClassLoader);
+               }
+       }
+
+       protected Authenticator.Retry askForWwwAuth(HttpExchange httpExchange) {
+               // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
+               // realm=\"" + httpAuthRealm + "\"");
+               if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO
+                       httpExchange.getResponseHeaders().set(HEADER_WWW_AUTHENTICATE, "Negotiate");
+               else
+                       httpExchange.getResponseHeaders().set(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");
+
+               return new Authenticator.Retry(401);
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java b/org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java
new file mode 100644 (file)
index 0000000..68e0b1e
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.cms.internal.http;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthSession;
+
+import com.sun.net.httpserver.HttpExchange;
+
+public class RemoteAuthHttpExchange implements RemoteAuthRequest, RemoteAuthResponse {
+       private HttpExchange httpExchange;
+
+       public RemoteAuthHttpExchange(HttpExchange httpExchange) {
+               this.httpExchange = httpExchange;
+       }
+
+       @Override
+       public void setHeader(String keys, String value) {
+               httpExchange.getResponseHeaders().put(keys, Collections.singletonList(value));
+       }
+
+       @Override
+       public RemoteAuthSession getSession() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public RemoteAuthSession createSession() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Locale getLocale() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Object getAttribute(String key) {
+               return httpExchange.getAttribute(key);
+       }
+
+       @Override
+       public void setAttribute(String key, Object object) {
+               httpExchange.setAttribute(key, object);
+       }
+
+       @Override
+       public String getHeader(String key) {
+               List<String> lst = httpExchange.getRequestHeaders().get(key);
+               if (lst == null || lst.size() == 0)
+                       return null;
+               return lst.get(0);
+       }
+
+       @Override
+       public int getLocalPort() {
+               return httpExchange.getLocalAddress().getPort();
+       }
+
+       @Override
+       public String getRemoteAddr() {
+               return httpExchange.getRemoteAddress().getHostName();
+       }
+
+       @Override
+       public int getRemotePort() {
+               return httpExchange.getRemoteAddress().getPort();
+       }
+
+}
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
deleted file mode 100644 (file)
index 4b4a776..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.argeo.cms.internal.http;
-
-import java.util.Locale;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-
-import org.argeo.cms.auth.RemoteAuthRequest;
-import org.argeo.cms.auth.RemoteAuthSession;
-import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.osgi.service.useradmin.Authorization;
-
-/** CMS session implementation in a web context. */
-public class WebCmsSessionImpl extends CmsSessionImpl {
-       private static final long serialVersionUID = -5178883380637048025L;
-       private RemoteAuthSession httpSession;
-
-       public WebCmsSessionImpl(UUID uuid, Subject initialSubject, Authorization authorization, Locale locale,
-                       RemoteAuthRequest request) {
-               super(uuid, initialSubject, authorization, locale, request.getSession().getId());
-               httpSession = request.getSession();
-       }
-
-       @Override
-       public boolean isValid() {
-               if (isClosed())
-                       return false;
-               return httpSession.isValid();
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/HttpCredentialProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/HttpCredentialProvider.java
deleted file mode 100644 (file)
index 4a9392c..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.argeo.cms.internal.http.client;
-
-import org.apache.commons.httpclient.Credentials;
-import org.apache.commons.httpclient.auth.AuthScheme;
-import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-
-/** SPNEGO credential provider */
-public class HttpCredentialProvider implements CredentialsProvider {
-
-       @Override
-       public Credentials getCredentials(AuthScheme scheme, String host, int port, boolean proxy)
-                       throws CredentialsNotAvailableException {
-               if (scheme instanceof SpnegoAuthScheme)
-                       return new SpnegoCredentials();
-               else
-                       throw new UnsupportedOperationException("Auth scheme " + scheme.getSchemeName() + " not supported");
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java
deleted file mode 100644 (file)
index a8aa29b..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-package org.argeo.cms.internal.http.client;
-
-import java.net.URL;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-
-import org.apache.commons.httpclient.Credentials;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.AuthScheme;
-import org.apache.commons.httpclient.auth.AuthenticationException;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.auth.MalformedChallengeException;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.params.DefaultHttpParams;
-import org.apache.commons.httpclient.params.HttpParams;
-import org.argeo.cms.auth.RemoteAuthUtils;
-
-/** Implementation of the SPNEGO auth scheme. */
-public class SpnegoAuthScheme implements AuthScheme {
-//     private final static Log log = LogFactory.getLog(SpnegoAuthScheme.class);
-
-       public static final String NAME = "Negotiate";
-//     private final static Oid KERBEROS_OID;
-//     static {
-//             try {
-//                     KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
-//             } catch (GSSException e) {
-//                     throw new IllegalStateException("Cannot create Kerberos OID", e);
-//             }
-//     }
-
-       private final static String DEFAULT_KERBEROS_SERVICE = "HTTP";
-
-       private boolean complete = false;
-       private String realm;
-
-       @Override
-       public void processChallenge(String challenge) throws MalformedChallengeException {
-               // if(tokenStr!=null){
-               // log.error("Received challenge while there is a token. Failing.");
-               // complete = false;
-               // }
-
-       }
-
-       @Override
-       public String getSchemeName() {
-               return NAME;
-       }
-
-       @Override
-       public String getParameter(String name) {
-               return null;
-       }
-
-       @Override
-       public String getRealm() {
-               return realm;
-       }
-
-       @Override
-       public String getID() {
-               return NAME;
-       }
-
-       @Override
-       public boolean isConnectionBased() {
-               return true;
-       }
-
-       @Override
-       public boolean isComplete() {
-               return complete;
-       }
-
-       @Override
-       public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException {
-               // log.debug("authenticate " + method + " " + uri);
-               // return null;
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException {
-//             GSSContext context = null;
-               String hostname;
-               try {
-                       hostname = method.getURI().getHost();
-                       String tokenStr = RemoteAuthUtils.getGssToken(null, DEFAULT_KERBEROS_SERVICE, hostname);
-                       return "Negotiate " + tokenStr;
-               } catch (Exception e1) {
-                       complete = true;
-                       throw new AuthenticationException("Cannot authenticate " + method, e1);
-               }
-//             String serverPrinc = DEFAULT_KERBEROS_SERVICE + "@" + hostname;
-//
-//             try {
-//                     // Get service's principal name
-//                     GSSManager manager = GSSManager.getInstance();
-//                     GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID);
-//
-//                     // Get the context for authentication
-//                     context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME);
-//                     // context.requestMutualAuth(true); // Request mutual authentication
-//                     // context.requestConf(true); // Request confidentiality
-//                     context.requestCredDeleg(true);
-//
-//                     byte[] token = new byte[0];
-//
-//                     // token is ignored on the first call
-//                     token = context.initSecContext(token, 0, token.length);
-//
-//                     // Send a token to the server if one was generated by
-//                     // initSecContext
-//                     if (token != null) {
-//                             tokenStr = Base64.getEncoder().encodeToString(token);
-//                             // complete=true;
-//                     }
-//             } catch (GSSException e) {
-//                     complete = true;
-//                     throw new AuthenticationException("Cannot authenticate to " + serverPrinc, e);
-//             }
-       }
-
-       public static void main(String[] args) {
-               String principal = System.getProperty("javax.security.auth.login.name");
-               if (args.length == 0 || principal == null) {
-                       System.err.println("usage: java -Djavax.security.auth.login.name=<principal@REALM> "
-                                       + SpnegoAuthScheme.class.getName() + " <url>");
-                       System.exit(1);
-                       return;
-               }
-               String url = args[0];
-
-               URL jaasUrl = SpnegoAuthScheme.class.getResource("jaas.cfg");
-               System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
-               try {
-                       LoginContext lc = new LoginContext("SINGLE_USER");
-                       lc.login();
-
-                       AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
-                       HttpParams params = DefaultHttpParams.getDefaultParams();
-                       ArrayList<String> schemes = new ArrayList<>();
-                       schemes.add(SpnegoAuthScheme.NAME);
-                       params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
-                       params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
-
-                       int responseCode = Subject.doAs(lc.getSubject(), new PrivilegedExceptionAction<Integer>() {
-                               public Integer run() throws Exception {
-                                       HttpClient httpClient = new HttpClient();
-                                       return httpClient.executeMethod(new GetMethod(url));
-                               }
-                       });
-                       System.out.println("Reponse code: " + responseCode);
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoCredentials.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoCredentials.java
deleted file mode 100644 (file)
index f59b72a..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.argeo.cms.internal.http.client;
-
-import org.apache.commons.httpclient.Credentials;
-
-public class SpnegoCredentials implements Credentials {
-
-}
index 9675c25429e1ff3d3274a84e2a9e52960bd4cbd3..4f0ba10aad03d6432d94603353f26e343adc5a76 100644 (file)
@@ -25,19 +25,12 @@ import javax.security.auth.kerberos.KerberosPrincipal;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.params.DefaultHttpParams;
-import org.apache.commons.httpclient.params.HttpMethodParams;
-import org.apache.commons.httpclient.params.HttpParams;
 import org.apache.commons.io.FileUtils;
 import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
 import org.argeo.cms.CmsDeployProperty;
-import org.argeo.cms.internal.http.client.HttpCredentialProvider;
-import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
 import org.argeo.osgi.useradmin.AggregatingUserAdmin;
 import org.argeo.osgi.useradmin.DirectoryUserAdmin;
 import org.argeo.osgi.useradmin.UserDirectory;
@@ -298,16 +291,6 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
                                }
                        }
 
-                       // Register client-side SPNEGO auth scheme
-                       AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
-                       HttpParams params = DefaultHttpParams.getDefaultParams();
-                       ArrayList<String> schemes = new ArrayList<>();
-                       schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
-                       // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
-                       params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
-                       params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
-                       params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
-                       // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
                }
        }
 
diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/CmsCli.java b/org.argeo.cms/src/org/argeo/cms/runtime/CmsCli.java
deleted file mode 100644 (file)
index 425bc30..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.cms.runtime;
-
-import org.argeo.api.cli.CommandsCli;
-
-public class CmsCli extends CommandsCli {
-
-       public CmsCli(String commandName) {
-               super(commandName);
-               addCommand("launch", new StaticCmsLaunch());
-       }
-
-       @Override
-       public String getDescription() {
-               return "Static CMS utilities.";
-       }
-
-       public static void main(String[] args) {
-               mainImpl(new CmsCli("cms"), args);
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCmsLaunch.java b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCmsLaunch.java
deleted file mode 100644 (file)
index b4cfb90..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.argeo.cms.runtime;
-
-import java.lang.management.ManagementFactory;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.argeo.api.cli.DescribedCommand;
-
-public class StaticCmsLaunch implements DescribedCommand<String> {
-       private Option dataOption;
-
-       @Override
-       public Options getOptions() {
-               Options options = new Options();
-               dataOption = Option.builder().longOpt("data").hasArg().required()
-                               .desc("path to the writable data area (mandatory)").build();
-               options.addOption(dataOption);
-               return options;
-       }
-
-       @Override
-       public String apply(List<String> args) {
-               CommandLine cl = toCommandLine(args);
-               String dataPath = cl.getOptionValue(dataOption);
-
-               Path instancePath = Paths.get(dataPath);
-               System.setProperty("osgi.instance.area", instancePath.toUri().toString());
-               System.setProperty("argeo.http.port", "0");
-
-               StaticCms staticCms = new StaticCms();
-               Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown"));
-               staticCms.start();
-
-               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-               System.out.println("Static CMS available in " + jvmUptime + " ms.");
-
-               staticCms.waitForStop();
-
-               return null;
-       }
-
-       @Override
-       public String getDescription() {
-               return "Launch a static CMS";
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/dav/DavServerHandler.java b/org.argeo.util/src/org/argeo/util/dav/DavServerHandler.java
new file mode 100644 (file)
index 0000000..4fc73f3
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.util.dav;
+
+import java.io.IOException;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+public class DavServerHandler implements HttpHandler {
+
+       @Override
+       public void handle(HttpExchange exchange) throws IOException {
+               String method = exchange.getRequestMethod();
+               if (DavMethod.PROPFIND.name().equals(method)) {
+                       handle(exchange);
+               } else {
+                       throw new IllegalArgumentException("Unsupported method " + method);
+               }
+
+       }
+
+       protected DavResponse handlePROPFIND(HttpExchange exchange) {
+               return null;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/http/HttpServerUtils.java b/org.argeo.util/src/org/argeo/util/http/HttpServerUtils.java
new file mode 100644 (file)
index 0000000..6c6e884
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.util.http;
+
+import java.net.URI;
+import java.util.Objects;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+
+public class HttpServerUtils {
+
+       public static String relativize(HttpContext httpContext, String path) {
+               Objects.requireNonNull(path);
+               if (!path.startsWith(httpContext.getPath()))
+                       throw new IllegalArgumentException(path + " does not belong to context" + httpContext.getPath());
+               String relativePath = path.substring(httpContext.getPath().length());
+               // TODO optimise?
+               if (relativePath.startsWith("/"))
+                       relativePath = relativePath.substring(1);
+               return relativePath;
+       }
+
+       public static String relativize(HttpExchange exchange) {
+               URI uri = exchange.getRequestURI();
+               HttpContext httpContext = exchange.getHttpContext();
+               return relativize(httpContext, uri.getPath());
+       }
+
+       /** singleton */
+       private HttpServerUtils() {
+
+       }
+}