Rename CMS EE4J
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 10 Jul 2022 10:17:08 +0000 (12:17 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 10 Jul 2022 10:17:08 +0000 (12:17 +0200)
55 files changed:
Makefile
org.argeo.cms.ee/.classpath [new file with mode: 0644]
org.argeo.cms.ee/.project [new file with mode: 0644]
org.argeo.cms.ee/OSGI-INF/pkgServlet.xml [new file with mode: 0644]
org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml [new file with mode: 0644]
org.argeo.cms.ee/bnd.bnd [new file with mode: 0644]
org.argeo.cms.ee/build.properties [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpRequest.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpSession.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/HttpUtils.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/RobotServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/WebSocketView.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/package-info.java [new file with mode: 0644]
org.argeo.cms.ee4j/.classpath [deleted file]
org.argeo.cms.ee4j/.project [deleted file]
org.argeo.cms.ee4j/OSGI-INF/pkgServlet.xml [deleted file]
org.argeo.cms.ee4j/OSGI-INF/pkgServletContext.xml [deleted file]
org.argeo.cms.ee4j/bnd.bnd [deleted file]
org.argeo.cms.ee4j/build.properties [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsExceptionsChain.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsLoginServlet.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsLogoutServlet.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsPrivateServletContext.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsSessionDescriptor.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsTokenServlet.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/TokenDescriptor.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/package-info.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/CmsServletContext.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpRequest.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpResponse.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpSession.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/HttpUtils.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/PkgServlet.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/RobotServlet.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketView.java [deleted file]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/package-info.java [deleted file]

index 4de934b22611f9325fa4fcd389ec3665128947f4..ab2d6254847778013a1bd794e359197107cc4b1a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ org.argeo.api.cli \
 org.argeo.api.cms \
 org.argeo.cms \
 org.argeo.cms.ux \
-org.argeo.cms.ee4j \
+org.argeo.cms.ee \
 org.argeo.cms.lib.jetty \
 org.argeo.cms.lib.equinox \
 org.argeo.cms.lib.sshd \
diff --git a/org.argeo.cms.ee/.classpath b/org.argeo.cms.ee/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.cms.ee/.project b/org.argeo.cms.ee/.project
new file mode 100644 (file)
index 0000000..4b68cdd
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.ee</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml b/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml
new file mode 100644 (file)
index 0000000..00fcaff
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.pkgServlet">
+   <implementation class="org.argeo.cms.servlet.internal.PkgServlet"/>
+   <service>
+      <provide interface="javax.servlet.Servlet"/>
+   </service>
+   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
+   <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=pkgServletContext)"/>
+</scr:component>
diff --git a/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml b/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml
new file mode 100644 (file)
index 0000000..7540a2c
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.pkgServletContext">
+   <implementation class="org.argeo.cms.servlet.CmsServletContext"/>
+   <service>
+      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
+   </service>
+   <property name="osgi.http.whiteboard.context.name" type="String" value="pkgServletContext"/>
+   <property name="osgi.http.whiteboard.context.path" type="String" value="/pkg"/>
+</scr:component>
diff --git a/org.argeo.cms.ee/bnd.bnd b/org.argeo.cms.ee/bnd.bnd
new file mode 100644 (file)
index 0000000..6fae1ea
--- /dev/null
@@ -0,0 +1,11 @@
+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.cms.osgi,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Service-Component:\
+OSGI-INF/pkgServletContext.xml,\
+OSGI-INF/pkgServlet.xml
diff --git a/org.argeo.cms.ee/build.properties b/org.argeo.cms.ee/build.properties
new file mode 100644 (file)
index 0000000..ee94f53
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/jettyServiceFactory.xml
+source.. = src/
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java
new file mode 100644 (file)
index 0000000..fb289c1
--- /dev/null
@@ -0,0 +1,154 @@
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsLog;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Serialisable wrapper of a {@link Throwable}. */
+public class CmsExceptionsChain {
+       public final static CmsLog log = CmsLog.getLog(CmsExceptionsChain.class);
+
+       private List<SystemException> exceptions = new ArrayList<>();
+
+       public CmsExceptionsChain() {
+               super();
+       }
+
+       public CmsExceptionsChain(Throwable exception) {
+               writeException(exception);
+               if (log.isDebugEnabled())
+                       log.error("Exception chain", exception);
+       }
+
+       public String toJsonString(ObjectMapper objectMapper) {
+               try {
+                       return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
+               } catch (JsonProcessingException e) {
+                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
+               }
+       }
+
+       public void writeAsJson(ObjectMapper objectMapper, Writer writer) {
+               try {
+                       JsonGenerator jg = objectMapper.writerWithDefaultPrettyPrinter().getFactory().createGenerator(writer);
+                       jg.writeObject(this);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
+               }
+       }
+
+       public void writeAsJson(ObjectMapper objectMapper, HttpServletResponse resp) {
+               try {
+                       resp.setContentType("application/json");
+                       resp.setStatus(500);
+                       writeAsJson(objectMapper, resp.getWriter());
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
+               }
+       }
+
+       /** recursive */
+       protected void writeException(Throwable exception) {
+               SystemException systemException = new SystemException(exception);
+               exceptions.add(systemException);
+               Throwable cause = exception.getCause();
+               if (cause != null)
+                       writeException(cause);
+       }
+
+       public List<SystemException> getExceptions() {
+               return exceptions;
+       }
+
+       public void setExceptions(List<SystemException> exceptions) {
+               this.exceptions = exceptions;
+       }
+
+       /** An exception in the chain. */
+       public static class SystemException {
+               private String type;
+               private String message;
+               private List<String> stackTrace;
+
+               public SystemException() {
+               }
+
+               public SystemException(Throwable exception) {
+                       this.type = exception.getClass().getName();
+                       this.message = exception.getMessage();
+                       this.stackTrace = new ArrayList<>();
+                       StackTraceElement[] elems = exception.getStackTrace();
+                       for (int i = 0; i < elems.length; i++)
+                               stackTrace.add("at " + elems[i].toString());
+               }
+
+               public String getType() {
+                       return type;
+               }
+
+               public void setType(String type) {
+                       this.type = type;
+               }
+
+               public String getMessage() {
+                       return message;
+               }
+
+               public void setMessage(String message) {
+                       this.message = message;
+               }
+
+               public List<String> getStackTrace() {
+                       return stackTrace;
+               }
+
+               public void setStackTrace(List<String> stackTrace) {
+                       this.stackTrace = stackTrace;
+               }
+
+               @Override
+               public String toString() {
+                       return "System exception: " + type + ", " + message + ", " + stackTrace;
+               }
+
+       }
+
+       @Override
+       public String toString() {
+               return exceptions.toString();
+       }
+
+//     public static void main(String[] args) throws Exception {
+//             try {
+//                     try {
+//                             try {
+//                                     testDeeper();
+//                             } catch (Exception e) {
+//                                     throw new Exception("Less deep exception", e);
+//                             }
+//                     } catch (Exception e) {
+//                             throw new RuntimeException("Top exception", e);
+//                     }
+//             } catch (Exception e) {
+//                     CmsExceptionsChain vjeSystemErrors = new CmsExceptionsChain(e);
+//                     ObjectMapper objectMapper = new ObjectMapper();
+//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(vjeSystemErrors));
+//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(e));
+//                     e.printStackTrace();
+//             }
+//     }
+//
+//     static void testDeeper() throws Exception {
+//             throw new IllegalStateException("Deep exception");
+//     }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java
new file mode 100644 (file)
index 0000000..29a3137
--- /dev/null
@@ -0,0 +1,112 @@
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.NameCallback;
+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.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsSessionId;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.osgi.service.useradmin.Authorization;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Externally authenticate an http session. */
+public class CmsLoginServlet extends HttpServlet {
+       public final static String PARAM_USERNAME = "username";
+       public final static String PARAM_PASSWORD = "password";
+
+       private static final long serialVersionUID = 2478080654328751539L;
+       private ObjectMapper objectMapper = new ObjectMapper();
+
+       @Override
+       protected void doGet(HttpServletRequest request, HttpServletResponse response)
+                       throws ServletException, IOException {
+               doPost(request, response);
+       }
+
+       @Override
+       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               LoginContext lc = null;
+               String username = req.getParameter(PARAM_USERNAME);
+               String password = req.getParameter(PARAM_PASSWORD);
+               ServletHttpRequest request = new ServletHttpRequest(req);
+               ServletHttpResponse response = new ServletHttpResponse(resp);
+               try {
+                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) {
+                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                       for (Callback callback : callbacks) {
+                                               if (callback instanceof NameCallback && username != null)
+                                                       ((NameCallback) callback).setName(username);
+                                               else if (callback instanceof PasswordCallback && password != null)
+                                                       ((PasswordCallback) callback).setPassword(password.toCharArray());
+                                               else if (callback instanceof RemoteAuthCallback) {
+                                                       ((RemoteAuthCallback) callback).setRequest(request);
+                                                       ((RemoteAuthCallback) callback).setResponse(response);
+                                               }
+                                       }
+                               }
+                       });
+                       lc.login();
+
+                       Subject subject = lc.getSubject();
+                       CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class));
+                       if (cmsSessionId == null) {
+                               resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+                               return;
+                       }
+                       Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class));
+                       Locale locale = extractFrom(subject.getPublicCredentials(Locale.class));
+
+                       CmsSessionDescriptor cmsSessionDescriptor = new CmsSessionDescriptor(authorization.getName(),
+                                       cmsSessionId.getUuid().toString(), authorization.getRoles(), authorization.toString(),
+                                       locale != null ? locale.toString() : null);
+
+                       resp.setContentType("application/json");
+                       JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter());
+                       jg.writeObject(cmsSessionDescriptor);
+
+                       String redirectTo = redirectTo(req);
+                       if (redirectTo != null)
+                               resp.sendRedirect(redirectTo);
+               } catch (LoginException e) {
+                       resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+                       return;
+               }
+       }
+
+       protected <T> T extractFrom(Set<T> creds) {
+               if (creds.size() > 0)
+                       return creds.iterator().next();
+               else
+                       return null;
+       }
+
+       /**
+        * To be overridden in order to return a richer {@link CmsSessionDescriptor} to
+        * be serialized.
+        */
+       protected CmsSessionDescriptor enrichJson(CmsSessionDescriptor cmsSessionDescriptor) {
+               return cmsSessionDescriptor;
+       }
+
+       protected String redirectTo(HttpServletRequest request) {
+               return null;
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java
new file mode 100644 (file)
index 0000000..0628eae
--- /dev/null
@@ -0,0 +1,79 @@
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsSessionId;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+
+/** Externally authenticate an http session. */
+public class CmsLogoutServlet extends HttpServlet {
+       private static final long serialVersionUID = 2478080654328751539L;
+
+       @Override
+       protected void doGet(HttpServletRequest request, HttpServletResponse response)
+                       throws ServletException, IOException {
+               doPost(request, response);
+       }
+
+       @Override
+       protected void doPost(HttpServletRequest request, HttpServletResponse response)
+                       throws ServletException, IOException {
+               ServletHttpRequest httpRequest = new ServletHttpRequest(request);
+               ServletHttpResponse httpResponse = new ServletHttpResponse(response);
+               LoginContext lc = null;
+               try {
+                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
+                                       new RemoteAuthCallbackHandler(httpRequest, httpResponse) {
+                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                                       for (Callback callback : callbacks) {
+                                                               if (callback instanceof RemoteAuthCallback) {
+                                                                       ((RemoteAuthCallback) callback).setRequest(httpRequest);
+                                                                       ((RemoteAuthCallback) callback).setResponse(httpResponse);
+                                                               }
+                                                       }
+                                               }
+                                       });
+                       lc.login();
+
+                       Subject subject = lc.getSubject();
+                       CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class));
+                       if (cmsSessionId != null) {// logged in
+                               CurrentUser.logoutCmsSession(subject);
+                       }
+
+               } catch (LoginException e) {
+                       // ignore
+               }
+
+               String redirectTo = redirectTo(request);
+               if (redirectTo != null)
+                       response.sendRedirect(redirectTo);
+       }
+
+       protected <T> T extractFrom(Set<T> creds) {
+               if (creds.size() > 0)
+                       return creds.iterator().next();
+               else
+                       return null;
+       }
+
+       protected String redirectTo(HttpServletRequest request) {
+               return null;
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java
new file mode 100644 (file)
index 0000000..cec04d2
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.security.AccessControlContext;
+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.argeo.api.cms.CmsAuth;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.osgi.service.http.context.ServletContextHelper;
+
+/** Manages security access to servlets. */
+public class CmsPrivateServletContext extends ServletContextHelper {
+       public final static String LOGIN_PAGE = "argeo.cms.integration.loginPage";
+       public final static String LOGIN_SERVLET = "argeo.cms.integration.loginServlet";
+       private String loginPage;
+       private String loginServlet;
+
+       public void init(Map<String, String> properties) {
+               loginPage = properties.get(LOGIN_PAGE);
+               loginServlet = properties.get(LOGIN_SERVLET);
+       }
+
+       /**
+        * Add the {@link AccessControlContext} as a request attribute, or redirect to
+        * the login page.
+        */
+       @Override
+       public boolean handleSecurity(final HttpServletRequest req, HttpServletResponse resp) throws IOException {
+               LoginContext lc = null;
+               ServletHttpRequest request = new ServletHttpRequest(req);
+               ServletHttpResponse response = new ServletHttpResponse(resp);
+
+               String pathInfo = req.getPathInfo();
+               String servletPath = req.getServletPath();
+               if ((pathInfo != null && (servletPath + pathInfo).equals(loginPage)) || servletPath.contentEquals(loginServlet))
+                       return true;
+               try {
+                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response));
+                       lc.login();
+               } catch (LoginException e) {
+                       lc = processUnauthorized(req, resp);
+                       if (lc == null)
+                               return false;
+               }
+               Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
+
+                       @Override
+                       public Void run() {
+                               // TODO also set login context in order to log out ?
+                               RemoteAuthUtils.configureRequestSecurity(request);
+                               return null;
+                       }
+
+               });
+
+               return true;
+       }
+
+       @Override
+       public void finishSecurity(HttpServletRequest req, HttpServletResponse resp) {
+               RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(req));
+       }
+
+       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+               try {
+                       response.sendRedirect(loginPage);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot redirect to login page", e);
+               }
+               return null;
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java
new file mode 100644 (file)
index 0000000..30de616
--- /dev/null
@@ -0,0 +1,96 @@
+package org.argeo.cms.integration;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.argeo.api.cms.CmsSession;
+import org.osgi.service.useradmin.Authorization;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/** A serializable descriptor of an internal {@link CmsSession}. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CmsSessionDescriptor implements Serializable, Authorization {
+       private static final long serialVersionUID = 8592162323372641462L;
+
+       private String name;
+       private String cmsSessionId;
+       private String displayName;
+       private String locale;
+       private Set<String> roles;
+
+       public CmsSessionDescriptor() {
+       }
+
+       public CmsSessionDescriptor(String name, String cmsSessionId, String[] roles, String displayName, String locale) {
+               this.name = name;
+               this.displayName = displayName;
+               this.cmsSessionId = cmsSessionId;
+               this.locale = locale;
+               this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles)));
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public String getDisplayName() {
+               return displayName;
+       }
+
+       public void setDisplayName(String displayName) {
+               this.displayName = displayName;
+       }
+
+       public String getCmsSessionId() {
+               return cmsSessionId;
+       }
+
+       public void setCmsSessionId(String cmsSessionId) {
+               this.cmsSessionId = cmsSessionId;
+       }
+
+       public Boolean isAnonymous() {
+               return name == null;
+       }
+
+       public String getLocale() {
+               return locale;
+       }
+
+       public void setLocale(String locale) {
+               this.locale = locale;
+       }
+
+       @Override
+       public boolean hasRole(String name) {
+               return roles.contains(name);
+       }
+
+       @Override
+       public String[] getRoles() {
+               return roles.toArray(new String[roles.size()]);
+       }
+
+       public void setRoles(String[] roles) {
+               this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles)));
+       }
+
+       @Override
+       public int hashCode() {
+               return cmsSessionId != null ? cmsSessionId.hashCode() : super.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return displayName != null ? displayName : name != null ? name : super.toString();
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java
new file mode 100644 (file)
index 0000000..983202a
--- /dev/null
@@ -0,0 +1,117 @@
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.time.ZonedDateTime;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.argeo.util.naming.NamingUtils;
+import org.osgi.service.useradmin.Authorization;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Provides access to tokens. */
+public class CmsTokenServlet extends HttpServlet {
+       private static final long serialVersionUID = 302918711430864140L;
+
+       public final static String PARAM_EXPIRY_DATE = "expiryDate";
+       public final static String PARAM_TOKEN = "token";
+
+       private final static int DEFAULT_HOURS = 24;
+
+       private CmsUserManager userManager;
+       private ObjectMapper objectMapper = new ObjectMapper();
+
+       @Override
+       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               ServletHttpRequest request = new ServletHttpRequest(req);
+               ServletHttpResponse response = new ServletHttpResponse(resp);
+               LoginContext lc = null;
+               try {
+                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) {
+                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                       for (Callback callback : callbacks) {
+                                               if (callback instanceof RemoteAuthCallback) {
+                                                       ((RemoteAuthCallback) callback).setRequest(request);
+                                                       ((RemoteAuthCallback) callback).setResponse(response);
+                                               }
+                                       }
+                               }
+                       });
+                       lc.login();
+               } catch (LoginException e) {
+                       // ignore
+               }
+
+               try {
+                       Subject subject = lc.getSubject();
+                       Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class));
+                       String token = UUID.randomUUID().toString();
+                       String expiryDateStr = req.getParameter(PARAM_EXPIRY_DATE);
+                       ZonedDateTime expiryDate;
+                       if (expiryDateStr != null) {
+                               expiryDate = NamingUtils.ldapDateToZonedDateTime(expiryDateStr);
+                       } else {
+                               expiryDate = ZonedDateTime.now().plusHours(DEFAULT_HOURS);
+                               expiryDateStr = NamingUtils.instantToLdapDate(expiryDate);
+                       }
+                       userManager.addAuthToken(authorization.getName(), token, expiryDate);
+
+                       TokenDescriptor tokenDescriptor = new TokenDescriptor();
+                       tokenDescriptor.setUsername(authorization.getName());
+                       tokenDescriptor.setToken(token);
+                       tokenDescriptor.setExpiryDate(expiryDateStr);
+//                     tokenDescriptor.setRoles(Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles))));
+
+                       resp.setContentType("application/json");
+                       JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter());
+                       jg.writeObject(tokenDescriptor);
+               } catch (Exception e) {
+                       new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
+               }
+       }
+
+       @Override
+       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               // temporarily wrap POST for ease of testing
+               doPost(req, resp);
+       }
+
+       @Override
+       protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               try {
+                       String token = req.getParameter(PARAM_TOKEN);
+                       userManager.expireAuthToken(token);
+               } catch (Exception e) {
+                       new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
+               }
+       }
+
+       protected <T> T extractFrom(Set<T> creds) {
+               if (creds.size() > 0)
+                       return creds.iterator().next();
+               else
+                       return null;
+       }
+
+       public void setUserManager(CmsUserManager userManager) {
+               this.userManager = userManager;
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java
new file mode 100644 (file)
index 0000000..1541b4f
--- /dev/null
@@ -0,0 +1,49 @@
+package org.argeo.cms.integration;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/** A serializable descriptor of a token. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TokenDescriptor implements Serializable {
+       private static final long serialVersionUID = -6607393871416803324L;
+
+       private String token;
+       private String username;
+       private String expiryDate;
+//     private Set<String> roles;
+
+       public String getToken() {
+               return token;
+       }
+
+       public void setToken(String token) {
+               this.token = token;
+       }
+
+       public String getUsername() {
+               return username;
+       }
+
+       public void setUsername(String username) {
+               this.username = username;
+       }
+
+//     public Set<String> getRoles() {
+//             return roles;
+//     }
+//
+//     public void setRoles(Set<String> roles) {
+//             this.roles = roles;
+//     }
+
+       public String getExpiryDate() {
+               return expiryDate;
+       }
+
+       public void setExpiryDate(String expiryDate) {
+               this.expiryDate = expiryDate;
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java
new file mode 100644 (file)
index 0000000..1405737
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo CMS integration (JSON, web services). */
+package org.argeo.cms.integration;
\ No newline at end of file
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java
new file mode 100644 (file)
index 0000000..9cb48b2
--- /dev/null
@@ -0,0 +1,106 @@
+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.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthUtils;
+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 CmsLog log = CmsLog.getLog(CmsServletContext.class);
+       // use CMS bundle for resources
+       private Bundle bundle = FrameworkUtil.getBundle(getClass());
+
+       public void init(Map<String, String> properties) {
+
+       }
+
+       public void destroy() {
+
+       }
+
+       @Override
+       public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
+               if (log.isTraceEnabled())
+                       HttpUtils.logRequestHeaders(log, request);
+               ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
+               Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+               LoginContext lc;
+               try {
+                       lc = CmsAuth.USER.newLoginContext(
+                                       new RemoteAuthCallbackHandler(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;
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
+               }
+
+               Subject subject = lc.getSubject();
+               // log.debug("SERVLET CONTEXT: "+subject);
+               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;
+                       }
+
+               });
+               return true;
+       }
+
+       @Override
+       public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
+               RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
+       }
+
+       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+               // anonymous
+               ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+               try {
+                       Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+                       LoginContext lc = CmsAuth.ANONYMOUS.newLoginContext(
+                                       new RemoteAuthCallbackHandler(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;
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentContextClassLoader);
+               }
+       }
+
+       @Override
+       public URL getResource(String name) {
+               // TODO make it more robust and versatile
+               // if used directly it can only load from within this bundle
+               return bundle.getResource(name);
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java
new file mode 100644 (file)
index 0000000..3bea0b4
--- /dev/null
@@ -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.ee/src/org/argeo/cms/servlet/ServletHttpRequest.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpRequest.java
new file mode 100644 (file)
index 0000000..54c8804
--- /dev/null
@@ -0,0 +1,67 @@
+package org.argeo.cms.servlet;
+
+import java.util.Locale;
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+
+public class ServletHttpRequest implements RemoteAuthRequest {
+       private final HttpServletRequest request;
+
+       public ServletHttpRequest(HttpServletRequest request) {
+               Objects.requireNonNull(request);
+               this.request = request;
+       }
+
+       @Override
+       public RemoteAuthSession getSession() {
+               HttpSession httpSession = request.getSession(false);
+               if (httpSession == null)
+                       return null;
+               return new ServletHttpSession(httpSession);
+       }
+
+       @Override
+       public RemoteAuthSession createSession() {
+               return new ServletHttpSession(request.getSession(true));
+       }
+
+       @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();
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java
new file mode 100644 (file)
index 0000000..de47365
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.cms.servlet;
+
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class ServletHttpResponse implements RemoteAuthResponse {
+       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.ee/src/org/argeo/cms/servlet/ServletHttpSession.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpSession.java
new file mode 100644 (file)
index 0000000..8d087da
--- /dev/null
@@ -0,0 +1,28 @@
+package org.argeo.cms.servlet;
+
+import org.argeo.cms.auth.RemoteAuthSession;
+
+public class ServletHttpSession implements RemoteAuthSession {
+       private javax.servlet.http.HttpSession session;
+
+       public ServletHttpSession(javax.servlet.http.HttpSession session) {
+               super();
+               this.session = session;
+       }
+
+       @Override
+       public boolean isValid() {
+               try {// test http session
+                       session.getCreationTime();
+                       return true;
+               } catch (IllegalStateException ise) {
+                       return false;
+               }
+       }
+
+       @Override
+       public String getId() {
+               return session.getId();
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/HttpUtils.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/HttpUtils.java
new file mode 100644 (file)
index 0000000..70f2cc6
--- /dev/null
@@ -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.argeo.api.cms.CmsLog;
+
+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(CmsLog 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(CmsLog log, HttpServletRequest request) {
+               if (!log.isDebugEnabled())
+                       return;
+               for (Enumeration<String> 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(CmsLog 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<String> en = request.getHeaderNames();
+               while (en.hasMoreElements()) {
+                       String header = en.nextElement();
+                       Enumeration<String> values = request.getHeaders(header);
+                       while (values.hasMoreElements())
+                               buf.append("  " + header + ": " + values.nextElement());
+                       buf.append('\n');
+               }
+
+               // attributed
+               Enumeration<String> 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.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java
new file mode 100644 (file)
index 0000000..c762b67
--- /dev/null
@@ -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<BundleCapability> packages = frameworkWiring.findProviders(requirement);
+               if (packages.isEmpty()) {
+                       resp.sendError(404);
+                       return;
+               }
+
+               // TODO verify that it works with multiple versions
+               SortedMap<Version, BundleCapability> 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.ee/src/org/argeo/cms/servlet/internal/RobotServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/RobotServlet.java
new file mode 100644 (file)
index 0000000..288ee26
--- /dev/null
@@ -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.ee/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java
new file mode 100644 (file)
index 0000000..46dabc2
--- /dev/null
@@ -0,0 +1,109 @@
+package org.argeo.cms.websocket.javax.server;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.List;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import javax.websocket.server.ServerEndpointConfig.Configurator;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.argeo.cms.servlet.ServletHttpSession;
+
+/**
+ * <strong>Disabled until third party issues are solved.</strong>. Customises
+ * the initialisation of a new web socket.
+ */
+public class CmsWebSocketConfigurator extends Configurator {
+       public final static String WEBSOCKET_SUBJECT = "org.argeo.cms.websocket.subject";
+       public final static String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
+
+       private final static CmsLog log = CmsLog.getLog(CmsWebSocketConfigurator.class);
+       final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+       @Override
+       public boolean checkOrigin(String originHeaderValue) {
+               return true;
+       }
+
+       @Override
+       public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
+               try {
+                       return endpointClass.getDeclaredConstructor().newInstance();
+               } catch (Exception e) {
+                       throw new IllegalArgumentException("Cannot get endpoint instance", e);
+               }
+       }
+
+       @Override
+       public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested) {
+               return requested;
+       }
+
+       @Override
+       public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) {
+               if ((requested == null) || (requested.size() == 0))
+                       return "";
+               if ((supported == null) || (supported.isEmpty()))
+                       return "";
+               for (String possible : requested) {
+                       if (possible == null)
+                               continue;
+                       if (supported.contains(possible))
+                               return possible;
+               }
+               return "";
+       }
+
+       @Override
+       public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
+               if (true)
+                       return;
+
+               RemoteAuthSession httpSession = new ServletHttpSession(
+                               (javax.servlet.http.HttpSession) request.getHttpSession());
+               if (log.isDebugEnabled() && httpSession != null)
+                       log.debug("Web socket HTTP session id: " + httpSession.getId());
+
+               if (httpSession == null) {
+                       rejectResponse(response, null);
+               }
+               try {
+                       LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(httpSession));
+                       lc.login();
+                       if (log.isDebugEnabled())
+                               log.debug("Web socket logged-in as " + lc.getSubject());
+                       Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
+
+                               @Override
+                               public Void run() {
+                                       sec.getUserProperties().put(REMOTE_USER, AccessController.getContext());
+                                       return null;
+                               }
+
+                       });
+               } catch (Exception e) {
+                       rejectResponse(response, e);
+               }
+       }
+
+       /**
+        * Behaviour when the web socket could not be authenticated. Throws an
+        * {@link IllegalStateException} by default.
+        * 
+        * @param e can be null
+        */
+       protected void rejectResponse(HandshakeResponse response, Exception e) {
+               // violent implementation, as suggested in
+               // https://stackoverflow.com/questions/21763829/jsr-356-how-to-abort-a-websocket-connection-during-the-handshake
+//             throw new IllegalStateException("Web socket cannot be authenticated");
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java
new file mode 100644 (file)
index 0000000..e01f6f7
--- /dev/null
@@ -0,0 +1,178 @@
+package org.argeo.cms.websocket.javax.server;
+
+import java.io.IOException;
+import java.security.AccessControlContext;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.integration.CmsExceptionsChain;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.osgi.service.http.context.ServletContextHelper;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Provides WebSocket access. */
+@ServerEndpoint(value = "/ws/test/events/")
+public class TestEndpoint implements EventHandler {
+       private final static CmsLog log = CmsLog.getLog(TestEndpoint.class);
+
+       final static String TOPICS_BASE = "/test";
+       final static String INPUT = "input";
+       final static String TOPIC = "topic";
+       final static String VIEW_UID = "viewUid";
+       final static String COMPUTATION_UID = "computationUid";
+       final static String MESSAGES = "messages";
+       final static String ERRORS = "errors";
+
+       final static String EXCEPTION = "exception";
+       final static String MESSAGE = "message";
+
+       private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
+
+       private String wsSessionId;
+       private RemoteEndpoint.Basic remote;
+       private ServiceRegistration<EventHandler> eventHandlerSr;
+
+       // json
+       private ObjectMapper objectMapper = new ObjectMapper();
+
+       private WebSocketView view;
+
+       @OnOpen
+       public void onWebSocketConnect(Session session) {
+               wsSessionId = session.getId();
+
+               // 24h timeout
+               session.setMaxIdleTimeout(1000 * 60 * 60 * 24);
+
+               Map<String, Object> userProperties = session.getUserProperties();
+               Subject subject = null;
+//             AccessControlContext accessControlContext = (AccessControlContext) userProperties
+//                             .get(ServletContextHelper.REMOTE_USER);
+//             Subject subject = Subject.getSubject(accessControlContext);
+//             // Deal with authentication failure
+//             if (subject == null) {
+//                     try {
+//                             CloseReason.CloseCode closeCode = new CloseReason.CloseCode() {
+//
+//                                     @Override
+//                                     public int getCode() {
+//                                             return 4001;
+//                                     }
+//                             };
+//                             session.close(new CloseReason(closeCode, "Unauthorized"));
+//                             if (log.isTraceEnabled())
+//                                     log.trace("Unauthorized web socket " + wsSessionId + ". Closing with code " + closeCode.getCode()
+//                                                     + ".");
+//                             return;
+//                     } catch (IOException e) {
+//                             // silent
+//                     }
+//                     return;// ignore
+//             }
+
+               if (log.isDebugEnabled())
+                       log.debug("WS#" + wsSessionId + " open for: " + subject);
+               remote = session.getBasicRemote();
+               view = new WebSocketView(subject);
+
+               // OSGi events
+               String[] topics = new String[] { TOPICS_BASE + "/*" };
+               Hashtable<String, Object> ht = new Hashtable<>();
+               ht.put(EventConstants.EVENT_TOPIC, topics);
+               ht.put(EventConstants.EVENT_FILTER, "(" + VIEW_UID + "=" + view.getUid() + ")");
+               eventHandlerSr = bc.registerService(EventHandler.class, this, ht);
+
+               if (log.isDebugEnabled())
+                       log.debug("New view " + view.getUid() + " opened, via web socket.");
+       }
+
+       @OnMessage
+       public void onWebSocketText(Session session, String message) throws JsonMappingException, JsonProcessingException {
+               try {
+                       if (log.isTraceEnabled())
+                               log.trace("WS#" + view.getUid() + " received:\n" + message + "\n");
+//                     JsonNode jsonNode = objectMapper.readTree(message);
+//                     String topic = jsonNode.get(TOPIC).textValue();
+
+                       final String computationUid = null;
+//                     if (MY_TOPIC.equals(topic)) {
+//                             view.checkRole(SPECIFIC_ROLE);
+//                             computationUid= process();
+//                     }
+                       remote.sendText("ACK");
+               } catch (Exception e) {
+                       log.error("Error when receiving web socket message", e);
+                       sendSystemErrorMessage(e);
+               }
+       }
+
+       @OnClose
+       public void onWebSocketClose(CloseReason reason) {
+               if (eventHandlerSr != null)
+                       eventHandlerSr.unregister();
+               if (view != null && log.isDebugEnabled())
+                       log.debug("WS#" + view.getUid() + " closed: " + reason);
+       }
+
+       @OnError
+       public void onWebSocketError(Throwable cause) {
+               if (view != null) {
+                       log.error("WS#" + view.getUid() + " ERROR", cause);
+               } else {
+                       if (log.isTraceEnabled())
+                               log.error("Error in web socket session " + wsSessionId, cause);
+               }
+       }
+
+       @Override
+       public void handleEvent(Event event) {
+               try {
+                       Object uid = event.getProperty(COMPUTATION_UID);
+                       Exception exception = (Exception) event.getProperty(EXCEPTION);
+                       if (exception != null) {
+                               CmsExceptionsChain systemErrors = new CmsExceptionsChain(exception);
+                               String sent = systemErrors.toJsonString(objectMapper);
+                               remote.sendText(sent);
+                               return;
+                       }
+                       String topic = event.getTopic();
+                       if (log.isTraceEnabled())
+                               log.trace("WS#" + view.getUid() + " " + topic + ": notify event " + topic + "#" + uid + ", " + event);
+               } catch (Exception e) {
+                       log.error("Error when handling event for WebSocket", e);
+                       sendSystemErrorMessage(e);
+               }
+
+       }
+
+       /** Sends an error message in JSON format. */
+       protected void sendSystemErrorMessage(Exception e) {
+               CmsExceptionsChain systemErrors = new CmsExceptionsChain(e);
+               try {
+                       if (remote != null)
+                               remote.sendText(systemErrors.toJsonString(objectMapper));
+               } catch (Exception e1) {
+                       log.error("Cannot send WebSocket system error messages " + systemErrors, e1);
+               }
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java
new file mode 100644 (file)
index 0000000..819837b
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.cms.websocket.javax.server;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+
+/** Tests connectivity to the web socket server. */
+public class WebSocketTest {
+
+       public static void main(String[] args) throws Exception {
+               CompletableFuture<Boolean> received = new CompletableFuture<>();
+               WebSocket.Listener listener = new WebSocket.Listener() {
+
+                       public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
+                               System.out.println(message);
+                               CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
+                               received.complete(true);
+                               return res;
+                       }
+               };
+
+               HttpClient client = HttpClient.newHttpClient();
+               CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+                               .buildAsync(URI.create("ws://localhost:7070/ws/test/events/"), listener);
+               WebSocket webSocket = ws.get();
+               webSocket.sendText("TEST", true);
+
+               received.get(10, TimeUnit.SECONDS);
+               webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/WebSocketView.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/WebSocketView.java
new file mode 100644 (file)
index 0000000..a5da88b
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.cms.websocket.javax.server;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.osgi.service.useradmin.Role;
+
+/**
+ * Abstraction of a single Frontend view, that is a web browser page. There can
+ * be multiple views within one single authenticated HTTP session.
+ */
+public class WebSocketView {
+       private final String uid;
+       private Subject subject;
+
+       public WebSocketView(Subject subject) {
+               this.uid = UUID.randomUUID().toString();
+               this.subject = subject;
+       }
+
+       public String getUid() {
+               return uid;
+       }
+
+       public Set<String> getRoles() {
+               return roles(subject);
+       }
+
+       public boolean isInRole(String role) {
+               return getRoles().contains(role);
+       }
+
+       public void checkRole(String role) {
+               checkRole(subject, role);
+       }
+
+       public final static Set<String> roles(Subject subject) {
+               Set<String> roles = new HashSet<String>();
+               X500Principal principal = subject.getPrincipals(X500Principal.class).iterator().next();
+               String username = principal.getName();
+               roles.add(username);
+               for (Principal group : subject.getPrincipals()) {
+                       if (group instanceof Role)
+                               roles.add(group.getName());
+               }
+               return roles;
+       }
+
+       public static void checkRole(Subject subject, String role) {
+               Set<String> roles = roles(subject);
+               if (!roles.contains(role))
+                       throw new IllegalStateException("User is not in role " + role);
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/package-info.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/javax/server/package-info.java
new file mode 100644 (file)
index 0000000..564c881
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo CMS websocket integration. */
+package org.argeo.cms.websocket.javax.server;
\ No newline at end of file
diff --git a/org.argeo.cms.ee4j/.classpath b/org.argeo.cms.ee4j/.classpath
deleted file mode 100644 (file)
index e801ebf..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.cms.ee4j/.project b/org.argeo.cms.ee4j/.project
deleted file mode 100644 (file)
index 9140add..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.ee4j</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ds.core.builder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.cms.ee4j/OSGI-INF/pkgServlet.xml b/org.argeo.cms.ee4j/OSGI-INF/pkgServlet.xml
deleted file mode 100644 (file)
index 00fcaff..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.pkgServlet">
-   <implementation class="org.argeo.cms.servlet.internal.PkgServlet"/>
-   <service>
-      <provide interface="javax.servlet.Servlet"/>
-   </service>
-   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
-   <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=pkgServletContext)"/>
-</scr:component>
diff --git a/org.argeo.cms.ee4j/OSGI-INF/pkgServletContext.xml b/org.argeo.cms.ee4j/OSGI-INF/pkgServletContext.xml
deleted file mode 100644 (file)
index 7540a2c..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.pkgServletContext">
-   <implementation class="org.argeo.cms.servlet.CmsServletContext"/>
-   <service>
-      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
-   </service>
-   <property name="osgi.http.whiteboard.context.name" type="String" value="pkgServletContext"/>
-   <property name="osgi.http.whiteboard.context.path" type="String" value="/pkg"/>
-</scr:component>
diff --git a/org.argeo.cms.ee4j/bnd.bnd b/org.argeo.cms.ee4j/bnd.bnd
deleted file mode 100644 (file)
index 6fae1ea..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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.cms.osgi,\
-javax.servlet.*;version="[3,5)",\
-*
-
-Service-Component:\
-OSGI-INF/pkgServletContext.xml,\
-OSGI-INF/pkgServlet.xml
diff --git a/org.argeo.cms.ee4j/build.properties b/org.argeo.cms.ee4j/build.properties
deleted file mode 100644 (file)
index ee94f53..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               OSGI-INF/jettyServiceFactory.xml
-source.. = src/
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsExceptionsChain.java b/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsExceptionsChain.java
deleted file mode 100644 (file)
index fb289c1..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsLog;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Serialisable wrapper of a {@link Throwable}. */
-public class CmsExceptionsChain {
-       public final static CmsLog log = CmsLog.getLog(CmsExceptionsChain.class);
-
-       private List<SystemException> exceptions = new ArrayList<>();
-
-       public CmsExceptionsChain() {
-               super();
-       }
-
-       public CmsExceptionsChain(Throwable exception) {
-               writeException(exception);
-               if (log.isDebugEnabled())
-                       log.error("Exception chain", exception);
-       }
-
-       public String toJsonString(ObjectMapper objectMapper) {
-               try {
-                       return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
-               } catch (JsonProcessingException e) {
-                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
-               }
-       }
-
-       public void writeAsJson(ObjectMapper objectMapper, Writer writer) {
-               try {
-                       JsonGenerator jg = objectMapper.writerWithDefaultPrettyPrinter().getFactory().createGenerator(writer);
-                       jg.writeObject(this);
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
-               }
-       }
-
-       public void writeAsJson(ObjectMapper objectMapper, HttpServletResponse resp) {
-               try {
-                       resp.setContentType("application/json");
-                       resp.setStatus(500);
-                       writeAsJson(objectMapper, resp.getWriter());
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
-               }
-       }
-
-       /** recursive */
-       protected void writeException(Throwable exception) {
-               SystemException systemException = new SystemException(exception);
-               exceptions.add(systemException);
-               Throwable cause = exception.getCause();
-               if (cause != null)
-                       writeException(cause);
-       }
-
-       public List<SystemException> getExceptions() {
-               return exceptions;
-       }
-
-       public void setExceptions(List<SystemException> exceptions) {
-               this.exceptions = exceptions;
-       }
-
-       /** An exception in the chain. */
-       public static class SystemException {
-               private String type;
-               private String message;
-               private List<String> stackTrace;
-
-               public SystemException() {
-               }
-
-               public SystemException(Throwable exception) {
-                       this.type = exception.getClass().getName();
-                       this.message = exception.getMessage();
-                       this.stackTrace = new ArrayList<>();
-                       StackTraceElement[] elems = exception.getStackTrace();
-                       for (int i = 0; i < elems.length; i++)
-                               stackTrace.add("at " + elems[i].toString());
-               }
-
-               public String getType() {
-                       return type;
-               }
-
-               public void setType(String type) {
-                       this.type = type;
-               }
-
-               public String getMessage() {
-                       return message;
-               }
-
-               public void setMessage(String message) {
-                       this.message = message;
-               }
-
-               public List<String> getStackTrace() {
-                       return stackTrace;
-               }
-
-               public void setStackTrace(List<String> stackTrace) {
-                       this.stackTrace = stackTrace;
-               }
-
-               @Override
-               public String toString() {
-                       return "System exception: " + type + ", " + message + ", " + stackTrace;
-               }
-
-       }
-
-       @Override
-       public String toString() {
-               return exceptions.toString();
-       }
-
-//     public static void main(String[] args) throws Exception {
-//             try {
-//                     try {
-//                             try {
-//                                     testDeeper();
-//                             } catch (Exception e) {
-//                                     throw new Exception("Less deep exception", e);
-//                             }
-//                     } catch (Exception e) {
-//                             throw new RuntimeException("Top exception", e);
-//                     }
-//             } catch (Exception e) {
-//                     CmsExceptionsChain vjeSystemErrors = new CmsExceptionsChain(e);
-//                     ObjectMapper objectMapper = new ObjectMapper();
-//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(vjeSystemErrors));
-//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(e));
-//                     e.printStackTrace();
-//             }
-//     }
-//
-//     static void testDeeper() throws Exception {
-//             throw new IllegalStateException("Deep exception");
-//     }
-
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsLoginServlet.java b/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsLoginServlet.java
deleted file mode 100644 (file)
index 29a3137..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.util.Locale;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.NameCallback;
-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.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsSessionId;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.osgi.service.useradmin.Authorization;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Externally authenticate an http session. */
-public class CmsLoginServlet extends HttpServlet {
-       public final static String PARAM_USERNAME = "username";
-       public final static String PARAM_PASSWORD = "password";
-
-       private static final long serialVersionUID = 2478080654328751539L;
-       private ObjectMapper objectMapper = new ObjectMapper();
-
-       @Override
-       protected void doGet(HttpServletRequest request, HttpServletResponse response)
-                       throws ServletException, IOException {
-               doPost(request, response);
-       }
-
-       @Override
-       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               LoginContext lc = null;
-               String username = req.getParameter(PARAM_USERNAME);
-               String password = req.getParameter(PARAM_PASSWORD);
-               ServletHttpRequest request = new ServletHttpRequest(req);
-               ServletHttpResponse response = new ServletHttpResponse(resp);
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) {
-                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                       for (Callback callback : callbacks) {
-                                               if (callback instanceof NameCallback && username != null)
-                                                       ((NameCallback) callback).setName(username);
-                                               else if (callback instanceof PasswordCallback && password != null)
-                                                       ((PasswordCallback) callback).setPassword(password.toCharArray());
-                                               else if (callback instanceof RemoteAuthCallback) {
-                                                       ((RemoteAuthCallback) callback).setRequest(request);
-                                                       ((RemoteAuthCallback) callback).setResponse(response);
-                                               }
-                                       }
-                               }
-                       });
-                       lc.login();
-
-                       Subject subject = lc.getSubject();
-                       CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class));
-                       if (cmsSessionId == null) {
-                               resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-                               return;
-                       }
-                       Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class));
-                       Locale locale = extractFrom(subject.getPublicCredentials(Locale.class));
-
-                       CmsSessionDescriptor cmsSessionDescriptor = new CmsSessionDescriptor(authorization.getName(),
-                                       cmsSessionId.getUuid().toString(), authorization.getRoles(), authorization.toString(),
-                                       locale != null ? locale.toString() : null);
-
-                       resp.setContentType("application/json");
-                       JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter());
-                       jg.writeObject(cmsSessionDescriptor);
-
-                       String redirectTo = redirectTo(req);
-                       if (redirectTo != null)
-                               resp.sendRedirect(redirectTo);
-               } catch (LoginException e) {
-                       resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-                       return;
-               }
-       }
-
-       protected <T> T extractFrom(Set<T> creds) {
-               if (creds.size() > 0)
-                       return creds.iterator().next();
-               else
-                       return null;
-       }
-
-       /**
-        * To be overridden in order to return a richer {@link CmsSessionDescriptor} to
-        * be serialized.
-        */
-       protected CmsSessionDescriptor enrichJson(CmsSessionDescriptor cmsSessionDescriptor) {
-               return cmsSessionDescriptor;
-       }
-
-       protected String redirectTo(HttpServletRequest request) {
-               return null;
-       }
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsLogoutServlet.java b/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsLogoutServlet.java
deleted file mode 100644 (file)
index 0628eae..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsSessionId;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-
-/** Externally authenticate an http session. */
-public class CmsLogoutServlet extends HttpServlet {
-       private static final long serialVersionUID = 2478080654328751539L;
-
-       @Override
-       protected void doGet(HttpServletRequest request, HttpServletResponse response)
-                       throws ServletException, IOException {
-               doPost(request, response);
-       }
-
-       @Override
-       protected void doPost(HttpServletRequest request, HttpServletResponse response)
-                       throws ServletException, IOException {
-               ServletHttpRequest httpRequest = new ServletHttpRequest(request);
-               ServletHttpResponse httpResponse = new ServletHttpResponse(response);
-               LoginContext lc = null;
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
-                                       new RemoteAuthCallbackHandler(httpRequest, httpResponse) {
-                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                                       for (Callback callback : callbacks) {
-                                                               if (callback instanceof RemoteAuthCallback) {
-                                                                       ((RemoteAuthCallback) callback).setRequest(httpRequest);
-                                                                       ((RemoteAuthCallback) callback).setResponse(httpResponse);
-                                                               }
-                                                       }
-                                               }
-                                       });
-                       lc.login();
-
-                       Subject subject = lc.getSubject();
-                       CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class));
-                       if (cmsSessionId != null) {// logged in
-                               CurrentUser.logoutCmsSession(subject);
-                       }
-
-               } catch (LoginException e) {
-                       // ignore
-               }
-
-               String redirectTo = redirectTo(request);
-               if (redirectTo != null)
-                       response.sendRedirect(redirectTo);
-       }
-
-       protected <T> T extractFrom(Set<T> creds) {
-               if (creds.size() > 0)
-                       return creds.iterator().next();
-               else
-                       return null;
-       }
-
-       protected String redirectTo(HttpServletRequest request) {
-               return null;
-       }
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsPrivateServletContext.java
deleted file mode 100644 (file)
index cec04d2..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.security.AccessControlContext;
-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.argeo.api.cms.CmsAuth;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.osgi.service.http.context.ServletContextHelper;
-
-/** Manages security access to servlets. */
-public class CmsPrivateServletContext extends ServletContextHelper {
-       public final static String LOGIN_PAGE = "argeo.cms.integration.loginPage";
-       public final static String LOGIN_SERVLET = "argeo.cms.integration.loginServlet";
-       private String loginPage;
-       private String loginServlet;
-
-       public void init(Map<String, String> properties) {
-               loginPage = properties.get(LOGIN_PAGE);
-               loginServlet = properties.get(LOGIN_SERVLET);
-       }
-
-       /**
-        * Add the {@link AccessControlContext} as a request attribute, or redirect to
-        * the login page.
-        */
-       @Override
-       public boolean handleSecurity(final HttpServletRequest req, HttpServletResponse resp) throws IOException {
-               LoginContext lc = null;
-               ServletHttpRequest request = new ServletHttpRequest(req);
-               ServletHttpResponse response = new ServletHttpResponse(resp);
-
-               String pathInfo = req.getPathInfo();
-               String servletPath = req.getServletPath();
-               if ((pathInfo != null && (servletPath + pathInfo).equals(loginPage)) || servletPath.contentEquals(loginServlet))
-                       return true;
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response));
-                       lc.login();
-               } catch (LoginException e) {
-                       lc = processUnauthorized(req, resp);
-                       if (lc == null)
-                               return false;
-               }
-               Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               // TODO also set login context in order to log out ?
-                               RemoteAuthUtils.configureRequestSecurity(request);
-                               return null;
-                       }
-
-               });
-
-               return true;
-       }
-
-       @Override
-       public void finishSecurity(HttpServletRequest req, HttpServletResponse resp) {
-               RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(req));
-       }
-
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               try {
-                       response.sendRedirect(loginPage);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot redirect to login page", e);
-               }
-               return null;
-       }
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsSessionDescriptor.java b/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsSessionDescriptor.java
deleted file mode 100644 (file)
index 30de616..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.argeo.api.cms.CmsSession;
-import org.osgi.service.useradmin.Authorization;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-/** A serializable descriptor of an internal {@link CmsSession}. */
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class CmsSessionDescriptor implements Serializable, Authorization {
-       private static final long serialVersionUID = 8592162323372641462L;
-
-       private String name;
-       private String cmsSessionId;
-       private String displayName;
-       private String locale;
-       private Set<String> roles;
-
-       public CmsSessionDescriptor() {
-       }
-
-       public CmsSessionDescriptor(String name, String cmsSessionId, String[] roles, String displayName, String locale) {
-               this.name = name;
-               this.displayName = displayName;
-               this.cmsSessionId = cmsSessionId;
-               this.locale = locale;
-               this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles)));
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public void setName(String name) {
-               this.name = name;
-       }
-
-       public String getDisplayName() {
-               return displayName;
-       }
-
-       public void setDisplayName(String displayName) {
-               this.displayName = displayName;
-       }
-
-       public String getCmsSessionId() {
-               return cmsSessionId;
-       }
-
-       public void setCmsSessionId(String cmsSessionId) {
-               this.cmsSessionId = cmsSessionId;
-       }
-
-       public Boolean isAnonymous() {
-               return name == null;
-       }
-
-       public String getLocale() {
-               return locale;
-       }
-
-       public void setLocale(String locale) {
-               this.locale = locale;
-       }
-
-       @Override
-       public boolean hasRole(String name) {
-               return roles.contains(name);
-       }
-
-       @Override
-       public String[] getRoles() {
-               return roles.toArray(new String[roles.size()]);
-       }
-
-       public void setRoles(String[] roles) {
-               this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles)));
-       }
-
-       @Override
-       public int hashCode() {
-               return cmsSessionId != null ? cmsSessionId.hashCode() : super.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return displayName != null ? displayName : name != null ? name : super.toString();
-       }
-
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsTokenServlet.java b/org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsTokenServlet.java
deleted file mode 100644 (file)
index 983202a..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.time.ZonedDateTime;
-import java.util.Set;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.argeo.util.naming.NamingUtils;
-import org.osgi.service.useradmin.Authorization;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Provides access to tokens. */
-public class CmsTokenServlet extends HttpServlet {
-       private static final long serialVersionUID = 302918711430864140L;
-
-       public final static String PARAM_EXPIRY_DATE = "expiryDate";
-       public final static String PARAM_TOKEN = "token";
-
-       private final static int DEFAULT_HOURS = 24;
-
-       private CmsUserManager userManager;
-       private ObjectMapper objectMapper = new ObjectMapper();
-
-       @Override
-       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               ServletHttpRequest request = new ServletHttpRequest(req);
-               ServletHttpResponse response = new ServletHttpResponse(resp);
-               LoginContext lc = null;
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) {
-                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                       for (Callback callback : callbacks) {
-                                               if (callback instanceof RemoteAuthCallback) {
-                                                       ((RemoteAuthCallback) callback).setRequest(request);
-                                                       ((RemoteAuthCallback) callback).setResponse(response);
-                                               }
-                                       }
-                               }
-                       });
-                       lc.login();
-               } catch (LoginException e) {
-                       // ignore
-               }
-
-               try {
-                       Subject subject = lc.getSubject();
-                       Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class));
-                       String token = UUID.randomUUID().toString();
-                       String expiryDateStr = req.getParameter(PARAM_EXPIRY_DATE);
-                       ZonedDateTime expiryDate;
-                       if (expiryDateStr != null) {
-                               expiryDate = NamingUtils.ldapDateToZonedDateTime(expiryDateStr);
-                       } else {
-                               expiryDate = ZonedDateTime.now().plusHours(DEFAULT_HOURS);
-                               expiryDateStr = NamingUtils.instantToLdapDate(expiryDate);
-                       }
-                       userManager.addAuthToken(authorization.getName(), token, expiryDate);
-
-                       TokenDescriptor tokenDescriptor = new TokenDescriptor();
-                       tokenDescriptor.setUsername(authorization.getName());
-                       tokenDescriptor.setToken(token);
-                       tokenDescriptor.setExpiryDate(expiryDateStr);
-//                     tokenDescriptor.setRoles(Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles))));
-
-                       resp.setContentType("application/json");
-                       JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter());
-                       jg.writeObject(tokenDescriptor);
-               } catch (Exception e) {
-                       new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
-               }
-       }
-
-       @Override
-       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               // temporarily wrap POST for ease of testing
-               doPost(req, resp);
-       }
-
-       @Override
-       protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               try {
-                       String token = req.getParameter(PARAM_TOKEN);
-                       userManager.expireAuthToken(token);
-               } catch (Exception e) {
-                       new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
-               }
-       }
-
-       protected <T> T extractFrom(Set<T> creds) {
-               if (creds.size() > 0)
-                       return creds.iterator().next();
-               else
-                       return null;
-       }
-
-       public void setUserManager(CmsUserManager userManager) {
-               this.userManager = userManager;
-       }
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/integration/TokenDescriptor.java b/org.argeo.cms.ee4j/src/org/argeo/cms/integration/TokenDescriptor.java
deleted file mode 100644 (file)
index 1541b4f..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.Serializable;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-/** A serializable descriptor of a token. */
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class TokenDescriptor implements Serializable {
-       private static final long serialVersionUID = -6607393871416803324L;
-
-       private String token;
-       private String username;
-       private String expiryDate;
-//     private Set<String> roles;
-
-       public String getToken() {
-               return token;
-       }
-
-       public void setToken(String token) {
-               this.token = token;
-       }
-
-       public String getUsername() {
-               return username;
-       }
-
-       public void setUsername(String username) {
-               this.username = username;
-       }
-
-//     public Set<String> getRoles() {
-//             return roles;
-//     }
-//
-//     public void setRoles(Set<String> roles) {
-//             this.roles = roles;
-//     }
-
-       public String getExpiryDate() {
-               return expiryDate;
-       }
-
-       public void setExpiryDate(String expiryDate) {
-               this.expiryDate = expiryDate;
-       }
-
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/integration/package-info.java b/org.argeo.cms.ee4j/src/org/argeo/cms/integration/package-info.java
deleted file mode 100644 (file)
index 1405737..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS integration (JSON, web services). */
-package org.argeo.cms.integration;
\ No newline at end of file
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/CmsServletContext.java
deleted file mode 100644 (file)
index 9cb48b2..0000000
+++ /dev/null
@@ -1,106 +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.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.auth.RemoteAuthUtils;
-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 CmsLog log = CmsLog.getLog(CmsServletContext.class);
-       // use CMS bundle for resources
-       private Bundle bundle = FrameworkUtil.getBundle(getClass());
-
-       public void init(Map<String, String> properties) {
-
-       }
-
-       public void destroy() {
-
-       }
-
-       @Override
-       public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
-               if (log.isTraceEnabled())
-                       HttpUtils.logRequestHeaders(log, request);
-               ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
-               Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
-               LoginContext lc;
-               try {
-                       lc = CmsAuth.USER.newLoginContext(
-                                       new RemoteAuthCallbackHandler(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;
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
-               }
-
-               Subject subject = lc.getSubject();
-               // log.debug("SERVLET CONTEXT: "+subject);
-               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;
-                       }
-
-               });
-               return true;
-       }
-
-       @Override
-       public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
-               RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
-       }
-
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               // anonymous
-               ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
-               try {
-                       Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
-                       LoginContext lc = CmsAuth.ANONYMOUS.newLoginContext(
-                                       new RemoteAuthCallbackHandler(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;
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentContextClassLoader);
-               }
-       }
-
-       @Override
-       public URL getResource(String name) {
-               // TODO make it more robust and versatile
-               // if used directly it can only load from within this bundle
-               return bundle.getResource(name);
-       }
-
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java
deleted file mode 100644 (file)
index 3bea0b4..0000000
+++ /dev/null
@@ -1,39 +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.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.ee4j/src/org/argeo/cms/servlet/ServletHttpRequest.java b/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpRequest.java
deleted file mode 100644 (file)
index 54c8804..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.argeo.cms.servlet;
-
-import java.util.Locale;
-import java.util.Objects;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
-import org.argeo.cms.auth.RemoteAuthRequest;
-import org.argeo.cms.auth.RemoteAuthSession;
-
-public class ServletHttpRequest implements RemoteAuthRequest {
-       private final HttpServletRequest request;
-
-       public ServletHttpRequest(HttpServletRequest request) {
-               Objects.requireNonNull(request);
-               this.request = request;
-       }
-
-       @Override
-       public RemoteAuthSession getSession() {
-               HttpSession httpSession = request.getSession(false);
-               if (httpSession == null)
-                       return null;
-               return new ServletHttpSession(httpSession);
-       }
-
-       @Override
-       public RemoteAuthSession createSession() {
-               return new ServletHttpSession(request.getSession(true));
-       }
-
-       @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();
-       }
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpResponse.java b/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpResponse.java
deleted file mode 100644 (file)
index de47365..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.cms.servlet;
-
-import java.util.Objects;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.cms.auth.RemoteAuthResponse;
-
-public class ServletHttpResponse implements RemoteAuthResponse {
-       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.ee4j/src/org/argeo/cms/servlet/ServletHttpSession.java b/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpSession.java
deleted file mode 100644 (file)
index 8d087da..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.argeo.cms.servlet;
-
-import org.argeo.cms.auth.RemoteAuthSession;
-
-public class ServletHttpSession implements RemoteAuthSession {
-       private javax.servlet.http.HttpSession session;
-
-       public ServletHttpSession(javax.servlet.http.HttpSession session) {
-               super();
-               this.session = session;
-       }
-
-       @Override
-       public boolean isValid() {
-               try {// test http session
-                       session.getCreationTime();
-                       return true;
-               } catch (IllegalStateException ise) {
-                       return false;
-               }
-       }
-
-       @Override
-       public String getId() {
-               return session.getId();
-       }
-
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/HttpUtils.java b/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/HttpUtils.java
deleted file mode 100644 (file)
index 70f2cc6..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.argeo.cms.servlet.internal;
-
-import java.util.Enumeration;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsLog;
-
-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(CmsLog 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(CmsLog log, HttpServletRequest request) {
-               if (!log.isDebugEnabled())
-                       return;
-               for (Enumeration<String> 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(CmsLog 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<String> en = request.getHeaderNames();
-               while (en.hasMoreElements()) {
-                       String header = en.nextElement();
-                       Enumeration<String> values = request.getHeaders(header);
-                       while (values.hasMoreElements())
-                               buf.append("  " + header + ": " + values.nextElement());
-                       buf.append('\n');
-               }
-
-               // attributed
-               Enumeration<String> 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.ee4j/src/org/argeo/cms/servlet/internal/PkgServlet.java b/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/PkgServlet.java
deleted file mode 100644 (file)
index c762b67..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-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<BundleCapability> packages = frameworkWiring.findProviders(requirement);
-               if (packages.isEmpty()) {
-                       resp.sendError(404);
-                       return;
-               }
-
-               // TODO verify that it works with multiple versions
-               SortedMap<Version, BundleCapability> 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.ee4j/src/org/argeo/cms/servlet/internal/RobotServlet.java b/org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/RobotServlet.java
deleted file mode 100644 (file)
index 288ee26..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-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.ee4j/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java b/org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java
deleted file mode 100644 (file)
index 46dabc2..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-package org.argeo.cms.websocket.javax.server;
-
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.List;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.websocket.Extension;
-import javax.websocket.HandshakeResponse;
-import javax.websocket.server.HandshakeRequest;
-import javax.websocket.server.ServerEndpointConfig;
-import javax.websocket.server.ServerEndpointConfig.Configurator;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.auth.RemoteAuthSession;
-import org.argeo.cms.servlet.ServletHttpSession;
-
-/**
- * <strong>Disabled until third party issues are solved.</strong>. Customises
- * the initialisation of a new web socket.
- */
-public class CmsWebSocketConfigurator extends Configurator {
-       public final static String WEBSOCKET_SUBJECT = "org.argeo.cms.websocket.subject";
-       public final static String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
-
-       private final static CmsLog log = CmsLog.getLog(CmsWebSocketConfigurator.class);
-       final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
-       @Override
-       public boolean checkOrigin(String originHeaderValue) {
-               return true;
-       }
-
-       @Override
-       public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
-               try {
-                       return endpointClass.getDeclaredConstructor().newInstance();
-               } catch (Exception e) {
-                       throw new IllegalArgumentException("Cannot get endpoint instance", e);
-               }
-       }
-
-       @Override
-       public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested) {
-               return requested;
-       }
-
-       @Override
-       public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) {
-               if ((requested == null) || (requested.size() == 0))
-                       return "";
-               if ((supported == null) || (supported.isEmpty()))
-                       return "";
-               for (String possible : requested) {
-                       if (possible == null)
-                               continue;
-                       if (supported.contains(possible))
-                               return possible;
-               }
-               return "";
-       }
-
-       @Override
-       public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
-               if (true)
-                       return;
-
-               RemoteAuthSession httpSession = new ServletHttpSession(
-                               (javax.servlet.http.HttpSession) request.getHttpSession());
-               if (log.isDebugEnabled() && httpSession != null)
-                       log.debug("Web socket HTTP session id: " + httpSession.getId());
-
-               if (httpSession == null) {
-                       rejectResponse(response, null);
-               }
-               try {
-                       LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(httpSession));
-                       lc.login();
-                       if (log.isDebugEnabled())
-                               log.debug("Web socket logged-in as " + lc.getSubject());
-                       Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
-
-                               @Override
-                               public Void run() {
-                                       sec.getUserProperties().put(REMOTE_USER, AccessController.getContext());
-                                       return null;
-                               }
-
-                       });
-               } catch (Exception e) {
-                       rejectResponse(response, e);
-               }
-       }
-
-       /**
-        * Behaviour when the web socket could not be authenticated. Throws an
-        * {@link IllegalStateException} by default.
-        * 
-        * @param e can be null
-        */
-       protected void rejectResponse(HandshakeResponse response, Exception e) {
-               // violent implementation, as suggested in
-               // https://stackoverflow.com/questions/21763829/jsr-356-how-to-abort-a-websocket-connection-during-the-handshake
-//             throw new IllegalStateException("Web socket cannot be authenticated");
-       }
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java b/org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java
deleted file mode 100644 (file)
index e01f6f7..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-package org.argeo.cms.websocket.javax.server;
-
-import java.io.IOException;
-import java.security.AccessControlContext;
-import java.util.Hashtable;
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.websocket.CloseReason;
-import javax.websocket.OnClose;
-import javax.websocket.OnError;
-import javax.websocket.OnMessage;
-import javax.websocket.OnOpen;
-import javax.websocket.RemoteEndpoint;
-import javax.websocket.Session;
-import javax.websocket.server.ServerEndpoint;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.integration.CmsExceptionsChain;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventConstants;
-import org.osgi.service.event.EventHandler;
-import org.osgi.service.http.context.ServletContextHelper;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Provides WebSocket access. */
-@ServerEndpoint(value = "/ws/test/events/")
-public class TestEndpoint implements EventHandler {
-       private final static CmsLog log = CmsLog.getLog(TestEndpoint.class);
-
-       final static String TOPICS_BASE = "/test";
-       final static String INPUT = "input";
-       final static String TOPIC = "topic";
-       final static String VIEW_UID = "viewUid";
-       final static String COMPUTATION_UID = "computationUid";
-       final static String MESSAGES = "messages";
-       final static String ERRORS = "errors";
-
-       final static String EXCEPTION = "exception";
-       final static String MESSAGE = "message";
-
-       private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
-
-       private String wsSessionId;
-       private RemoteEndpoint.Basic remote;
-       private ServiceRegistration<EventHandler> eventHandlerSr;
-
-       // json
-       private ObjectMapper objectMapper = new ObjectMapper();
-
-       private WebSocketView view;
-
-       @OnOpen
-       public void onWebSocketConnect(Session session) {
-               wsSessionId = session.getId();
-
-               // 24h timeout
-               session.setMaxIdleTimeout(1000 * 60 * 60 * 24);
-
-               Map<String, Object> userProperties = session.getUserProperties();
-               Subject subject = null;
-//             AccessControlContext accessControlContext = (AccessControlContext) userProperties
-//                             .get(ServletContextHelper.REMOTE_USER);
-//             Subject subject = Subject.getSubject(accessControlContext);
-//             // Deal with authentication failure
-//             if (subject == null) {
-//                     try {
-//                             CloseReason.CloseCode closeCode = new CloseReason.CloseCode() {
-//
-//                                     @Override
-//                                     public int getCode() {
-//                                             return 4001;
-//                                     }
-//                             };
-//                             session.close(new CloseReason(closeCode, "Unauthorized"));
-//                             if (log.isTraceEnabled())
-//                                     log.trace("Unauthorized web socket " + wsSessionId + ". Closing with code " + closeCode.getCode()
-//                                                     + ".");
-//                             return;
-//                     } catch (IOException e) {
-//                             // silent
-//                     }
-//                     return;// ignore
-//             }
-
-               if (log.isDebugEnabled())
-                       log.debug("WS#" + wsSessionId + " open for: " + subject);
-               remote = session.getBasicRemote();
-               view = new WebSocketView(subject);
-
-               // OSGi events
-               String[] topics = new String[] { TOPICS_BASE + "/*" };
-               Hashtable<String, Object> ht = new Hashtable<>();
-               ht.put(EventConstants.EVENT_TOPIC, topics);
-               ht.put(EventConstants.EVENT_FILTER, "(" + VIEW_UID + "=" + view.getUid() + ")");
-               eventHandlerSr = bc.registerService(EventHandler.class, this, ht);
-
-               if (log.isDebugEnabled())
-                       log.debug("New view " + view.getUid() + " opened, via web socket.");
-       }
-
-       @OnMessage
-       public void onWebSocketText(Session session, String message) throws JsonMappingException, JsonProcessingException {
-               try {
-                       if (log.isTraceEnabled())
-                               log.trace("WS#" + view.getUid() + " received:\n" + message + "\n");
-//                     JsonNode jsonNode = objectMapper.readTree(message);
-//                     String topic = jsonNode.get(TOPIC).textValue();
-
-                       final String computationUid = null;
-//                     if (MY_TOPIC.equals(topic)) {
-//                             view.checkRole(SPECIFIC_ROLE);
-//                             computationUid= process();
-//                     }
-                       remote.sendText("ACK");
-               } catch (Exception e) {
-                       log.error("Error when receiving web socket message", e);
-                       sendSystemErrorMessage(e);
-               }
-       }
-
-       @OnClose
-       public void onWebSocketClose(CloseReason reason) {
-               if (eventHandlerSr != null)
-                       eventHandlerSr.unregister();
-               if (view != null && log.isDebugEnabled())
-                       log.debug("WS#" + view.getUid() + " closed: " + reason);
-       }
-
-       @OnError
-       public void onWebSocketError(Throwable cause) {
-               if (view != null) {
-                       log.error("WS#" + view.getUid() + " ERROR", cause);
-               } else {
-                       if (log.isTraceEnabled())
-                               log.error("Error in web socket session " + wsSessionId, cause);
-               }
-       }
-
-       @Override
-       public void handleEvent(Event event) {
-               try {
-                       Object uid = event.getProperty(COMPUTATION_UID);
-                       Exception exception = (Exception) event.getProperty(EXCEPTION);
-                       if (exception != null) {
-                               CmsExceptionsChain systemErrors = new CmsExceptionsChain(exception);
-                               String sent = systemErrors.toJsonString(objectMapper);
-                               remote.sendText(sent);
-                               return;
-                       }
-                       String topic = event.getTopic();
-                       if (log.isTraceEnabled())
-                               log.trace("WS#" + view.getUid() + " " + topic + ": notify event " + topic + "#" + uid + ", " + event);
-               } catch (Exception e) {
-                       log.error("Error when handling event for WebSocket", e);
-                       sendSystemErrorMessage(e);
-               }
-
-       }
-
-       /** Sends an error message in JSON format. */
-       protected void sendSystemErrorMessage(Exception e) {
-               CmsExceptionsChain systemErrors = new CmsExceptionsChain(e);
-               try {
-                       if (remote != null)
-                               remote.sendText(systemErrors.toJsonString(objectMapper));
-               } catch (Exception e1) {
-                       log.error("Cannot send WebSocket system error messages " + systemErrors, e1);
-               }
-       }
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java b/org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java
deleted file mode 100644 (file)
index 819837b..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.cms.websocket.javax.server;
-
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.WebSocket;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.TimeUnit;
-
-/** Tests connectivity to the web socket server. */
-public class WebSocketTest {
-
-       public static void main(String[] args) throws Exception {
-               CompletableFuture<Boolean> received = new CompletableFuture<>();
-               WebSocket.Listener listener = new WebSocket.Listener() {
-
-                       public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
-                               System.out.println(message);
-                               CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
-                               received.complete(true);
-                               return res;
-                       }
-               };
-
-               HttpClient client = HttpClient.newHttpClient();
-               CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
-                               .buildAsync(URI.create("ws://localhost:7070/ws/test/events/"), listener);
-               WebSocket webSocket = ws.get();
-               webSocket.sendText("TEST", true);
-
-               received.get(10, TimeUnit.SECONDS);
-               webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
-       }
-
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketView.java b/org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketView.java
deleted file mode 100644 (file)
index a5da88b..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.argeo.cms.websocket.javax.server;
-
-import java.security.Principal;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.x500.X500Principal;
-
-import org.osgi.service.useradmin.Role;
-
-/**
- * Abstraction of a single Frontend view, that is a web browser page. There can
- * be multiple views within one single authenticated HTTP session.
- */
-public class WebSocketView {
-       private final String uid;
-       private Subject subject;
-
-       public WebSocketView(Subject subject) {
-               this.uid = UUID.randomUUID().toString();
-               this.subject = subject;
-       }
-
-       public String getUid() {
-               return uid;
-       }
-
-       public Set<String> getRoles() {
-               return roles(subject);
-       }
-
-       public boolean isInRole(String role) {
-               return getRoles().contains(role);
-       }
-
-       public void checkRole(String role) {
-               checkRole(subject, role);
-       }
-
-       public final static Set<String> roles(Subject subject) {
-               Set<String> roles = new HashSet<String>();
-               X500Principal principal = subject.getPrincipals(X500Principal.class).iterator().next();
-               String username = principal.getName();
-               roles.add(username);
-               for (Principal group : subject.getPrincipals()) {
-                       if (group instanceof Role)
-                               roles.add(group.getName());
-               }
-               return roles;
-       }
-
-       public static void checkRole(Subject subject, String role) {
-               Set<String> roles = roles(subject);
-               if (!roles.contains(role))
-                       throw new IllegalStateException("User is not in role " + role);
-       }
-
-}
diff --git a/org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/package-info.java b/org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/package-info.java
deleted file mode 100644 (file)
index 564c881..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS websocket integration. */
-package org.argeo.cms.websocket.javax.server;
\ No newline at end of file