Refactor non-SWT projects.
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 10 Jul 2022 06:57:09 +0000 (08:57 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 10 Jul 2022 06:57:09 +0000 (08:57 +0200)
138 files changed:
Makefile
eclipse/org.argeo.cms.servlet/.classpath [deleted file]
eclipse/org.argeo.cms.servlet/.project [deleted file]
eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml [deleted file]
eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml [deleted file]
eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml [deleted file]
eclipse/org.argeo.cms.servlet/bnd.bnd [deleted file]
eclipse/org.argeo.cms.servlet/build.properties [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsExceptionsChain.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsLoginServlet.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsLogoutServlet.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsPrivateServletContext.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsSessionDescriptor.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsTokenServlet.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/TokenDescriptor.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/package-info.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/WebSocketView.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/package-info.java [deleted file]
eclipse/org.argeo.ext.equinox.jetty/.classpath [deleted file]
eclipse/org.argeo.ext.equinox.jetty/.gitignore [deleted file]
eclipse/org.argeo.ext.equinox.jetty/.project [deleted file]
eclipse/org.argeo.ext.equinox.jetty/META-INF/.gitignore [deleted file]
eclipse/org.argeo.ext.equinox.jetty/bnd.bnd [deleted file]
eclipse/org.argeo.ext.equinox.jetty/build.properties [deleted file]
eclipse/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java [deleted file]
eclipse/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/package-info.java [deleted file]
org.argeo.cms.ee4j/.classpath [new file with mode: 0644]
org.argeo.cms.ee4j/.project [new file with mode: 0644]
org.argeo.cms.ee4j/OSGI-INF/pkgServlet.xml [new file with mode: 0644]
org.argeo.cms.ee4j/OSGI-INF/pkgServletContext.xml [new file with mode: 0644]
org.argeo.cms.ee4j/bnd.bnd [new file with mode: 0644]
org.argeo.cms.ee4j/build.properties [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsExceptionsChain.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsLoginServlet.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsLogoutServlet.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsPrivateServletContext.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsSessionDescriptor.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/CmsTokenServlet.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/TokenDescriptor.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/integration/package-info.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/CmsServletContext.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpRequest.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpResponse.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/ServletHttpSession.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/HttpUtils.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/PkgServlet.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/servlet/internal/RobotServlet.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketView.java [new file with mode: 0644]
org.argeo.cms.ee4j/src/org/argeo/cms/websocket/javax/server/package-info.java [new file with mode: 0644]
org.argeo.cms.lib.equinox/.classpath [new file with mode: 0644]
org.argeo.cms.lib.equinox/.gitignore [new file with mode: 0644]
org.argeo.cms.lib.equinox/.project [new file with mode: 0644]
org.argeo.cms.lib.equinox/META-INF/.gitignore [new file with mode: 0644]
org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml [new file with mode: 0644]
org.argeo.cms.lib.equinox/bnd.bnd [new file with mode: 0644]
org.argeo.cms.lib.equinox/build.properties [new file with mode: 0644]
org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java [new file with mode: 0644]
org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java [new file with mode: 0644]
org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java [new file with mode: 0644]
org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java [new file with mode: 0644]
org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/.classpath [new file with mode: 0644]
org.argeo.cms.lib.jetty/.project [new file with mode: 0644]
org.argeo.cms.lib.jetty/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
org.argeo.cms.lib.jetty/.settings/org.eclipse.pde.core.prefs [new file with mode: 0644]
org.argeo.cms.lib.jetty/bnd.bnd [new file with mode: 0644]
org.argeo.cms.lib.jetty/build.properties [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/lib/jetty/CmsJettyServer.java [new file with mode: 0644]
org.argeo.cms.lib.pgsql/.classpath [new file with mode: 0644]
org.argeo.cms.lib.pgsql/.project [new file with mode: 0644]
org.argeo.cms.lib.pgsql/bnd.bnd [new file with mode: 0644]
org.argeo.cms.lib.pgsql/build.properties [new file with mode: 0644]
org.argeo.cms.lib.pgsql/src/org/argeo/cms/sql/postgres/CheckPg.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/.classpath [new file with mode: 0644]
org.argeo.cms.lib.sshd/.gitignore [new file with mode: 0644]
org.argeo.cms.lib.sshd/.project [new file with mode: 0644]
org.argeo.cms.lib.sshd/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
org.argeo.cms.lib.sshd/OSGI-INF/cmsSshServer.xml [new file with mode: 0644]
org.argeo.cms.lib.sshd/bnd.bnd [new file with mode: 0644]
org.argeo.cms.lib.sshd/build.properties [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/BasicSshServer.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Sftp.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Ssh.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshKeyPair.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshSync.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshCli.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshShell.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/package-info.java [new file with mode: 0644]
org.argeo.cms.sql/.classpath [deleted file]
org.argeo.cms.sql/.project [deleted file]
org.argeo.cms.sql/bnd.bnd [deleted file]
org.argeo.cms.sql/build.properties [deleted file]
org.argeo.cms.sql/src/org/argeo/cms/sql/postgres/CheckPg.java [deleted file]
org.argeo.cms.ssh/.classpath [deleted file]
org.argeo.cms.ssh/.gitignore [deleted file]
org.argeo.cms.ssh/.project [deleted file]
org.argeo.cms.ssh/.settings/org.eclipse.jdt.core.prefs [deleted file]
org.argeo.cms.ssh/OSGI-INF/cmsSshServer.xml [deleted file]
org.argeo.cms.ssh/bnd.bnd [deleted file]
org.argeo.cms.ssh/build.properties [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/AbstractSsh.java [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/BasicSshServer.java [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/CmsSshServer.java [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/Sftp.java [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/Ssh.java [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshKeyPair.java [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshSync.java [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/cli/SshCli.java [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/cli/SshShell.java [deleted file]
org.argeo.cms.ssh/src/org/argeo/cms/ssh/package-info.java [deleted file]
org.argeo.cms.ux/src/org/argeo/cms/ux/cli/FileSync.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/cli/FsCommands.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/fs/FileSync.java [deleted file]
org.argeo.cms/src/org/argeo/cms/acr/fs/FsCommands.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java

index ee06046301d15b77b5b8f3cca23d090bd2a80cfe..3b432b84972c339cdffe0fadc3c250e3dab93d14 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -19,11 +19,12 @@ org.argeo.api.acr \
 org.argeo.api.cli \
 org.argeo.api.cms \
 org.argeo.cms \
-org.argeo.cms.sql \
-org.argeo.cms.ssh \
 org.argeo.cms.ux \
-eclipse/org.argeo.ext.equinox.jetty \
-eclipse/org.argeo.cms.servlet \
+org.argeo.cms.ee4j \
+org.argeo.cms.lib.jetty \
+org.argeo.cms.lib.equinox \
+org.argeo.cms.lib.sshd \
+org.argeo.cms.lib.pgsql \
 swt/org.argeo.cms.swt \
 swt/org.argeo.cms.e4 \
 swt/rap/org.argeo.swt.specific.rap \
diff --git a/eclipse/org.argeo.cms.servlet/.classpath b/eclipse/org.argeo.cms.servlet/.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/eclipse/org.argeo.cms.servlet/.project b/eclipse/org.argeo.cms.servlet/.project
deleted file mode 100644 (file)
index d39f974..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.servlet</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/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml
deleted file mode 100644 (file)
index c2cf1c7..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" activate="start" deactivate="stop" name="Jetty Service Factory">
-   <implementation class="org.argeo.cms.servlet.internal.jetty.JettyConfig"/>
-   <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
-   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
-</scr:component>
diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/bnd.bnd b/eclipse/org.argeo.cms.servlet/bnd.bnd
deleted file mode 100644 (file)
index 7c537ba..0000000
+++ /dev/null
@@ -1,12 +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/jettyServiceFactory.xml,\
-OSGI-INF/pkgServletContext.xml,\
-OSGI-INF/pkgServlet.xml
diff --git a/eclipse/org.argeo.cms.servlet/build.properties b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsExceptionsChain.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsLoginServlet.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsLogoutServlet.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsSessionDescriptor.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/CmsTokenServlet.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/TokenDescriptor.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/integration/package-info.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java
deleted file mode 100644 (file)
index 9732191..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-package org.argeo.cms.servlet.internal.jetty;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.Map;
-import java.util.concurrent.ForkJoinPool;
-
-import javax.websocket.DeploymentException;
-import javax.websocket.server.ServerContainer;
-import javax.websocket.server.ServerEndpointConfig;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsState;
-import org.argeo.cms.CmsDeployProperty;
-import org.argeo.cms.websocket.javax.server.CmsWebSocketConfigurator;
-import org.argeo.cms.websocket.javax.server.TestEndpoint;
-import org.argeo.util.LangUtils;
-import org.eclipse.equinox.http.jetty.JettyConfigurator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class JettyConfig {
-       private final static CmsLog log = CmsLog.getLog(JettyConfig.class);
-
-       final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
-
-       private CmsState cmsState;
-
-       private final BundleContext bc = FrameworkUtil.getBundle(JettyConfig.class).getBundleContext();
-
-       // private static final String JETTY_PROPERTY_PREFIX =
-       // "org.eclipse.equinox.http.jetty.";
-
-       public void start() {
-               // We need to start asynchronously so that Jetty bundle get started by lazy init
-               // due to the non-configurable behaviour of its activator
-               ForkJoinPool.commonPool().execute(() -> {
-                       Dictionary<String, ?> properties = getHttpServerConfig();
-                       startServer(properties);
-               });
-
-               ServiceTracker<ServerContainer, ServerContainer> serverSt = new ServiceTracker<ServerContainer, ServerContainer>(
-                               bc, ServerContainer.class, null) {
-
-                       @Override
-                       public ServerContainer addingService(ServiceReference<ServerContainer> reference) {
-                               ServerContainer serverContainer = super.addingService(reference);
-
-                               BundleContext bc = reference.getBundle().getBundleContext();
-                               ServiceReference<ServerEndpointConfig.Configurator> srConfigurator = bc
-                                               .getServiceReference(ServerEndpointConfig.Configurator.class);
-                               ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator);
-                               ServerEndpointConfig config = ServerEndpointConfig.Builder
-                                               .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build();
-                               try {
-                                       serverContainer.addEndpoint(config);
-                               } catch (DeploymentException e) {
-                                       throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e);
-                               }
-                               return serverContainer;
-                       }
-
-               };
-               serverSt.open();
-
-               // check initialisation
-//             ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
-//
-//                     @Override
-//                     public HttpService addingService(ServiceReference<HttpService> sr) {
-//                             Object httpPort = sr.getProperty("http.port");
-//                             Object httpsPort = sr.getProperty("https.port");
-//                             log.info(httpPortsMsg(httpPort, httpsPort));
-//                             close();
-//                             return super.addingService(sr);
-//                     }
-//             };
-//             httpSt.open();
-       }
-
-       public void stop() {
-               try {
-                       JettyConfigurator.stopServer(CmsConstants.DEFAULT);
-               } catch (Exception e) {
-                       log.error("Cannot stop default Jetty server.", e);
-               }
-
-       }
-
-       public void startServer(Dictionary<String, ?> properties) {
-               // Explicitly configures Jetty so that the default server is not started by the
-               // activator of the Equinox Jetty bundle.
-               Map<String, String> config = LangUtils.dictToStringMap(properties);
-               if (!config.isEmpty()) {
-                       config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS);
-
-                       // TODO centralise with Jetty extender
-                       Object webSocketEnabled = config.get(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty());
-                       if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
-                               bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
-                               // config.put(WEBSOCKET_ENABLED, "true");
-                       }
-               }
-
-               long begin = System.currentTimeMillis();
-               int tryCount = 60;
-               try {
-                       while (tryCount > 0) {
-                               try {
-                                       // FIXME deal with multiple ids
-                                       JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config));
-
-                                       Object httpPort = config.get(JettyHttpConstants.HTTP_PORT);
-                                       Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT);
-                                       log.info(httpPortsMsg(httpPort, httpsPort));
-
-                                       // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
-                                       // configuration is not cleaned
-                                       FrameworkUtil.getBundle(JettyConfigurator.class).start();
-                                       return;
-                               } catch (IllegalStateException e) {
-                                       // e.printStackTrace();
-                                       // Jetty may not be ready
-                                       try {
-                                               Thread.sleep(1000);
-                                       } catch (Exception e1) {
-                                               // silent
-                                       }
-                                       tryCount--;
-                               }
-                       }
-                       long duration = System.currentTimeMillis() - begin;
-                       log.error("Gave up with starting Jetty server after " + (duration / 1000) + " s");
-               } catch (Exception e) {
-                       log.error("Cannot start default Jetty server with config " + properties, e);
-               }
-
-       }
-
-       private String httpPortsMsg(Object httpPort, Object httpsPort) {
-               return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
-       }
-
-       /** Override the provided config with the framework properties */
-       public Dictionary<String, Object> getHttpServerConfig() {
-               String httpPort = getFrameworkProp(CmsDeployProperty.HTTP_PORT);
-               String httpsPort = getFrameworkProp(CmsDeployProperty.HTTPS_PORT);
-               /// TODO make it more generic
-               String httpHost = getFrameworkProp(CmsDeployProperty.HOST);
-//             String httpsHost = getFrameworkProp(
-//                             JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
-               String webSocketEnabled = getFrameworkProp(CmsDeployProperty.WEBSOCKET_ENABLED);
-
-               final Hashtable<String, Object> props = new Hashtable<String, Object>();
-               // try {
-               if (httpPort != null || httpsPort != null) {
-                       boolean httpEnabled = httpPort != null;
-                       props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
-                       boolean httpsEnabled = httpsPort != null;
-                       props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
-
-                       if (httpEnabled) {
-                               props.put(JettyHttpConstants.HTTP_PORT, httpPort);
-                               if (httpHost != null)
-                                       props.put(JettyHttpConstants.HTTP_HOST, httpHost);
-                       }
-
-                       if (httpsEnabled) {
-                               props.put(JettyHttpConstants.HTTPS_PORT, httpsPort);
-                               if (httpHost != null)
-                                       props.put(JettyHttpConstants.HTTPS_HOST, httpHost);
-
-                               // keystore
-                               props.put(JettyHttpConstants.SSL_KEYSTORETYPE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORETYPE));
-                               props.put(JettyHttpConstants.SSL_KEYSTORE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORE));
-                               props.put(JettyHttpConstants.SSL_PASSWORD, getFrameworkProp(CmsDeployProperty.SSL_PASSWORD));
-
-                               // truststore
-                               props.put(JettyHttpConstants.SSL_TRUSTSTORETYPE,
-                                               getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORETYPE));
-                               props.put(JettyHttpConstants.SSL_TRUSTSTORE, getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORE));
-                               props.put(JettyHttpConstants.SSL_TRUSTSTOREPASSWORD,
-                                               getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
-
-                               // client certificate authentication
-                               String wantClientAuth = getFrameworkProp(CmsDeployProperty.SSL_WANTCLIENTAUTH);
-                               if (wantClientAuth != null)
-                                       props.put(JettyHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
-                               String needClientAuth = getFrameworkProp(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
-                               if (needClientAuth != null)
-                                       props.put(JettyHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
-                       }
-
-                       // web socket
-                       if (webSocketEnabled != null && webSocketEnabled.equals("true"))
-                               props.put(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty(), true);
-
-                       props.put(CmsConstants.CN, CmsConstants.DEFAULT);
-               }
-               return props;
-       }
-
-       private String getFrameworkProp(CmsDeployProperty deployProperty) {
-               return cmsState.getDeployProperty(deployProperty.getProperty());
-       }
-
-       public void setCmsState(CmsState cmsState) {
-               this.cmsState = cmsState;
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java
deleted file mode 100644 (file)
index 8ceb358..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.cms.servlet.internal.jetty;
-
-/** Compatible with Jetty. */
-interface JettyHttpConstants {
-       static final String HTTP_ENABLED = "http.enabled";
-       static final String HTTP_PORT = "http.port";
-       static final String HTTP_HOST = "http.host";
-       static final String HTTPS_ENABLED = "https.enabled";
-       static final String HTTPS_HOST = "https.host";
-       static final String HTTPS_PORT = "https.port";
-       static final String SSL_KEYSTORE = "ssl.keystore";
-       static final String SSL_PASSWORD = "ssl.password";
-       static final String SSL_KEYPASSWORD = "ssl.keypassword";
-       static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth";
-       static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth";
-       static final String SSL_PROTOCOL = "ssl.protocol";
-       static final String SSL_ALGORITHM = "ssl.algorithm";
-       static final String SSL_KEYSTORETYPE = "ssl.keystoretype";
-
-       // Argeo
-       static final String SSL_TRUSTSTORE = "ssl.truststore";
-       static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword";
-       static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype";
-
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java
deleted file mode 100644 (file)
index 005af74..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-package org.argeo.cms.servlet.internal.jetty;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.Map;
-
-import javax.websocket.DeploymentException;
-import javax.websocket.server.ServerContainer;
-import javax.websocket.server.ServerEndpointConfig;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.websocket.javax.server.CmsWebSocketConfigurator;
-import org.argeo.cms.websocket.javax.server.TestEndpoint;
-import org.argeo.util.LangUtils;
-import org.eclipse.equinox.http.jetty.JettyConfigurator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedServiceFactory;
-import org.osgi.util.tracker.ServiceTracker;
-
-@Deprecated
-public class JettyServiceFactory implements ManagedServiceFactory {
-       private final static CmsLog log = CmsLog.getLog(JettyServiceFactory.class);
-
-       final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
-       // Argeo specific
-       final static String WEBSOCKET_ENABLED = "websocket.enabled";
-
-       private final BundleContext bc = FrameworkUtil.getBundle(JettyServiceFactory.class).getBundleContext();
-
-       public void start() {
-               ServiceTracker<ServerContainer, ServerContainer> serverSt = new ServiceTracker<ServerContainer, ServerContainer>(
-                               bc, ServerContainer.class, null) {
-
-                       @Override
-                       public ServerContainer addingService(ServiceReference<ServerContainer> reference) {
-                               ServerContainer serverContainer = super.addingService(reference);
-
-                               BundleContext bc = reference.getBundle().getBundleContext();
-                               ServiceReference<ServerEndpointConfig.Configurator> srConfigurator = bc
-                                               .getServiceReference(ServerEndpointConfig.Configurator.class);
-                               ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator);
-                               ServerEndpointConfig config = ServerEndpointConfig.Builder
-                                               .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build();
-                               try {
-                                       serverContainer.addEndpoint(config);
-                               } catch (DeploymentException e) {
-                                       throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e);
-                               }
-                               return serverContainer;
-                       }
-
-               };
-               serverSt.open();
-       }
-
-       @Override
-       public String getName() {
-               return "Jetty Service Factory";
-       }
-
-       @Override
-       public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
-               // Explicitly configures Jetty so that the default server is not started by the
-               // activator of the Equinox Jetty bundle.
-               Map<String, String> config = LangUtils.dictToStringMap(properties);
-               if (!config.isEmpty()) {
-                       config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS);
-
-                       // TODO centralise with Jetty extender
-                       Object webSocketEnabled = config.get(WEBSOCKET_ENABLED);
-                       if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
-                               bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
-                               config.put(WEBSOCKET_ENABLED, "true");
-                       }
-               }
-
-               int tryCount = 60;
-               try {
-                       tryGettyJetty: while (tryCount > 0) {
-                               try {
-                                       // FIXME deal with multiple ids
-                                       JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config));
-                                       // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
-                                       // configuration is not cleaned
-                                       FrameworkUtil.getBundle(JettyConfigurator.class).start();
-                                       break tryGettyJetty;
-                               } catch (IllegalStateException e) {
-                                       // Jetty may not be ready
-                                       try {
-                                               Thread.sleep(1000);
-                                       } catch (Exception e1) {
-                                               // silent
-                                       }
-                                       tryCount--;
-                               }
-                       }
-               } catch (Exception e) {
-                       log.error("Cannot start default Jetty server with config " + properties, e);
-               }
-
-       }
-
-       @Override
-       public void deleted(String pid) {
-       }
-
-       public void stop() {
-               try {
-                       JettyConfigurator.stopServer(CmsConstants.DEFAULT);
-               } catch (Exception e) {
-                       log.error("Cannot stop default Jetty server.", e);
-               }
-
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java
deleted file mode 100644 (file)
index 8cc1655..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;
-import org.osgi.service.http.context.ServletContextHelper;
-
-/**
- * <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";
-
-       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(ServletContextHelper.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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/WebSocketView.java b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/websocket/javax/server/package-info.java b/eclipse/org.argeo.cms.servlet/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
diff --git a/eclipse/org.argeo.ext.equinox.jetty/.classpath b/eclipse/org.argeo.ext.equinox.jetty/.classpath
deleted file mode 100644 (file)
index eca7bdb..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-1.8"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/eclipse/org.argeo.ext.equinox.jetty/.gitignore b/eclipse/org.argeo.ext.equinox.jetty/.gitignore
deleted file mode 100644 (file)
index 09e3bc9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/bin/
-/target/
diff --git a/eclipse/org.argeo.ext.equinox.jetty/.project b/eclipse/org.argeo.ext.equinox.jetty/.project
deleted file mode 100644 (file)
index 0b9700d..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.ext.equinox.jetty</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>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/eclipse/org.argeo.ext.equinox.jetty/META-INF/.gitignore b/eclipse/org.argeo.ext.equinox.jetty/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/eclipse/org.argeo.ext.equinox.jetty/bnd.bnd b/eclipse/org.argeo.ext.equinox.jetty/bnd.bnd
deleted file mode 100644 (file)
index 7fca536..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Fragment-Host: org.eclipse.equinox.http.jetty
diff --git a/eclipse/org.argeo.ext.equinox.jetty/build.properties b/eclipse/org.argeo.ext.equinox.jetty/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/eclipse/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java b/eclipse/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java
deleted file mode 100644 (file)
index ab291b5..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.argeo.equinox.jetty;
-
-import java.util.Dictionary;
-
-import javax.servlet.ServletContext;
-import javax.websocket.DeploymentException;
-import javax.websocket.server.ServerContainer;
-
-import org.eclipse.equinox.http.jetty.JettyCustomizer;
-import org.eclipse.jetty.server.ConnectionFactory;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.SslConnectionFactory;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-/** Customises the Jetty HTTP server. */
-public class CmsJettyCustomizer extends JettyCustomizer {
-       static final String SSL_TRUSTSTORE = "ssl.truststore";
-       static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword";
-       static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype";
-
-       private BundleContext bc = FrameworkUtil.getBundle(CmsJettyCustomizer.class).getBundleContext();
-
-       public final static String WEBSOCKET_ENABLED = "argeo.websocket.enabled";
-
-       @Override
-       public Object customizeContext(Object context, Dictionary<String, ?> settings) {
-               // WebSocket
-               Object webSocketEnabled = settings.get(WEBSOCKET_ENABLED);
-               if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
-                       ServletContextHandler servletContextHandler = (ServletContextHandler) context;
-                       JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
-
-                               @Override
-                               public void accept(ServletContext servletContext, ServerContainer serverContainer)
-                                               throws DeploymentException {
-                                       bc.registerService(javax.websocket.server.ServerContainer.class, serverContainer, null);
-                               }
-                       });
-               }
-               return super.customizeContext(context, settings);
-
-       }
-
-       @Override
-       public Object customizeHttpsConnector(Object connector, Dictionary<String, ?> settings) {
-               ServerConnector httpsConnector = (ServerConnector) connector;
-               if (httpsConnector != null)
-                       for (ConnectionFactory connectionFactory : httpsConnector.getConnectionFactories()) {
-                               if (connectionFactory instanceof SslConnectionFactory) {
-                                       SslContextFactory.Server sslConnectionFactory = ((SslConnectionFactory) connectionFactory)
-                                                       .getSslContextFactory();
-                                       sslConnectionFactory.setTrustStorePath((String) settings.get(SSL_TRUSTSTORE));
-                                       sslConnectionFactory.setTrustStoreType((String) settings.get(SSL_TRUSTSTORETYPE));
-                                       sslConnectionFactory.setTrustStorePassword((String) settings.get(SSL_TRUSTSTOREPASSWORD));
-                               }
-                       }
-               return super.customizeHttpsConnector(connector, settings);
-       }
-
-}
diff --git a/eclipse/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/package-info.java b/eclipse/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/package-info.java
deleted file mode 100644 (file)
index 41c8ce9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Equinox Jetty extensions. */
-package org.argeo.equinox.jetty;
\ No newline at end of file
diff --git a/org.argeo.cms.ee4j/.classpath b/org.argeo.cms.ee4j/.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.ee4j/.project b/org.argeo.cms.ee4j/.project
new file mode 100644 (file)
index 0000000..9140add
--- /dev/null
@@ -0,0 +1,33 @@
+<?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
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.ee4j/OSGI-INF/pkgServletContext.xml b/org.argeo.cms.ee4j/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.ee4j/bnd.bnd b/org.argeo.cms.ee4j/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.ee4j/build.properties b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/integration/CmsExceptionsChain.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/integration/CmsLoginServlet.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/integration/CmsLogoutServlet.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/integration/CmsSessionDescriptor.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/integration/CmsTokenServlet.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/integration/TokenDescriptor.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/integration/package-info.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/servlet/ServletHttpRequest.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/servlet/ServletHttpResponse.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/servlet/ServletHttpSession.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/servlet/internal/HttpUtils.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/servlet/internal/PkgServlet.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/servlet/internal/RobotServlet.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/websocket/javax/server/CmsWebSocketConfigurator.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/websocket/javax/server/TestEndpoint.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketTest.java b/org.argeo.cms.ee4j/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.ee4j/src/org/argeo/cms/websocket/javax/server/WebSocketView.java b/org.argeo.cms.ee4j/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.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
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.lib.equinox/.classpath b/org.argeo.cms.lib.equinox/.classpath
new file mode 100644 (file)
index 0000000..eca7bdb
--- /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-1.8"/>
+       <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.lib.equinox/.gitignore b/org.argeo.cms.lib.equinox/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/org.argeo.cms.lib.equinox/.project b/org.argeo.cms.lib.equinox/.project
new file mode 100644 (file)
index 0000000..3be5eb6
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.lib.equinox</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>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.lib.equinox/META-INF/.gitignore b/org.argeo.cms.lib.equinox/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml b/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml
new file mode 100644 (file)
index 0000000..c2cf1c7
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" activate="start" deactivate="stop" name="Jetty Service Factory">
+   <implementation class="org.argeo.cms.servlet.internal.jetty.JettyConfig"/>
+   <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms.lib.equinox/bnd.bnd b/org.argeo.cms.lib.equinox/bnd.bnd
new file mode 100644 (file)
index 0000000..3d836df
--- /dev/null
@@ -0,0 +1,4 @@
+Fragment-Host: org.eclipse.equinox.http.jetty
+
+Service-Component: \
+OSGI-INF/jettyServiceFactory.xml,\
diff --git a/org.argeo.cms.lib.equinox/build.properties b/org.argeo.cms.lib.equinox/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java b/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java
new file mode 100644 (file)
index 0000000..9732191
--- /dev/null
@@ -0,0 +1,214 @@
+package org.argeo.cms.servlet.internal.jetty;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.ForkJoinPool;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.websocket.javax.server.CmsWebSocketConfigurator;
+import org.argeo.cms.websocket.javax.server.TestEndpoint;
+import org.argeo.util.LangUtils;
+import org.eclipse.equinox.http.jetty.JettyConfigurator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class JettyConfig {
+       private final static CmsLog log = CmsLog.getLog(JettyConfig.class);
+
+       final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
+
+       private CmsState cmsState;
+
+       private final BundleContext bc = FrameworkUtil.getBundle(JettyConfig.class).getBundleContext();
+
+       // private static final String JETTY_PROPERTY_PREFIX =
+       // "org.eclipse.equinox.http.jetty.";
+
+       public void start() {
+               // We need to start asynchronously so that Jetty bundle get started by lazy init
+               // due to the non-configurable behaviour of its activator
+               ForkJoinPool.commonPool().execute(() -> {
+                       Dictionary<String, ?> properties = getHttpServerConfig();
+                       startServer(properties);
+               });
+
+               ServiceTracker<ServerContainer, ServerContainer> serverSt = new ServiceTracker<ServerContainer, ServerContainer>(
+                               bc, ServerContainer.class, null) {
+
+                       @Override
+                       public ServerContainer addingService(ServiceReference<ServerContainer> reference) {
+                               ServerContainer serverContainer = super.addingService(reference);
+
+                               BundleContext bc = reference.getBundle().getBundleContext();
+                               ServiceReference<ServerEndpointConfig.Configurator> srConfigurator = bc
+                                               .getServiceReference(ServerEndpointConfig.Configurator.class);
+                               ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator);
+                               ServerEndpointConfig config = ServerEndpointConfig.Builder
+                                               .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build();
+                               try {
+                                       serverContainer.addEndpoint(config);
+                               } catch (DeploymentException e) {
+                                       throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e);
+                               }
+                               return serverContainer;
+                       }
+
+               };
+               serverSt.open();
+
+               // check initialisation
+//             ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
+//
+//                     @Override
+//                     public HttpService addingService(ServiceReference<HttpService> sr) {
+//                             Object httpPort = sr.getProperty("http.port");
+//                             Object httpsPort = sr.getProperty("https.port");
+//                             log.info(httpPortsMsg(httpPort, httpsPort));
+//                             close();
+//                             return super.addingService(sr);
+//                     }
+//             };
+//             httpSt.open();
+       }
+
+       public void stop() {
+               try {
+                       JettyConfigurator.stopServer(CmsConstants.DEFAULT);
+               } catch (Exception e) {
+                       log.error("Cannot stop default Jetty server.", e);
+               }
+
+       }
+
+       public void startServer(Dictionary<String, ?> properties) {
+               // Explicitly configures Jetty so that the default server is not started by the
+               // activator of the Equinox Jetty bundle.
+               Map<String, String> config = LangUtils.dictToStringMap(properties);
+               if (!config.isEmpty()) {
+                       config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS);
+
+                       // TODO centralise with Jetty extender
+                       Object webSocketEnabled = config.get(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty());
+                       if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+                               bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
+                               // config.put(WEBSOCKET_ENABLED, "true");
+                       }
+               }
+
+               long begin = System.currentTimeMillis();
+               int tryCount = 60;
+               try {
+                       while (tryCount > 0) {
+                               try {
+                                       // FIXME deal with multiple ids
+                                       JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config));
+
+                                       Object httpPort = config.get(JettyHttpConstants.HTTP_PORT);
+                                       Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT);
+                                       log.info(httpPortsMsg(httpPort, httpsPort));
+
+                                       // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
+                                       // configuration is not cleaned
+                                       FrameworkUtil.getBundle(JettyConfigurator.class).start();
+                                       return;
+                               } catch (IllegalStateException e) {
+                                       // e.printStackTrace();
+                                       // Jetty may not be ready
+                                       try {
+                                               Thread.sleep(1000);
+                                       } catch (Exception e1) {
+                                               // silent
+                                       }
+                                       tryCount--;
+                               }
+                       }
+                       long duration = System.currentTimeMillis() - begin;
+                       log.error("Gave up with starting Jetty server after " + (duration / 1000) + " s");
+               } catch (Exception e) {
+                       log.error("Cannot start default Jetty server with config " + properties, e);
+               }
+
+       }
+
+       private String httpPortsMsg(Object httpPort, Object httpsPort) {
+               return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
+       }
+
+       /** Override the provided config with the framework properties */
+       public Dictionary<String, Object> getHttpServerConfig() {
+               String httpPort = getFrameworkProp(CmsDeployProperty.HTTP_PORT);
+               String httpsPort = getFrameworkProp(CmsDeployProperty.HTTPS_PORT);
+               /// TODO make it more generic
+               String httpHost = getFrameworkProp(CmsDeployProperty.HOST);
+//             String httpsHost = getFrameworkProp(
+//                             JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
+               String webSocketEnabled = getFrameworkProp(CmsDeployProperty.WEBSOCKET_ENABLED);
+
+               final Hashtable<String, Object> props = new Hashtable<String, Object>();
+               // try {
+               if (httpPort != null || httpsPort != null) {
+                       boolean httpEnabled = httpPort != null;
+                       props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
+                       boolean httpsEnabled = httpsPort != null;
+                       props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
+
+                       if (httpEnabled) {
+                               props.put(JettyHttpConstants.HTTP_PORT, httpPort);
+                               if (httpHost != null)
+                                       props.put(JettyHttpConstants.HTTP_HOST, httpHost);
+                       }
+
+                       if (httpsEnabled) {
+                               props.put(JettyHttpConstants.HTTPS_PORT, httpsPort);
+                               if (httpHost != null)
+                                       props.put(JettyHttpConstants.HTTPS_HOST, httpHost);
+
+                               // keystore
+                               props.put(JettyHttpConstants.SSL_KEYSTORETYPE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORETYPE));
+                               props.put(JettyHttpConstants.SSL_KEYSTORE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORE));
+                               props.put(JettyHttpConstants.SSL_PASSWORD, getFrameworkProp(CmsDeployProperty.SSL_PASSWORD));
+
+                               // truststore
+                               props.put(JettyHttpConstants.SSL_TRUSTSTORETYPE,
+                                               getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+                               props.put(JettyHttpConstants.SSL_TRUSTSTORE, getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORE));
+                               props.put(JettyHttpConstants.SSL_TRUSTSTOREPASSWORD,
+                                               getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
+
+                               // client certificate authentication
+                               String wantClientAuth = getFrameworkProp(CmsDeployProperty.SSL_WANTCLIENTAUTH);
+                               if (wantClientAuth != null)
+                                       props.put(JettyHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
+                               String needClientAuth = getFrameworkProp(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
+                               if (needClientAuth != null)
+                                       props.put(JettyHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
+                       }
+
+                       // web socket
+                       if (webSocketEnabled != null && webSocketEnabled.equals("true"))
+                               props.put(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty(), true);
+
+                       props.put(CmsConstants.CN, CmsConstants.DEFAULT);
+               }
+               return props;
+       }
+
+       private String getFrameworkProp(CmsDeployProperty deployProperty) {
+               return cmsState.getDeployProperty(deployProperty.getProperty());
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+}
diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java b/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java
new file mode 100644 (file)
index 0000000..8ceb358
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.cms.servlet.internal.jetty;
+
+/** Compatible with Jetty. */
+interface JettyHttpConstants {
+       static final String HTTP_ENABLED = "http.enabled";
+       static final String HTTP_PORT = "http.port";
+       static final String HTTP_HOST = "http.host";
+       static final String HTTPS_ENABLED = "https.enabled";
+       static final String HTTPS_HOST = "https.host";
+       static final String HTTPS_PORT = "https.port";
+       static final String SSL_KEYSTORE = "ssl.keystore";
+       static final String SSL_PASSWORD = "ssl.password";
+       static final String SSL_KEYPASSWORD = "ssl.keypassword";
+       static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth";
+       static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth";
+       static final String SSL_PROTOCOL = "ssl.protocol";
+       static final String SSL_ALGORITHM = "ssl.algorithm";
+       static final String SSL_KEYSTORETYPE = "ssl.keystoretype";
+
+       // Argeo
+       static final String SSL_TRUSTSTORE = "ssl.truststore";
+       static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword";
+       static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype";
+
+}
diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java b/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java
new file mode 100644 (file)
index 0000000..005af74
--- /dev/null
@@ -0,0 +1,120 @@
+package org.argeo.cms.servlet.internal.jetty;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.websocket.javax.server.CmsWebSocketConfigurator;
+import org.argeo.cms.websocket.javax.server.TestEndpoint;
+import org.argeo.util.LangUtils;
+import org.eclipse.equinox.http.jetty.JettyConfigurator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+@Deprecated
+public class JettyServiceFactory implements ManagedServiceFactory {
+       private final static CmsLog log = CmsLog.getLog(JettyServiceFactory.class);
+
+       final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
+       // Argeo specific
+       final static String WEBSOCKET_ENABLED = "websocket.enabled";
+
+       private final BundleContext bc = FrameworkUtil.getBundle(JettyServiceFactory.class).getBundleContext();
+
+       public void start() {
+               ServiceTracker<ServerContainer, ServerContainer> serverSt = new ServiceTracker<ServerContainer, ServerContainer>(
+                               bc, ServerContainer.class, null) {
+
+                       @Override
+                       public ServerContainer addingService(ServiceReference<ServerContainer> reference) {
+                               ServerContainer serverContainer = super.addingService(reference);
+
+                               BundleContext bc = reference.getBundle().getBundleContext();
+                               ServiceReference<ServerEndpointConfig.Configurator> srConfigurator = bc
+                                               .getServiceReference(ServerEndpointConfig.Configurator.class);
+                               ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator);
+                               ServerEndpointConfig config = ServerEndpointConfig.Builder
+                                               .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build();
+                               try {
+                                       serverContainer.addEndpoint(config);
+                               } catch (DeploymentException e) {
+                                       throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e);
+                               }
+                               return serverContainer;
+                       }
+
+               };
+               serverSt.open();
+       }
+
+       @Override
+       public String getName() {
+               return "Jetty Service Factory";
+       }
+
+       @Override
+       public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
+               // Explicitly configures Jetty so that the default server is not started by the
+               // activator of the Equinox Jetty bundle.
+               Map<String, String> config = LangUtils.dictToStringMap(properties);
+               if (!config.isEmpty()) {
+                       config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS);
+
+                       // TODO centralise with Jetty extender
+                       Object webSocketEnabled = config.get(WEBSOCKET_ENABLED);
+                       if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+                               bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
+                               config.put(WEBSOCKET_ENABLED, "true");
+                       }
+               }
+
+               int tryCount = 60;
+               try {
+                       tryGettyJetty: while (tryCount > 0) {
+                               try {
+                                       // FIXME deal with multiple ids
+                                       JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config));
+                                       // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
+                                       // configuration is not cleaned
+                                       FrameworkUtil.getBundle(JettyConfigurator.class).start();
+                                       break tryGettyJetty;
+                               } catch (IllegalStateException e) {
+                                       // Jetty may not be ready
+                                       try {
+                                               Thread.sleep(1000);
+                                       } catch (Exception e1) {
+                                               // silent
+                                       }
+                                       tryCount--;
+                               }
+                       }
+               } catch (Exception e) {
+                       log.error("Cannot start default Jetty server with config " + properties, e);
+               }
+
+       }
+
+       @Override
+       public void deleted(String pid) {
+       }
+
+       public void stop() {
+               try {
+                       JettyConfigurator.stopServer(CmsConstants.DEFAULT);
+               } catch (Exception e) {
+                       log.error("Cannot stop default Jetty server.", e);
+               }
+
+       }
+
+}
diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java b/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java
new file mode 100644 (file)
index 0000000..ab291b5
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.equinox.jetty;
+
+import java.util.Dictionary;
+
+import javax.servlet.ServletContext;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.eclipse.equinox.http.jetty.JettyCustomizer;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** Customises the Jetty HTTP server. */
+public class CmsJettyCustomizer extends JettyCustomizer {
+       static final String SSL_TRUSTSTORE = "ssl.truststore";
+       static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword";
+       static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype";
+
+       private BundleContext bc = FrameworkUtil.getBundle(CmsJettyCustomizer.class).getBundleContext();
+
+       public final static String WEBSOCKET_ENABLED = "argeo.websocket.enabled";
+
+       @Override
+       public Object customizeContext(Object context, Dictionary<String, ?> settings) {
+               // WebSocket
+               Object webSocketEnabled = settings.get(WEBSOCKET_ENABLED);
+               if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+                       ServletContextHandler servletContextHandler = (ServletContextHandler) context;
+                       JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+                               @Override
+                               public void accept(ServletContext servletContext, ServerContainer serverContainer)
+                                               throws DeploymentException {
+                                       bc.registerService(javax.websocket.server.ServerContainer.class, serverContainer, null);
+                               }
+                       });
+               }
+               return super.customizeContext(context, settings);
+
+       }
+
+       @Override
+       public Object customizeHttpsConnector(Object connector, Dictionary<String, ?> settings) {
+               ServerConnector httpsConnector = (ServerConnector) connector;
+               if (httpsConnector != null)
+                       for (ConnectionFactory connectionFactory : httpsConnector.getConnectionFactories()) {
+                               if (connectionFactory instanceof SslConnectionFactory) {
+                                       SslContextFactory.Server sslConnectionFactory = ((SslConnectionFactory) connectionFactory)
+                                                       .getSslContextFactory();
+                                       sslConnectionFactory.setTrustStorePath((String) settings.get(SSL_TRUSTSTORE));
+                                       sslConnectionFactory.setTrustStoreType((String) settings.get(SSL_TRUSTSTORETYPE));
+                                       sslConnectionFactory.setTrustStorePassword((String) settings.get(SSL_TRUSTSTOREPASSWORD));
+                               }
+                       }
+               return super.customizeHttpsConnector(connector, settings);
+       }
+
+}
diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java b/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java
new file mode 100644 (file)
index 0000000..41c8ce9
--- /dev/null
@@ -0,0 +1,2 @@
+/** Equinox Jetty extensions. */
+package org.argeo.equinox.jetty;
\ No newline at end of file
diff --git a/org.argeo.cms.lib.jetty/.classpath b/org.argeo.cms.lib.jetty/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /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-17"/>
+       <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.lib.jetty/.project b/org.argeo.cms.lib.jetty/.project
new file mode 100644 (file)
index 0000000..132df7f
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.lib.jetty</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>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.lib.jetty/.settings/org.eclipse.jdt.core.prefs b/org.argeo.cms.lib.jetty/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..62ef348
--- /dev/null
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/org.argeo.cms.lib.jetty/.settings/org.eclipse.pde.core.prefs b/org.argeo.cms.lib.jetty/.settings/org.eclipse.pde.core.prefs
new file mode 100644 (file)
index 0000000..e8ff8be
--- /dev/null
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+pluginProject.equinox=false
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/org.argeo.cms.lib.jetty/bnd.bnd b/org.argeo.cms.lib.jetty/bnd.bnd
new file mode 100644 (file)
index 0000000..d926017
--- /dev/null
@@ -0,0 +1,9 @@
+Import-Package: \
+javax.servlet.http,\
+org.eclipse.jetty.util.component;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.http;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.io;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.security;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.server.handler;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.*;version="[9.4,12)";resolution:=optional,\
+*
\ No newline at end of file
diff --git a/org.argeo.cms.lib.jetty/build.properties b/org.argeo.cms.lib.jetty/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/lib/jetty/CmsJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/lib/jetty/CmsJettyServer.java
new file mode 100644 (file)
index 0000000..d197a00
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.cms.lib.jetty;
+
+import java.nio.file.Path;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+public class CmsJettyServer {
+       private Server server;
+       private ServerConnector serverConnector;
+       private Path tempDir;
+
+       public void start() {
+               server = new Server(new QueuedThreadPool(10, 1));
+               serverConnector = new ServerConnector(server);
+               serverConnector.setPort(0);
+               server.setConnectors(new Connector[] { serverConnector });
+
+               ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+               context.setContextPath("/");
+               server.setHandler(context);
+
+               //context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint);
+
+               // Required to serve rwt-resources. It is important that this is last.
+               ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
+               context.addServlet(holderPwd, "/");
+
+               try {
+                       server.start();
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot start Jetty server", e);
+               }
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
+       }
+
+       public void stop() {
+               try {
+                       serverConnector.close();
+                       server.stop();
+                       // TODO delete temp dir
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+
+       }
+}
diff --git a/org.argeo.cms.lib.pgsql/.classpath b/org.argeo.cms.lib.pgsql/.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.lib.pgsql/.project b/org.argeo.cms.lib.pgsql/.project
new file mode 100644 (file)
index 0000000..3cd5f6f
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.lib.pgsql</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>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.lib.pgsql/bnd.bnd b/org.argeo.cms.lib.pgsql/bnd.bnd
new file mode 100644 (file)
index 0000000..9c73009
--- /dev/null
@@ -0,0 +1 @@
+Import-Package: org.postgresql;version="[42,43)"
diff --git a/org.argeo.cms.lib.pgsql/build.properties b/org.argeo.cms.lib.pgsql/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.cms.lib.pgsql/src/org/argeo/cms/sql/postgres/CheckPg.java b/org.argeo.cms.lib.pgsql/src/org/argeo/cms/sql/postgres/CheckPg.java
new file mode 100644 (file)
index 0000000..bc002a6
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.cms.sql.postgres;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.postgresql.Driver;
+
+/** Simple PostgreSQL check. */
+public class CheckPg {
+
+       public List<String> listTables() {
+               String osUser = System.getProperty("user.name");
+
+               String url = "jdbc:postgresql://localhost/" + osUser;
+               Properties props = new Properties();
+               props.setProperty("user", osUser);
+               props.setProperty("password", "changeit");
+               List<String> result = new ArrayList<>();
+
+               Driver driver = new Driver();
+               try (Connection conn = driver.connect(url, props); Statement s = conn.createStatement();) {
+                       s.execute("SELECT * FROM pg_catalog.pg_tables");
+                       ResultSet rs = s.getResultSet();
+                       while (rs.next()) {
+                               result.add(rs.getString("tablename"));
+                       }
+                       return result;
+               } catch (SQLException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       public static void main(String[] args) {
+               new CheckPg().listTables().forEach(System.out::println);
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/.classpath b/org.argeo.cms.lib.sshd/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /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-17"/>
+       <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.lib.sshd/.gitignore b/org.argeo.cms.lib.sshd/.gitignore
new file mode 100644 (file)
index 0000000..7fb0c18
--- /dev/null
@@ -0,0 +1,3 @@
+/hostkey.ser
+/id_rsa
+/id_rsa.pub
diff --git a/org.argeo.cms.lib.sshd/.project b/org.argeo.cms.lib.sshd/.project
new file mode 100644 (file)
index 0000000..588b829
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.lib.sshd</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.lib.sshd/.settings/org.eclipse.jdt.core.prefs b/org.argeo.cms.lib.sshd/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..997d664
--- /dev/null
@@ -0,0 +1,104 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.builder.annotationPath.allLocations=disabled
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.problem.APILeak=warning
+org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/org.argeo.cms.lib.sshd/OSGI-INF/cmsSshServer.xml b/org.argeo.cms.lib.sshd/OSGI-INF/cmsSshServer.xml
new file mode 100644 (file)
index 0000000..aa2d8db
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="CMS SSH Server" immediate="true">
+   <implementation class="org.argeo.cms.ssh.CmsSshServer"/>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms.lib.sshd/bnd.bnd b/org.argeo.cms.lib.sshd/bnd.bnd
new file mode 100644 (file)
index 0000000..e708391
--- /dev/null
@@ -0,0 +1,7 @@
+Import-Package: \
+org.apache.sshd.server.forward,\
+org.apache.sshd.common.forward,\
+*
+
+Service-Component: \
+OSGI-INF/cmsSshServer.xml
diff --git a/org.argeo.cms.lib.sshd/build.properties b/org.argeo.cms.lib.sshd/build.properties
new file mode 100644 (file)
index 0000000..1e3132a
--- /dev/null
@@ -0,0 +1,9 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/
+source.. = src/
+additional.bundles = org.apache.sshd.common,\
+                     org.apache.sshd.core,\
+                     org.slf4j.api,\
+                     org.argeo.ext.slf4j
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java
new file mode 100644 (file)
index 0000000..f2525bf
--- /dev/null
@@ -0,0 +1,211 @@
+package org.argeo.cms.ssh;
+
+import java.io.Console;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.channel.ClientChannelEvent;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
+import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
+import org.argeo.api.cms.CmsLog;
+
+@SuppressWarnings("restriction")
+public abstract class AbstractSsh {
+       private final static CmsLog log = CmsLog.getLog(AbstractSsh.class);
+
+       private static SshClient sshClient;
+       private static SftpFileSystemProvider sftpFileSystemProvider;
+
+       private boolean passwordSet = false;
+       private ClientSession session;
+
+       private SshKeyPair sshKeyPair;
+
+       public synchronized SshClient getSshClient() {
+               if (sshClient == null) {
+                       long begin = System.currentTimeMillis();
+                       sshClient = SshClient.setUpDefaultClient();
+                       sshClient.start();
+                       long duration = System.currentTimeMillis() - begin;
+                       if (log.isDebugEnabled())
+                               log.debug("SSH client started in " + duration + " ms");
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> sshClient.stop(), "Stop SSH client"));
+               }
+               return sshClient;
+       }
+
+       synchronized SftpFileSystemProvider getSftpFileSystemProvider() {
+               if (sftpFileSystemProvider == null) {
+                       sftpFileSystemProvider = new SftpFileSystemProvider(sshClient);
+               }
+               return sftpFileSystemProvider;
+       }
+
+       public void authenticate() {
+               if (sshKeyPair != null) {
+                       session.addPublicKeyIdentity(sshKeyPair.asKeyPair());
+               } else {
+
+                       if (!passwordSet) {
+                               String password;
+                               Console console = System.console();
+                               if (console == null) {// IDE
+                                       System.out.print("Password: ");
+                                       try (Scanner s = new Scanner(System.in)) {
+                                               password = s.next();
+                                       }
+                               } else {
+                                       console.printf("Password: ");
+                                       char[] pwd = console.readPassword();
+                                       password = new String(pwd);
+                                       Arrays.fill(pwd, ' ');
+                               }
+                               session.addPasswordIdentity(password);
+                               passwordSet = true;
+                       }
+               }
+               verifyAuth();
+       }
+
+       public void verifyAuth() {
+               try {
+                       session.auth().verify(1000l);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot verify auth", e);
+               }
+       }
+
+       public static char[] readPassword() {
+               Console console = System.console();
+               if (console == null) {// IDE
+                       System.out.print("Password: ");
+                       try (Scanner s = new Scanner(System.in)) {
+                               String password = s.next();
+                               return password.toCharArray();
+                       }
+               } else {
+                       console.printf("Password: ");
+                       char[] pwd = console.readPassword();
+                       return pwd;
+               }
+       }
+
+       void addPassword(String password) {
+               session.addPasswordIdentity(password);
+       }
+
+       void loadKey(String password) {
+               loadKey(password, System.getProperty("user.home") + "/.ssh/id_rsa");
+       }
+
+       void loadKey(String password, String keyPath) {
+//             try {
+//                     KeyPair keyPair = ClientIdentityLoader.DEFAULT.loadClientIdentity(keyPath,
+//                                     FilePasswordProvider.of(password));
+//                     session.addPublicKeyIdentity(keyPair);
+//             } catch (IOException | GeneralSecurityException e) {
+//                     throw new IllegalStateException(e);
+//             }
+       }
+
+       void openSession(URI uri) {
+               openSession(uri.getUserInfo(), uri.getHost(), uri.getPort() > 0 ? uri.getPort() : null);
+       }
+
+       void openSession(String login, String host, Integer port) {
+               if (session != null)
+                       throw new IllegalStateException("Session is already open");
+
+               if (host == null)
+                       host = "localhost";
+               if (port == null)
+                       port = 22;
+               if (login == null)
+                       login = System.getProperty("user.name");
+               String password = null;
+               int sepIndex = login.indexOf(':');
+               if (sepIndex > 0)
+                       if (sepIndex + 1 < login.length()) {
+                               password = login.substring(sepIndex + 1);
+                               login = login.substring(0, sepIndex);
+                       } else {
+                               throw new IllegalArgumentException("Illegal authority: " + login);
+                       }
+               try {
+                       ConnectFuture connectFuture = getSshClient().connect(login, host, port);
+                       connectFuture.await();
+                       ClientSession session = connectFuture.getSession();
+                       if (password != null) {
+                               session.addPasswordIdentity(password);
+                               passwordSet = true;
+                       }
+                       this.session = session;
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot connect to " + host + ":" + port);
+               }
+       }
+
+       public void closeSession() {
+               if (session == null)
+                       throw new IllegalStateException("No session is open");
+               try {
+                       session.close();
+               } catch (IOException e) {
+                       e.printStackTrace();
+               } finally {
+                       session = null;
+               }
+       }
+
+       ClientSession getSession() {
+               return session;
+       }
+
+       public void setSshKeyPair(SshKeyPair sshKeyPair) {
+               this.sshKeyPair = sshKeyPair;
+       }
+
+       public static void openShell(AbstractSsh ssh) {
+               openShell(ssh.getSession());
+       }
+
+       public static void openShell(ClientSession session) {
+               try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) {
+                       channel.setIn(new NoCloseInputStream(System.in));
+                       channel.setOut(new NoCloseOutputStream(System.out));
+                       channel.setErr(new NoCloseOutputStream(System.err));
+                       channel.open();
+
+                       Set<ClientChannelEvent> events = new HashSet<>();
+                       events.add(ClientChannelEvent.CLOSED);
+                       channel.waitFor(events, 0);
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } finally {
+                       session.close(false);
+               }
+       }
+
+       static URI toUri(String username, String host, int port) {
+               try {
+                       if (username == null)
+                               username = "root";
+                       return new URI("ssh://" + username + "@" + host + ":" + port);
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Cannot generate SSH URI to " + host + ":" + port + " for " + username,
+                                       e);
+               }
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/BasicSshServer.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/BasicSshServer.java
new file mode 100644 (file)
index 0000000..9e93893
--- /dev/null
@@ -0,0 +1,108 @@
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.shell.ProcessShellFactory;
+import org.argeo.util.OS;
+
+/** A simple SSH server with some defaults. Supports SCP. */
+@SuppressWarnings("restriction")
+public class BasicSshServer {
+       private Integer port;
+       private Path hostKeyPath;
+
+       private SshServer sshd = null;
+
+       public BasicSshServer(Integer port, Path hostKeyPath) {
+               this.port = port;
+               this.hostKeyPath = hostKeyPath;
+       }
+
+       public void init() {
+               try {
+                       sshd = SshServer.setUpDefaultServer();
+                       sshd.setPort(port);
+                       if (hostKeyPath == null)
+                               throw new IllegalStateException("An SSH server key must be set");
+                       sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
+                       // sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i",
+                       // "-l" }));
+                       String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
+                       // FIXME transfer args
+//                     sshd.setShellFactory(new ProcessShellFactory(shellCommand));
+                       sshd.setShellFactory(new ProcessShellFactory(shellCommand[0], shellCommand));
+                       sshd.setCommandFactory(new ScpCommandFactory());
+
+                       sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
+                       sshd.start();
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot start SSH server on port " + port, e);
+               }
+       }
+
+       public void destroy() {
+               try {
+                       sshd.stop();
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot stop SSH server on port " + port, e);
+               }
+       }
+
+       public Integer getPort() {
+               return port;
+       }
+
+       public void setPort(Integer port) {
+               this.port = port;
+       }
+
+       public Path getHostKeyPath() {
+               return hostKeyPath;
+       }
+
+       public void setHostKeyPath(Path hostKeyPath) {
+               this.hostKeyPath = hostKeyPath;
+       }
+
+       public static void main(String[] args) {
+               int port = 2222;
+               Path hostKeyPath = Paths.get("hostkey.ser");
+               try {
+                       if (args.length > 0)
+                               port = Integer.parseInt(args[0]);
+                       if (args.length > 1)
+                               hostKeyPath = Paths.get(args[1]);
+               } catch (Exception e1) {
+                       printUsage();
+               }
+
+               BasicSshServer sshServer = new BasicSshServer(port, hostKeyPath);
+               sshServer.init();
+               Runtime.getRuntime().addShutdownHook(new Thread("Shutdown SSH server") {
+
+                       @Override
+                       public void run() {
+                               sshServer.destroy();
+                       }
+               });
+               try {
+                       synchronized (sshServer) {
+                               sshServer.wait();
+                       }
+               } catch (InterruptedException e) {
+                       sshServer.destroy();
+               }
+
+       }
+
+       public static void printUsage() {
+               System.out.println("java " + BasicSshServer.class.getName() + " [port] [server key path]");
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java
new file mode 100644 (file)
index 0000000..7246d60
--- /dev/null
@@ -0,0 +1,142 @@
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+
+import org.apache.sshd.common.forward.PortForwardingEventListener;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
+import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
+import org.apache.sshd.server.jaas.JaasPasswordAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
+import org.apache.sshd.sftp.server.SftpSubsystemFactory;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+
+public class CmsSshServer {
+       private final static CmsLog log = CmsLog.getLog(CmsSshServer.class);
+       private static final String DEFAULT_SSH_HOST_KEY_PATH = CmsConstants.NODE + '/' + CmsConstants.NODE + ".ser";
+
+       private CmsState cmsState;
+       private SshServer sshd = null;
+
+       private int port;
+       private String host;
+
+       public void start() {
+               String portStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
+               if (portStr == null)
+                       return; // ignore
+               port = Integer.parseInt(portStr);
+
+               host = cmsState.getDeployProperty(CmsDeployProperty.HOST.getProperty());
+
+               Path hostKeyPath = cmsState.getDataPath(DEFAULT_SSH_HOST_KEY_PATH);
+
+               try {
+                       sshd = SshServer.setUpDefaultServer();
+                       sshd.setPort(port);
+                       if (host != null)
+                               sshd.setHost(host);
+                       if (hostKeyPath == null)
+                               throw new IllegalStateException("An SSH server key must be set");
+                       sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
+                       // sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i",
+                       // "-l" }));
+//                     String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
+                       // FIXME transfer args
+//             sshd.setShellFactory(new ProcessShellFactory(shellCommand));
+                       sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
+                       sshd.setCommandFactory(new ScpCommandFactory());
+
+                       // tunnels
+                       sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
+                       // sshd.setForwardingFilter(ForwardingFilter.asForwardingFilter(null, null,
+                       // TcpForwardingFilter.DEFAULT));
+                       // sshd.setForwarderFactory(DefaultForwarderFactory.INSTANCE);
+//                     TcpForwardingFilter tcpForwardingFilter = sshd.getTcpForwardingFilter();
+                       sshd.addPortForwardingEventListener(new PortForwardingEventListener() {
+
+                               @Override
+                               public void establishingExplicitTunnel(Session session, SshdSocketAddress local,
+                                               SshdSocketAddress remote, boolean localForwarding) throws IOException {
+                                       log.debug("Establishing tunnel " + local + ", " + remote);
+                               }
+
+                               @Override
+                               public void establishedExplicitTunnel(Session session, SshdSocketAddress local,
+                                               SshdSocketAddress remote, boolean localForwarding, SshdSocketAddress boundAddress,
+                                               Throwable reason) throws IOException {
+                                       log.debug("Established tunnel " + local + ", " + remote + ", " + boundAddress);
+                               }
+
+                               @Override
+                               public void establishingDynamicTunnel(Session session, SshdSocketAddress local) throws IOException {
+                                       log.debug("Establishing dynamic tunnel " + local);
+                               }
+
+                               @Override
+                               public void establishedDynamicTunnel(Session session, SshdSocketAddress local,
+                                               SshdSocketAddress boundAddress, Throwable reason) throws IOException {
+                                       log.debug("Established dynamic tunnel " + local);
+                               }
+
+                       });
+
+                       // Authentication
+                       //sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
+                       sshd.setPublickeyAuthenticator(null);
+                       // sshd.setKeyboardInteractiveAuthenticator(null);
+                       JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator();
+                       jaasPasswordAuthenticator.setDomain(CmsAuth.NODE.getLoginContextName());
+                       sshd.setPasswordAuthenticator(jaasPasswordAuthenticator);
+
+                       Path krb5keyTab = cmsState.getDataPath("node/krb5.keytab");
+                       if (Files.exists(krb5keyTab)) {
+                               // FIXME experimental
+                               GSSAuthenticator gssAuthenticator = new GSSAuthenticator();
+                               gssAuthenticator.setKeytabFile(cmsState.getDataPath("node/krb5.keytab").toString());
+                               gssAuthenticator.setServicePrincipalName("HTTP@" + host);
+                               sshd.setGSSAuthenticator(gssAuthenticator);
+                       }
+
+                       // SFTP
+                       sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+
+                       // start
+                       sshd.start();
+
+                       log.debug(() -> "CMS SSH server started on port " + port + (host != null ? " of host " + host : ""));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot start SSH server on port " + port, e);
+               }
+
+       }
+
+       public void stop() {
+               if (sshd == null)
+                       return;
+               try {
+                       sshd.stop();
+                       log.debug(() -> "CMS SSH server stopped on port " + port + (host != null ? " of host " + host : ""));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot stop SSH server", e);
+               }
+
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Sftp.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Sftp.java
new file mode 100644 (file)
index 0000000..f6f4474
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+
+import org.apache.sshd.sftp.client.fs.SftpFileSystem;
+
+/** Create an SFTP {@link FileSystem}. */
+public class Sftp extends AbstractSsh {
+       private URI uri;
+
+       private SftpFileSystem fileSystem;
+
+       public Sftp(String username, String host, int port) {
+               this(AbstractSsh.toUri(username, host, port));
+       }
+
+       public Sftp(URI uri) {
+               this.uri = uri;
+               openSession(uri);
+       }
+
+       public FileSystem getFileSystem() {
+               if (fileSystem == null) {
+                       try {
+                               authenticate();
+                               fileSystem = getSftpFileSystemProvider().newFileSystem(getSession());
+                       } catch (IOException e) {
+                               throw new IllegalStateException(e);
+                       }
+               }
+               return fileSystem;
+       }
+
+       public Path getBasePath() {
+               String p = uri.getPath() != null ? uri.getPath() : "/";
+               return getFileSystem().getPath(p);
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Ssh.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Ssh.java
new file mode 100644 (file)
index 0000000..6e7b6ef
--- /dev/null
@@ -0,0 +1,81 @@
+package org.argeo.cms.ssh;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+/** Create an SSH shell. */
+public class Ssh extends AbstractSsh {
+       private final URI uri;
+
+       public Ssh(String username, String host, int port) {
+               this(AbstractSsh.toUri(username, host, port));
+       }
+
+       public Ssh(URI uri) {
+               this.uri = uri;
+               openSession(uri);
+       }
+
+       public static void main(String[] args) {
+               Options options = getOptions();
+               CommandLineParser parser = new DefaultParser();
+               try {
+                       CommandLine line = parser.parse(options, args);
+                       List<String> remaining = line.getArgList();
+                       if (remaining.size() == 0) {
+                               System.err.println("There must be at least one argument");
+                               printHelp(options);
+                               System.exit(1);
+                       }
+                       URI uri = new URI("ssh://" + remaining.get(0));
+                       List<String> command = new ArrayList<>();
+                       if (remaining.size() > 1) {
+                               for (int i = 1; i < remaining.size(); i++) {
+                                       command.add(remaining.get(i));
+                               }
+                       }
+
+                       // auth
+                       Ssh ssh = new Ssh(uri);
+                       ssh.authenticate();
+
+                       if (command.size() == 0) {// shell
+                               AbstractSsh.openShell(ssh.getSession());
+                       } else {// execute command
+
+                       }
+                       ssh.closeSession();
+               } catch (Exception exp) {
+                       exp.printStackTrace();
+                       printHelp(options);
+                       System.exit(1);
+               } finally {
+
+               }
+       }
+
+       public URI getUri() {
+               return uri;
+       }
+
+       public static Options getOptions() {
+               Options options = new Options();
+//             options.addOption("p", true, "port");
+               options.addOption(Option.builder("p").hasArg().argName("port").desc("port of the SSH server").build());
+
+               return options;
+       }
+
+       public static void printHelp(Options options) {
+               HelpFormatter formatter = new HelpFormatter();
+               formatter.printHelp("ssh [username@]hostname", options, true);
+       }
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshKeyPair.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshKeyPair.java
new file mode 100644 (file)
index 0000000..f5cbb04
--- /dev/null
@@ -0,0 +1,205 @@
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.spec.RSAPublicKeySpec;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.openssl.PEMDecryptorProvider;
+import org.bouncycastle.openssl.PEMEncryptedKeyPair;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.PKCS8Generator;
+import org.bouncycastle.openssl.bc.BcPEMDecryptorProvider;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+
+@SuppressWarnings("restriction")
+public class SshKeyPair {
+       public final static String RSA_KEY_TYPE = "ssh-rsa";
+
+       private PublicKey publicKey;
+       private PrivateKey privateKey;
+       private KeyPair keyPair;
+
+       public SshKeyPair(KeyPair keyPair) {
+               super();
+               this.publicKey = keyPair.getPublic();
+               this.privateKey = keyPair.getPrivate();
+               this.keyPair = keyPair;
+       }
+
+       public SshKeyPair(PublicKey publicKey, PrivateKey privateKey) {
+               super();
+               this.publicKey = publicKey;
+               this.privateKey = privateKey;
+               this.keyPair = new KeyPair(publicKey, privateKey);
+       }
+
+       public KeyPair asKeyPair() {
+               return keyPair;
+       }
+
+       public String getPublicKeyAsOpenSshString() {
+               return PublicKeyEntry.toString(publicKey);
+       }
+
+       public String getPrivateKeyAsPemString(char[] password) {
+               try {
+                       Object obj;
+
+                       if (password != null) {
+                               JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(
+                                               PKCS8Generator.PBE_SHA1_3DES);
+                               encryptorBuilder.setPasssword(password);
+                               OutputEncryptor oe = encryptorBuilder.build();
+                               JcaPKCS8Generator gen = new JcaPKCS8Generator(privateKey, oe);
+                               obj = gen.generate();
+                       } else {
+                               obj = privateKey;
+                       }
+
+                       StringWriter sw = new StringWriter();
+                       JcaPEMWriter pemWrt = new JcaPEMWriter(sw);
+                       pemWrt.writeObject(obj);
+                       pemWrt.close();
+                       return sw.toString();
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot convert private key", e);
+               }
+       }
+
+       public static SshKeyPair loadOrGenerate(Path privateKeyPath, int size, char[] password) {
+               try {
+                       SshKeyPair sshKeyPair;
+                       if (Files.exists(privateKeyPath)) {
+//                             String privateKeyStr = new String(Files.readAllBytes(privateKeyPath), StandardCharsets.US_ASCII);
+                               sshKeyPair = load(
+                                               new InputStreamReader(Files.newInputStream(privateKeyPath), StandardCharsets.US_ASCII),
+                                               password);
+                               // TOD make sure public key is consistemt
+                       } else {
+                               sshKeyPair = generate(size);
+                               Files.write(privateKeyPath,
+                                               sshKeyPair.getPrivateKeyAsPemString(password).getBytes(StandardCharsets.US_ASCII));
+                               Path publicKeyPath = privateKeyPath.resolveSibling(privateKeyPath.getFileName() + ".pub");
+                               Files.write(publicKeyPath,
+                                               sshKeyPair.getPublicKeyAsOpenSshString().getBytes(StandardCharsets.US_ASCII));
+                       }
+                       return sshKeyPair;
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot read or write private key " + privateKeyPath, e);
+               }
+       }
+
+       public static SshKeyPair generate(int size) {
+               return generate(RSA_KEY_TYPE, size);
+       }
+
+       public static SshKeyPair generate(String keyType, int size) {
+               try {
+                       KeyPair keyPair = KeyUtils.generateKeyPair(keyType, size);
+                       PublicKey publicKey = keyPair.getPublic();
+                       PrivateKey privateKey = keyPair.getPrivate();
+                       return new SshKeyPair(publicKey, privateKey);
+               } catch (GeneralSecurityException e) {
+                       throw new RuntimeException("Cannot generate SSH key", e);
+               }
+       }
+
+       public static SshKeyPair loadDefault(char[] password) {
+               Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_rsa");
+               // TODO try other formats
+               return load(privateKeyPath, password);
+       }
+
+       public static SshKeyPair load(Path privateKeyPath, char[] password) {
+               try (Reader reader = Files.newBufferedReader(privateKeyPath)) {
+                       return load(reader, password);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot load private key from " + privateKeyPath, e);
+               }
+
+       }
+
+       public static SshKeyPair load(Reader reader, char[] password) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       Object object = pemParser.readObject();
+                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter();// .setProvider("BC");
+                       KeyPair kp;
+                       if (object instanceof PEMEncryptedKeyPair) {
+                               PEMEncryptedKeyPair ekp = (PEMEncryptedKeyPair) object;
+                               PEMDecryptorProvider decryptorProvider = new BcPEMDecryptorProvider(password);
+                               PEMKeyPair pemKp = ekp.decryptKeyPair(decryptorProvider);
+                               kp = converter.getKeyPair(pemKp);
+                       } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
+                               // Encrypted key - we will use provided password
+                               PKCS8EncryptedPrivateKeyInfo ckp = (PKCS8EncryptedPrivateKeyInfo) object;
+//                             PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password);
+                               InputDecryptorProvider inputDecryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder()
+                                               .build(password);
+                               PrivateKeyInfo pkInfo = ckp.decryptPrivateKeyInfo(inputDecryptorProvider);
+                               PrivateKey privateKey = converter.getPrivateKey(pkInfo);
+
+                               // generate public key
+                               RSAPrivateCrtKey privk = (RSAPrivateCrtKey) privateKey;
+                               RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(privk.getModulus(),
+                                               privk.getPublicExponent());
+                               KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+                               PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
+
+                               kp = new KeyPair(publicKey, privateKey);
+                       } else {
+                               // Unencrypted key - no password needed
+//                             PKCS8EncryptedPrivateKeyInfo ukp = (PKCS8EncryptedPrivateKeyInfo) object;
+                               PEMKeyPair pemKp = (PEMKeyPair) object;
+                               kp = converter.getKeyPair(pemKp);
+                       }
+                       return new SshKeyPair(kp);
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot load private key", e);
+               }
+       }
+
+       public static void main(String args[]) {
+               Path privateKeyPath = Paths.get(System.getProperty("user.dir") + "/id_rsa");
+               SshKeyPair skp = SshKeyPair.loadOrGenerate(privateKeyPath, 1024, null);
+               System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
+               System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
+               System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
+
+               StringReader reader = new StringReader(skp.getPrivateKeyAsPemString(null));
+               skp = SshKeyPair.load(reader, null);
+               System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
+               System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
+               System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
+
+               reader = new StringReader(skp.getPrivateKeyAsPemString("demo".toCharArray()));
+               skp = SshKeyPair.load(reader, "demo".toCharArray());
+               System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
+               System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
+               System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshSync.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshSync.java
new file mode 100644 (file)
index 0000000..71b6365
--- /dev/null
@@ -0,0 +1,154 @@
+package org.argeo.cms.ssh;
+
+import java.io.Console;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.util.Map;
+import java.util.Scanner;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.agent.unix.UnixAgentFactory;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.sftp.client.fs.SftpFileSystem;
+import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
+import org.argeo.api.cms.CmsLog;
+
+public class SshSync {
+       private final static CmsLog log = CmsLog.getLog(SshSync.class);
+
+       public static void main(String[] args) {
+
+               try (SshClient client = SshClient.setUpDefaultClient()) {
+                       client.start();
+                       boolean osAgent = false;
+                       SshAgentFactory agentFactory = osAgent ? new UnixAgentFactory() : new LocalAgentFactory();
+                       // SshAgentFactory agentFactory = new LocalAgentFactory();
+                       client.setAgentFactory(agentFactory);
+                       SshAgent sshAgent = agentFactory.createClient(null, client);
+
+                       String login = System.getProperty("user.name");
+                       String host = "localhost";
+                       int port = 22;
+
+                       if (!osAgent) {
+                               String keyPath = "/home/" + login + "/.ssh/id_rsa";
+
+                               String password;
+                               Console console = System.console();
+                               if (console != null) {
+                                       password = new String(console.readPassword(keyPath + ": "));
+                               } else {
+                                       System.out.print(keyPath + ": ");
+                                       try (Scanner s = new Scanner(System.in)) {
+                                               password = s.next();
+                                       }
+                               }
+                               NamedResource namedResource = new NamedResource() {
+
+                                       @Override
+                                       public String getName() {
+                                               return keyPath;
+                                       }
+                               };
+                               KeyPair keyPair = ClientIdentityLoader.DEFAULT
+                                               .loadClientIdentities(null, namedResource, FilePasswordProvider.of(password)).iterator().next();
+                               sshAgent.addIdentity(keyPair, "NO COMMENT");
+                       }
+
+//                     List<? extends Map.Entry<PublicKey, String>> identities = sshAgent.getIdentities();
+//                     for (Map.Entry<PublicKey, String> entry : identities) {
+//                             System.out.println(entry.getValue() + " : " + entry.getKey());
+//                     }
+
+                       ConnectFuture connectFuture = client.connect(login, host, port);
+                       connectFuture.await();
+                       ClientSession session = connectFuture.getSession();
+
+                       try {
+
+//                             session.addPasswordIdentity(new String(password));
+                               session.auth().verify(1000l);
+
+                               SftpFileSystemProvider fsProvider = new SftpFileSystemProvider(client);
+
+                               SftpFileSystem fs = fsProvider.newFileSystem(session);
+                               Path testPath = fs.getPath("/home/" + login + "/tmp");
+                               Files.list(testPath).forEach(System.out::println);
+                               test(testPath);
+
+                       } finally {
+                               client.stop();
+                       }
+               } catch (Exception e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+
+       static void test(Path testBase) {
+               try {
+                       Path testPath = testBase.resolve("ssh-test.txt");
+                       Files.createFile(testPath);
+                       log.debug("Created file " + testPath);
+                       Files.delete(testPath);
+                       log.debug("Deleted " + testPath);
+                       String txt = "TEST\nTEST2\n";
+                       byte[] arr = txt.getBytes();
+                       Files.write(testPath, arr);
+                       log.debug("Wrote " + testPath);
+                       byte[] read = Files.readAllBytes(testPath);
+                       log.debug("Read " + testPath);
+                       Path testDir = testBase.resolve("testDir");
+                       log.debug("Resolved " + testDir);
+                       // Copy
+                       Files.createDirectory(testDir);
+                       log.debug("Created directory " + testDir);
+                       Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
+                       log.debug("Created sub directories " + subsubdir);
+                       Path copiedFile = testDir.resolve("copiedFile.txt");
+                       log.debug("Resolved " + copiedFile);
+                       Path relativeCopiedFile = testDir.relativize(copiedFile);
+                       log.debug("Relative copied file " + relativeCopiedFile);
+                       try (OutputStream out = Files.newOutputStream(copiedFile);
+                                       InputStream in = Files.newInputStream(testPath)) {
+                               IOUtils.copy(in, out);
+                       }
+                       log.debug("Copied " + testPath + " to " + copiedFile);
+                       Files.delete(testPath);
+                       log.debug("Deleted " + testPath);
+                       byte[] copiedRead = Files.readAllBytes(copiedFile);
+                       log.debug("Read " + copiedFile);
+                       // Browse directories
+                       DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
+                       int fileCount = 0;
+                       Path listedFile = null;
+                       for (Path file : files) {
+                               fileCount++;
+                               if (!Files.isDirectory(file))
+                                       listedFile = file;
+                       }
+                       log.debug("Listed " + testDir);
+                       // Generic attributes
+                       Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
+                       log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshCli.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshCli.java
new file mode 100644 (file)
index 0000000..12b4d5e
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.cms.ssh.cli;
+
+import org.argeo.api.cli.CommandsCli;
+
+public class SshCli extends CommandsCli {
+       public SshCli(String commandName) {
+               super(commandName);
+               addCommand("shell", new SshShell());
+       }
+
+       @Override
+       public String getDescription() {
+               return "SSH utilities.";
+       }
+
+        
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshShell.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshShell.java
new file mode 100644 (file)
index 0000000..78903a7
--- /dev/null
@@ -0,0 +1,115 @@
+package org.argeo.cms.ssh.cli;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.agent.unix.UnixAgentFactory;
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.ssh.AbstractSsh;
+import org.argeo.cms.ssh.Ssh;
+
+public class SshShell implements DescribedCommand<String> {
+               private Option portOption;
+
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       portOption = Option.builder().option("p").longOpt("port").hasArg().desc("port to connect to").build();
+                       options.addOption(portOption);
+                       return options;
+               }
+
+               @Override
+               public String apply(List<String> args) {
+                       CommandLine cl = toCommandLine(args);
+                       String portStr = cl.getOptionValue(portOption);
+                       if (portStr == null)
+                               portStr = "22";
+
+                       String host = cl.getArgList().get(0);
+
+                       String uriStr = "ssh://" + host + ":" + portStr + "/";
+                       // System.out.println(uriStr);
+                       URI uri = URI.create(uriStr);
+
+                       Ssh ssh = null;
+                       try {
+                               ssh = new Ssh(uri);
+                               boolean osAgent;
+                               SshAgent sshAgent;
+                               try {
+                                       String sshAuthSockentEnv = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
+                                       if (sshAuthSockentEnv != null) {
+                                               ssh.getSshClient().getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, sshAuthSockentEnv);
+                                               SshAgentFactory agentFactory = new UnixAgentFactory();
+                                               ssh.getSshClient().setAgentFactory(agentFactory);
+                                               sshAgent = agentFactory.createClient(null, ssh.getSshClient());
+                                               osAgent = true;
+                                       } else {
+                                               osAgent = false;
+                                       }
+                               } catch (Exception e) {
+                                       e.printStackTrace();
+                                       osAgent = false;
+                               }
+
+                               if (!osAgent) {
+                                       SshAgentFactory agentFactory = new LocalAgentFactory();
+                                       ssh.getSshClient().setAgentFactory(agentFactory);
+                                       sshAgent = agentFactory.createClient(null, ssh.getSshClient());
+                                       String keyPath = System.getProperty("user.home") + "/.ssh/id_rsa";
+
+                                       char[] keyPassword = AbstractSsh.readPassword();
+                                       NamedResource namedResource = new NamedResource() {
+
+                                               @Override
+                                               public String getName() {
+                                                       return keyPath;
+                                               }
+                                       };
+                                       KeyPair keyPair = ClientIdentityLoader.DEFAULT
+                                                       .loadClientIdentities(null, namedResource, FilePasswordProvider.of(new String(keyPassword)))
+                                                       .iterator().next();
+                                       sshAgent.addIdentity(keyPair, "NO COMMENT");
+                               }
+
+//                             char[] keyPassword = AbstractSsh.readPassword();
+//                             SshKeyPair keyPair = SshKeyPair.loadDefault(keyPassword);
+//                             Arrays.fill(keyPassword, '*');
+//                             ssh.setSshKeyPair(keyPair);
+//                             ssh.authenticate();
+                               ssh.verifyAuth();
+
+                               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+                               System.out.println("Ssh available in " + jvmUptime + " ms.");
+
+                               AbstractSsh.openShell(ssh);
+                       } catch (IOException | GeneralSecurityException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } finally {
+                               if (ssh != null)
+                                       ssh.closeSession();
+                       }
+                       return null;
+               }
+
+               @Override
+               public String getDescription() {
+                       return "Launch a static CMS.";
+               }
+
+       }
\ No newline at end of file
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/package-info.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/package-info.java
new file mode 100644 (file)
index 0000000..9555662
--- /dev/null
@@ -0,0 +1,2 @@
+/** SSH support. */
+package org.argeo.cms.ssh;
\ No newline at end of file
diff --git a/org.argeo.cms.sql/.classpath b/org.argeo.cms.sql/.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.sql/.project b/org.argeo.cms.sql/.project
deleted file mode 100644 (file)
index b96a541..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.sql</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>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.cms.sql/bnd.bnd b/org.argeo.cms.sql/bnd.bnd
deleted file mode 100644 (file)
index 9c73009..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Import-Package: org.postgresql;version="[42,43)"
diff --git a/org.argeo.cms.sql/build.properties b/org.argeo.cms.sql/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/org.argeo.cms.sql/src/org/argeo/cms/sql/postgres/CheckPg.java b/org.argeo.cms.sql/src/org/argeo/cms/sql/postgres/CheckPg.java
deleted file mode 100644 (file)
index bc002a6..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.argeo.cms.sql.postgres;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-
-import org.postgresql.Driver;
-
-/** Simple PostgreSQL check. */
-public class CheckPg {
-
-       public List<String> listTables() {
-               String osUser = System.getProperty("user.name");
-
-               String url = "jdbc:postgresql://localhost/" + osUser;
-               Properties props = new Properties();
-               props.setProperty("user", osUser);
-               props.setProperty("password", "changeit");
-               List<String> result = new ArrayList<>();
-
-               Driver driver = new Driver();
-               try (Connection conn = driver.connect(url, props); Statement s = conn.createStatement();) {
-                       s.execute("SELECT * FROM pg_catalog.pg_tables");
-                       ResultSet rs = s.getResultSet();
-                       while (rs.next()) {
-                               result.add(rs.getString("tablename"));
-                       }
-                       return result;
-               } catch (SQLException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       public static void main(String[] args) {
-               new CheckPg().listTables().forEach(System.out::println);
-       }
-
-}
diff --git a/org.argeo.cms.ssh/.classpath b/org.argeo.cms.ssh/.classpath
deleted file mode 100644 (file)
index 81fe078..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-17"/>
-       <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.ssh/.gitignore b/org.argeo.cms.ssh/.gitignore
deleted file mode 100644 (file)
index 7fb0c18..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-/hostkey.ser
-/id_rsa
-/id_rsa.pub
diff --git a/org.argeo.cms.ssh/.project b/org.argeo.cms.ssh/.project
deleted file mode 100644 (file)
index d46b3af..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.ssh</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.ssh/.settings/org.eclipse.jdt.core.prefs b/org.argeo.cms.ssh/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644 (file)
index 997d664..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.builder.annotationPath.allLocations=disabled
-org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
-org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
-org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
-org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
-org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
-org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
-org.eclipse.jdt.core.compiler.problem.APILeak=warning
-org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
-org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
-org.eclipse.jdt.core.compiler.problem.deadCode=warning
-org.eclipse.jdt.core.compiler.problem.deprecation=warning
-org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
-org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
-org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
-org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
-org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
-org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
-org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
-org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
-org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
-org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
-org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
-org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
-org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
-org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
-org.eclipse.jdt.core.compiler.problem.nullReference=warning
-org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
-org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
-org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
-org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
-org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
-org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
-org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
-org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info
-org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
-org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
-org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
-org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
-org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
-org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
-org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
-org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedImport=warning
-org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
-org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
-org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/org.argeo.cms.ssh/OSGI-INF/cmsSshServer.xml b/org.argeo.cms.ssh/OSGI-INF/cmsSshServer.xml
deleted file mode 100644 (file)
index aa2d8db..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="CMS SSH Server" immediate="true">
-   <implementation class="org.argeo.cms.ssh.CmsSshServer"/>
-   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
-</scr:component>
diff --git a/org.argeo.cms.ssh/bnd.bnd b/org.argeo.cms.ssh/bnd.bnd
deleted file mode 100644 (file)
index e708391..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Import-Package: \
-org.apache.sshd.server.forward,\
-org.apache.sshd.common.forward,\
-*
-
-Service-Component: \
-OSGI-INF/cmsSshServer.xml
diff --git a/org.argeo.cms.ssh/build.properties b/org.argeo.cms.ssh/build.properties
deleted file mode 100644 (file)
index 1e3132a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               OSGI-INF/
-source.. = src/
-additional.bundles = org.apache.sshd.common,\
-                     org.apache.sshd.core,\
-                     org.slf4j.api,\
-                     org.argeo.ext.slf4j
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/AbstractSsh.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/AbstractSsh.java
deleted file mode 100644 (file)
index f2525bf..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-package org.argeo.cms.ssh;
-
-import java.io.Console;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Scanner;
-import java.util.Set;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.channel.ClientChannel;
-import org.apache.sshd.client.channel.ClientChannelEvent;
-import org.apache.sshd.client.future.ConnectFuture;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.util.io.input.NoCloseInputStream;
-import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
-import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
-import org.argeo.api.cms.CmsLog;
-
-@SuppressWarnings("restriction")
-public abstract class AbstractSsh {
-       private final static CmsLog log = CmsLog.getLog(AbstractSsh.class);
-
-       private static SshClient sshClient;
-       private static SftpFileSystemProvider sftpFileSystemProvider;
-
-       private boolean passwordSet = false;
-       private ClientSession session;
-
-       private SshKeyPair sshKeyPair;
-
-       public synchronized SshClient getSshClient() {
-               if (sshClient == null) {
-                       long begin = System.currentTimeMillis();
-                       sshClient = SshClient.setUpDefaultClient();
-                       sshClient.start();
-                       long duration = System.currentTimeMillis() - begin;
-                       if (log.isDebugEnabled())
-                               log.debug("SSH client started in " + duration + " ms");
-                       Runtime.getRuntime().addShutdownHook(new Thread(() -> sshClient.stop(), "Stop SSH client"));
-               }
-               return sshClient;
-       }
-
-       synchronized SftpFileSystemProvider getSftpFileSystemProvider() {
-               if (sftpFileSystemProvider == null) {
-                       sftpFileSystemProvider = new SftpFileSystemProvider(sshClient);
-               }
-               return sftpFileSystemProvider;
-       }
-
-       public void authenticate() {
-               if (sshKeyPair != null) {
-                       session.addPublicKeyIdentity(sshKeyPair.asKeyPair());
-               } else {
-
-                       if (!passwordSet) {
-                               String password;
-                               Console console = System.console();
-                               if (console == null) {// IDE
-                                       System.out.print("Password: ");
-                                       try (Scanner s = new Scanner(System.in)) {
-                                               password = s.next();
-                                       }
-                               } else {
-                                       console.printf("Password: ");
-                                       char[] pwd = console.readPassword();
-                                       password = new String(pwd);
-                                       Arrays.fill(pwd, ' ');
-                               }
-                               session.addPasswordIdentity(password);
-                               passwordSet = true;
-                       }
-               }
-               verifyAuth();
-       }
-
-       public void verifyAuth() {
-               try {
-                       session.auth().verify(1000l);
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot verify auth", e);
-               }
-       }
-
-       public static char[] readPassword() {
-               Console console = System.console();
-               if (console == null) {// IDE
-                       System.out.print("Password: ");
-                       try (Scanner s = new Scanner(System.in)) {
-                               String password = s.next();
-                               return password.toCharArray();
-                       }
-               } else {
-                       console.printf("Password: ");
-                       char[] pwd = console.readPassword();
-                       return pwd;
-               }
-       }
-
-       void addPassword(String password) {
-               session.addPasswordIdentity(password);
-       }
-
-       void loadKey(String password) {
-               loadKey(password, System.getProperty("user.home") + "/.ssh/id_rsa");
-       }
-
-       void loadKey(String password, String keyPath) {
-//             try {
-//                     KeyPair keyPair = ClientIdentityLoader.DEFAULT.loadClientIdentity(keyPath,
-//                                     FilePasswordProvider.of(password));
-//                     session.addPublicKeyIdentity(keyPair);
-//             } catch (IOException | GeneralSecurityException e) {
-//                     throw new IllegalStateException(e);
-//             }
-       }
-
-       void openSession(URI uri) {
-               openSession(uri.getUserInfo(), uri.getHost(), uri.getPort() > 0 ? uri.getPort() : null);
-       }
-
-       void openSession(String login, String host, Integer port) {
-               if (session != null)
-                       throw new IllegalStateException("Session is already open");
-
-               if (host == null)
-                       host = "localhost";
-               if (port == null)
-                       port = 22;
-               if (login == null)
-                       login = System.getProperty("user.name");
-               String password = null;
-               int sepIndex = login.indexOf(':');
-               if (sepIndex > 0)
-                       if (sepIndex + 1 < login.length()) {
-                               password = login.substring(sepIndex + 1);
-                               login = login.substring(0, sepIndex);
-                       } else {
-                               throw new IllegalArgumentException("Illegal authority: " + login);
-                       }
-               try {
-                       ConnectFuture connectFuture = getSshClient().connect(login, host, port);
-                       connectFuture.await();
-                       ClientSession session = connectFuture.getSession();
-                       if (password != null) {
-                               session.addPasswordIdentity(password);
-                               passwordSet = true;
-                       }
-                       this.session = session;
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot connect to " + host + ":" + port);
-               }
-       }
-
-       public void closeSession() {
-               if (session == null)
-                       throw new IllegalStateException("No session is open");
-               try {
-                       session.close();
-               } catch (IOException e) {
-                       e.printStackTrace();
-               } finally {
-                       session = null;
-               }
-       }
-
-       ClientSession getSession() {
-               return session;
-       }
-
-       public void setSshKeyPair(SshKeyPair sshKeyPair) {
-               this.sshKeyPair = sshKeyPair;
-       }
-
-       public static void openShell(AbstractSsh ssh) {
-               openShell(ssh.getSession());
-       }
-
-       public static void openShell(ClientSession session) {
-               try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) {
-                       channel.setIn(new NoCloseInputStream(System.in));
-                       channel.setOut(new NoCloseOutputStream(System.out));
-                       channel.setErr(new NoCloseOutputStream(System.err));
-                       channel.open();
-
-                       Set<ClientChannelEvent> events = new HashSet<>();
-                       events.add(ClientChannelEvent.CLOSED);
-                       channel.waitFor(events, 0);
-               } catch (IOException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               } finally {
-                       session.close(false);
-               }
-       }
-
-       static URI toUri(String username, String host, int port) {
-               try {
-                       if (username == null)
-                               username = "root";
-                       return new URI("ssh://" + username + "@" + host + ":" + port);
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("Cannot generate SSH URI to " + host + ":" + port + " for " + username,
-                                       e);
-               }
-       }
-
-}
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/BasicSshServer.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/BasicSshServer.java
deleted file mode 100644 (file)
index 9e93893..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-package org.argeo.cms.ssh;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.apache.sshd.scp.server.ScpCommandFactory;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
-import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
-import org.apache.sshd.server.shell.ProcessShellFactory;
-import org.argeo.util.OS;
-
-/** A simple SSH server with some defaults. Supports SCP. */
-@SuppressWarnings("restriction")
-public class BasicSshServer {
-       private Integer port;
-       private Path hostKeyPath;
-
-       private SshServer sshd = null;
-
-       public BasicSshServer(Integer port, Path hostKeyPath) {
-               this.port = port;
-               this.hostKeyPath = hostKeyPath;
-       }
-
-       public void init() {
-               try {
-                       sshd = SshServer.setUpDefaultServer();
-                       sshd.setPort(port);
-                       if (hostKeyPath == null)
-                               throw new IllegalStateException("An SSH server key must be set");
-                       sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
-                       // sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i",
-                       // "-l" }));
-                       String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
-                       // FIXME transfer args
-//                     sshd.setShellFactory(new ProcessShellFactory(shellCommand));
-                       sshd.setShellFactory(new ProcessShellFactory(shellCommand[0], shellCommand));
-                       sshd.setCommandFactory(new ScpCommandFactory());
-
-                       sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
-                       sshd.start();
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot start SSH server on port " + port, e);
-               }
-       }
-
-       public void destroy() {
-               try {
-                       sshd.stop();
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot stop SSH server on port " + port, e);
-               }
-       }
-
-       public Integer getPort() {
-               return port;
-       }
-
-       public void setPort(Integer port) {
-               this.port = port;
-       }
-
-       public Path getHostKeyPath() {
-               return hostKeyPath;
-       }
-
-       public void setHostKeyPath(Path hostKeyPath) {
-               this.hostKeyPath = hostKeyPath;
-       }
-
-       public static void main(String[] args) {
-               int port = 2222;
-               Path hostKeyPath = Paths.get("hostkey.ser");
-               try {
-                       if (args.length > 0)
-                               port = Integer.parseInt(args[0]);
-                       if (args.length > 1)
-                               hostKeyPath = Paths.get(args[1]);
-               } catch (Exception e1) {
-                       printUsage();
-               }
-
-               BasicSshServer sshServer = new BasicSshServer(port, hostKeyPath);
-               sshServer.init();
-               Runtime.getRuntime().addShutdownHook(new Thread("Shutdown SSH server") {
-
-                       @Override
-                       public void run() {
-                               sshServer.destroy();
-                       }
-               });
-               try {
-                       synchronized (sshServer) {
-                               sshServer.wait();
-                       }
-               } catch (InterruptedException e) {
-                       sshServer.destroy();
-               }
-
-       }
-
-       public static void printUsage() {
-               System.out.println("java " + BasicSshServer.class.getName() + " [port] [server key path]");
-       }
-
-}
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/CmsSshServer.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/CmsSshServer.java
deleted file mode 100644 (file)
index 7246d60..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.argeo.cms.ssh;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Collections;
-
-import org.apache.sshd.common.forward.PortForwardingEventListener;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.util.net.SshdSocketAddress;
-import org.apache.sshd.scp.server.ScpCommandFactory;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.gss.GSSAuthenticator;
-import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
-import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
-import org.apache.sshd.server.jaas.JaasPasswordAuthenticator;
-import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
-import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
-import org.apache.sshd.sftp.server.SftpSubsystemFactory;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsState;
-import org.argeo.cms.CmsDeployProperty;
-
-public class CmsSshServer {
-       private final static CmsLog log = CmsLog.getLog(CmsSshServer.class);
-       private static final String DEFAULT_SSH_HOST_KEY_PATH = CmsConstants.NODE + '/' + CmsConstants.NODE + ".ser";
-
-       private CmsState cmsState;
-       private SshServer sshd = null;
-
-       private int port;
-       private String host;
-
-       public void start() {
-               String portStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
-               if (portStr == null)
-                       return; // ignore
-               port = Integer.parseInt(portStr);
-
-               host = cmsState.getDeployProperty(CmsDeployProperty.HOST.getProperty());
-
-               Path hostKeyPath = cmsState.getDataPath(DEFAULT_SSH_HOST_KEY_PATH);
-
-               try {
-                       sshd = SshServer.setUpDefaultServer();
-                       sshd.setPort(port);
-                       if (host != null)
-                               sshd.setHost(host);
-                       if (hostKeyPath == null)
-                               throw new IllegalStateException("An SSH server key must be set");
-                       sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
-                       // sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i",
-                       // "-l" }));
-//                     String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
-                       // FIXME transfer args
-//             sshd.setShellFactory(new ProcessShellFactory(shellCommand));
-                       sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
-                       sshd.setCommandFactory(new ScpCommandFactory());
-
-                       // tunnels
-                       sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
-                       // sshd.setForwardingFilter(ForwardingFilter.asForwardingFilter(null, null,
-                       // TcpForwardingFilter.DEFAULT));
-                       // sshd.setForwarderFactory(DefaultForwarderFactory.INSTANCE);
-//                     TcpForwardingFilter tcpForwardingFilter = sshd.getTcpForwardingFilter();
-                       sshd.addPortForwardingEventListener(new PortForwardingEventListener() {
-
-                               @Override
-                               public void establishingExplicitTunnel(Session session, SshdSocketAddress local,
-                                               SshdSocketAddress remote, boolean localForwarding) throws IOException {
-                                       log.debug("Establishing tunnel " + local + ", " + remote);
-                               }
-
-                               @Override
-                               public void establishedExplicitTunnel(Session session, SshdSocketAddress local,
-                                               SshdSocketAddress remote, boolean localForwarding, SshdSocketAddress boundAddress,
-                                               Throwable reason) throws IOException {
-                                       log.debug("Established tunnel " + local + ", " + remote + ", " + boundAddress);
-                               }
-
-                               @Override
-                               public void establishingDynamicTunnel(Session session, SshdSocketAddress local) throws IOException {
-                                       log.debug("Establishing dynamic tunnel " + local);
-                               }
-
-                               @Override
-                               public void establishedDynamicTunnel(Session session, SshdSocketAddress local,
-                                               SshdSocketAddress boundAddress, Throwable reason) throws IOException {
-                                       log.debug("Established dynamic tunnel " + local);
-                               }
-
-                       });
-
-                       // Authentication
-                       //sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
-                       sshd.setPublickeyAuthenticator(null);
-                       // sshd.setKeyboardInteractiveAuthenticator(null);
-                       JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator();
-                       jaasPasswordAuthenticator.setDomain(CmsAuth.NODE.getLoginContextName());
-                       sshd.setPasswordAuthenticator(jaasPasswordAuthenticator);
-
-                       Path krb5keyTab = cmsState.getDataPath("node/krb5.keytab");
-                       if (Files.exists(krb5keyTab)) {
-                               // FIXME experimental
-                               GSSAuthenticator gssAuthenticator = new GSSAuthenticator();
-                               gssAuthenticator.setKeytabFile(cmsState.getDataPath("node/krb5.keytab").toString());
-                               gssAuthenticator.setServicePrincipalName("HTTP@" + host);
-                               sshd.setGSSAuthenticator(gssAuthenticator);
-                       }
-
-                       // SFTP
-                       sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
-
-                       // start
-                       sshd.start();
-
-                       log.debug(() -> "CMS SSH server started on port " + port + (host != null ? " of host " + host : ""));
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot start SSH server on port " + port, e);
-               }
-
-       }
-
-       public void stop() {
-               if (sshd == null)
-                       return;
-               try {
-                       sshd.stop();
-                       log.debug(() -> "CMS SSH server stopped on port " + port + (host != null ? " of host " + host : ""));
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot stop SSH server", e);
-               }
-
-       }
-
-       public void setCmsState(CmsState cmsState) {
-               this.cmsState = cmsState;
-       }
-
-}
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/Sftp.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/Sftp.java
deleted file mode 100644 (file)
index f6f4474..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.argeo.cms.ssh;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.file.FileSystem;
-import java.nio.file.Path;
-
-import org.apache.sshd.sftp.client.fs.SftpFileSystem;
-
-/** Create an SFTP {@link FileSystem}. */
-public class Sftp extends AbstractSsh {
-       private URI uri;
-
-       private SftpFileSystem fileSystem;
-
-       public Sftp(String username, String host, int port) {
-               this(AbstractSsh.toUri(username, host, port));
-       }
-
-       public Sftp(URI uri) {
-               this.uri = uri;
-               openSession(uri);
-       }
-
-       public FileSystem getFileSystem() {
-               if (fileSystem == null) {
-                       try {
-                               authenticate();
-                               fileSystem = getSftpFileSystemProvider().newFileSystem(getSession());
-                       } catch (IOException e) {
-                               throw new IllegalStateException(e);
-                       }
-               }
-               return fileSystem;
-       }
-
-       public Path getBasePath() {
-               String p = uri.getPath() != null ? uri.getPath() : "/";
-               return getFileSystem().getPath(p);
-       }
-
-}
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/Ssh.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/Ssh.java
deleted file mode 100644 (file)
index 6e7b6ef..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.argeo.cms.ssh;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-
-/** Create an SSH shell. */
-public class Ssh extends AbstractSsh {
-       private final URI uri;
-
-       public Ssh(String username, String host, int port) {
-               this(AbstractSsh.toUri(username, host, port));
-       }
-
-       public Ssh(URI uri) {
-               this.uri = uri;
-               openSession(uri);
-       }
-
-       public static void main(String[] args) {
-               Options options = getOptions();
-               CommandLineParser parser = new DefaultParser();
-               try {
-                       CommandLine line = parser.parse(options, args);
-                       List<String> remaining = line.getArgList();
-                       if (remaining.size() == 0) {
-                               System.err.println("There must be at least one argument");
-                               printHelp(options);
-                               System.exit(1);
-                       }
-                       URI uri = new URI("ssh://" + remaining.get(0));
-                       List<String> command = new ArrayList<>();
-                       if (remaining.size() > 1) {
-                               for (int i = 1; i < remaining.size(); i++) {
-                                       command.add(remaining.get(i));
-                               }
-                       }
-
-                       // auth
-                       Ssh ssh = new Ssh(uri);
-                       ssh.authenticate();
-
-                       if (command.size() == 0) {// shell
-                               AbstractSsh.openShell(ssh.getSession());
-                       } else {// execute command
-
-                       }
-                       ssh.closeSession();
-               } catch (Exception exp) {
-                       exp.printStackTrace();
-                       printHelp(options);
-                       System.exit(1);
-               } finally {
-
-               }
-       }
-
-       public URI getUri() {
-               return uri;
-       }
-
-       public static Options getOptions() {
-               Options options = new Options();
-//             options.addOption("p", true, "port");
-               options.addOption(Option.builder("p").hasArg().argName("port").desc("port of the SSH server").build());
-
-               return options;
-       }
-
-       public static void printHelp(Options options) {
-               HelpFormatter formatter = new HelpFormatter();
-               formatter.printHelp("ssh [username@]hostname", options, true);
-       }
-}
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshKeyPair.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshKeyPair.java
deleted file mode 100644 (file)
index f5cbb04..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-package org.argeo.cms.ssh;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.RSAPrivateCrtKey;
-import java.security.spec.RSAPublicKeySpec;
-
-import org.apache.sshd.common.config.keys.KeyUtils;
-import org.apache.sshd.common.config.keys.PublicKeyEntry;
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.openssl.PEMDecryptorProvider;
-import org.bouncycastle.openssl.PEMEncryptedKeyPair;
-import org.bouncycastle.openssl.PEMKeyPair;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.openssl.PKCS8Generator;
-import org.bouncycastle.openssl.bc.BcPEMDecryptorProvider;
-import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
-import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
-import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
-import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
-import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
-import org.bouncycastle.operator.InputDecryptorProvider;
-import org.bouncycastle.operator.OutputEncryptor;
-import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
-
-@SuppressWarnings("restriction")
-public class SshKeyPair {
-       public final static String RSA_KEY_TYPE = "ssh-rsa";
-
-       private PublicKey publicKey;
-       private PrivateKey privateKey;
-       private KeyPair keyPair;
-
-       public SshKeyPair(KeyPair keyPair) {
-               super();
-               this.publicKey = keyPair.getPublic();
-               this.privateKey = keyPair.getPrivate();
-               this.keyPair = keyPair;
-       }
-
-       public SshKeyPair(PublicKey publicKey, PrivateKey privateKey) {
-               super();
-               this.publicKey = publicKey;
-               this.privateKey = privateKey;
-               this.keyPair = new KeyPair(publicKey, privateKey);
-       }
-
-       public KeyPair asKeyPair() {
-               return keyPair;
-       }
-
-       public String getPublicKeyAsOpenSshString() {
-               return PublicKeyEntry.toString(publicKey);
-       }
-
-       public String getPrivateKeyAsPemString(char[] password) {
-               try {
-                       Object obj;
-
-                       if (password != null) {
-                               JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(
-                                               PKCS8Generator.PBE_SHA1_3DES);
-                               encryptorBuilder.setPasssword(password);
-                               OutputEncryptor oe = encryptorBuilder.build();
-                               JcaPKCS8Generator gen = new JcaPKCS8Generator(privateKey, oe);
-                               obj = gen.generate();
-                       } else {
-                               obj = privateKey;
-                       }
-
-                       StringWriter sw = new StringWriter();
-                       JcaPEMWriter pemWrt = new JcaPEMWriter(sw);
-                       pemWrt.writeObject(obj);
-                       pemWrt.close();
-                       return sw.toString();
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot convert private key", e);
-               }
-       }
-
-       public static SshKeyPair loadOrGenerate(Path privateKeyPath, int size, char[] password) {
-               try {
-                       SshKeyPair sshKeyPair;
-                       if (Files.exists(privateKeyPath)) {
-//                             String privateKeyStr = new String(Files.readAllBytes(privateKeyPath), StandardCharsets.US_ASCII);
-                               sshKeyPair = load(
-                                               new InputStreamReader(Files.newInputStream(privateKeyPath), StandardCharsets.US_ASCII),
-                                               password);
-                               // TOD make sure public key is consistemt
-                       } else {
-                               sshKeyPair = generate(size);
-                               Files.write(privateKeyPath,
-                                               sshKeyPair.getPrivateKeyAsPemString(password).getBytes(StandardCharsets.US_ASCII));
-                               Path publicKeyPath = privateKeyPath.resolveSibling(privateKeyPath.getFileName() + ".pub");
-                               Files.write(publicKeyPath,
-                                               sshKeyPair.getPublicKeyAsOpenSshString().getBytes(StandardCharsets.US_ASCII));
-                       }
-                       return sshKeyPair;
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot read or write private key " + privateKeyPath, e);
-               }
-       }
-
-       public static SshKeyPair generate(int size) {
-               return generate(RSA_KEY_TYPE, size);
-       }
-
-       public static SshKeyPair generate(String keyType, int size) {
-               try {
-                       KeyPair keyPair = KeyUtils.generateKeyPair(keyType, size);
-                       PublicKey publicKey = keyPair.getPublic();
-                       PrivateKey privateKey = keyPair.getPrivate();
-                       return new SshKeyPair(publicKey, privateKey);
-               } catch (GeneralSecurityException e) {
-                       throw new RuntimeException("Cannot generate SSH key", e);
-               }
-       }
-
-       public static SshKeyPair loadDefault(char[] password) {
-               Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_rsa");
-               // TODO try other formats
-               return load(privateKeyPath, password);
-       }
-
-       public static SshKeyPair load(Path privateKeyPath, char[] password) {
-               try (Reader reader = Files.newBufferedReader(privateKeyPath)) {
-                       return load(reader, password);
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot load private key from " + privateKeyPath, e);
-               }
-
-       }
-
-       public static SshKeyPair load(Reader reader, char[] password) {
-               try (PEMParser pemParser = new PEMParser(reader)) {
-                       Object object = pemParser.readObject();
-                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter();// .setProvider("BC");
-                       KeyPair kp;
-                       if (object instanceof PEMEncryptedKeyPair) {
-                               PEMEncryptedKeyPair ekp = (PEMEncryptedKeyPair) object;
-                               PEMDecryptorProvider decryptorProvider = new BcPEMDecryptorProvider(password);
-                               PEMKeyPair pemKp = ekp.decryptKeyPair(decryptorProvider);
-                               kp = converter.getKeyPair(pemKp);
-                       } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
-                               // Encrypted key - we will use provided password
-                               PKCS8EncryptedPrivateKeyInfo ckp = (PKCS8EncryptedPrivateKeyInfo) object;
-//                             PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password);
-                               InputDecryptorProvider inputDecryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder()
-                                               .build(password);
-                               PrivateKeyInfo pkInfo = ckp.decryptPrivateKeyInfo(inputDecryptorProvider);
-                               PrivateKey privateKey = converter.getPrivateKey(pkInfo);
-
-                               // generate public key
-                               RSAPrivateCrtKey privk = (RSAPrivateCrtKey) privateKey;
-                               RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(privk.getModulus(),
-                                               privk.getPublicExponent());
-                               KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-                               PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
-
-                               kp = new KeyPair(publicKey, privateKey);
-                       } else {
-                               // Unencrypted key - no password needed
-//                             PKCS8EncryptedPrivateKeyInfo ukp = (PKCS8EncryptedPrivateKeyInfo) object;
-                               PEMKeyPair pemKp = (PEMKeyPair) object;
-                               kp = converter.getKeyPair(pemKp);
-                       }
-                       return new SshKeyPair(kp);
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot load private key", e);
-               }
-       }
-
-       public static void main(String args[]) {
-               Path privateKeyPath = Paths.get(System.getProperty("user.dir") + "/id_rsa");
-               SshKeyPair skp = SshKeyPair.loadOrGenerate(privateKeyPath, 1024, null);
-               System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
-               System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
-               System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
-
-               StringReader reader = new StringReader(skp.getPrivateKeyAsPemString(null));
-               skp = SshKeyPair.load(reader, null);
-               System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
-               System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
-               System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
-
-               reader = new StringReader(skp.getPrivateKeyAsPemString("demo".toCharArray()));
-               skp = SshKeyPair.load(reader, "demo".toCharArray());
-               System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
-               System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
-               System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
-       }
-
-}
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshSync.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/SshSync.java
deleted file mode 100644 (file)
index 71b6365..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-package org.argeo.cms.ssh;
-
-import java.io.Console;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.KeyPair;
-import java.util.Map;
-import java.util.Scanner;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.sshd.agent.SshAgent;
-import org.apache.sshd.agent.SshAgentFactory;
-import org.apache.sshd.agent.local.LocalAgentFactory;
-import org.apache.sshd.agent.unix.UnixAgentFactory;
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.config.keys.ClientIdentityLoader;
-import org.apache.sshd.client.future.ConnectFuture;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.config.keys.FilePasswordProvider;
-import org.apache.sshd.sftp.client.fs.SftpFileSystem;
-import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
-import org.argeo.api.cms.CmsLog;
-
-public class SshSync {
-       private final static CmsLog log = CmsLog.getLog(SshSync.class);
-
-       public static void main(String[] args) {
-
-               try (SshClient client = SshClient.setUpDefaultClient()) {
-                       client.start();
-                       boolean osAgent = false;
-                       SshAgentFactory agentFactory = osAgent ? new UnixAgentFactory() : new LocalAgentFactory();
-                       // SshAgentFactory agentFactory = new LocalAgentFactory();
-                       client.setAgentFactory(agentFactory);
-                       SshAgent sshAgent = agentFactory.createClient(null, client);
-
-                       String login = System.getProperty("user.name");
-                       String host = "localhost";
-                       int port = 22;
-
-                       if (!osAgent) {
-                               String keyPath = "/home/" + login + "/.ssh/id_rsa";
-
-                               String password;
-                               Console console = System.console();
-                               if (console != null) {
-                                       password = new String(console.readPassword(keyPath + ": "));
-                               } else {
-                                       System.out.print(keyPath + ": ");
-                                       try (Scanner s = new Scanner(System.in)) {
-                                               password = s.next();
-                                       }
-                               }
-                               NamedResource namedResource = new NamedResource() {
-
-                                       @Override
-                                       public String getName() {
-                                               return keyPath;
-                                       }
-                               };
-                               KeyPair keyPair = ClientIdentityLoader.DEFAULT
-                                               .loadClientIdentities(null, namedResource, FilePasswordProvider.of(password)).iterator().next();
-                               sshAgent.addIdentity(keyPair, "NO COMMENT");
-                       }
-
-//                     List<? extends Map.Entry<PublicKey, String>> identities = sshAgent.getIdentities();
-//                     for (Map.Entry<PublicKey, String> entry : identities) {
-//                             System.out.println(entry.getValue() + " : " + entry.getKey());
-//                     }
-
-                       ConnectFuture connectFuture = client.connect(login, host, port);
-                       connectFuture.await();
-                       ClientSession session = connectFuture.getSession();
-
-                       try {
-
-//                             session.addPasswordIdentity(new String(password));
-                               session.auth().verify(1000l);
-
-                               SftpFileSystemProvider fsProvider = new SftpFileSystemProvider(client);
-
-                               SftpFileSystem fs = fsProvider.newFileSystem(session);
-                               Path testPath = fs.getPath("/home/" + login + "/tmp");
-                               Files.list(testPath).forEach(System.out::println);
-                               test(testPath);
-
-                       } finally {
-                               client.stop();
-                       }
-               } catch (Exception e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               }
-       }
-
-       static void test(Path testBase) {
-               try {
-                       Path testPath = testBase.resolve("ssh-test.txt");
-                       Files.createFile(testPath);
-                       log.debug("Created file " + testPath);
-                       Files.delete(testPath);
-                       log.debug("Deleted " + testPath);
-                       String txt = "TEST\nTEST2\n";
-                       byte[] arr = txt.getBytes();
-                       Files.write(testPath, arr);
-                       log.debug("Wrote " + testPath);
-                       byte[] read = Files.readAllBytes(testPath);
-                       log.debug("Read " + testPath);
-                       Path testDir = testBase.resolve("testDir");
-                       log.debug("Resolved " + testDir);
-                       // Copy
-                       Files.createDirectory(testDir);
-                       log.debug("Created directory " + testDir);
-                       Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
-                       log.debug("Created sub directories " + subsubdir);
-                       Path copiedFile = testDir.resolve("copiedFile.txt");
-                       log.debug("Resolved " + copiedFile);
-                       Path relativeCopiedFile = testDir.relativize(copiedFile);
-                       log.debug("Relative copied file " + relativeCopiedFile);
-                       try (OutputStream out = Files.newOutputStream(copiedFile);
-                                       InputStream in = Files.newInputStream(testPath)) {
-                               IOUtils.copy(in, out);
-                       }
-                       log.debug("Copied " + testPath + " to " + copiedFile);
-                       Files.delete(testPath);
-                       log.debug("Deleted " + testPath);
-                       byte[] copiedRead = Files.readAllBytes(copiedFile);
-                       log.debug("Read " + copiedFile);
-                       // Browse directories
-                       DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
-                       int fileCount = 0;
-                       Path listedFile = null;
-                       for (Path file : files) {
-                               fileCount++;
-                               if (!Files.isDirectory(file))
-                                       listedFile = file;
-                       }
-                       log.debug("Listed " + testDir);
-                       // Generic attributes
-                       Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
-                       log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
-               } catch (IOException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               }
-
-       }
-
-}
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/cli/SshCli.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/cli/SshCli.java
deleted file mode 100644 (file)
index 12b4d5e..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.cms.ssh.cli;
-
-import org.argeo.api.cli.CommandsCli;
-
-public class SshCli extends CommandsCli {
-       public SshCli(String commandName) {
-               super(commandName);
-               addCommand("shell", new SshShell());
-       }
-
-       @Override
-       public String getDescription() {
-               return "SSH utilities.";
-       }
-
-        
-}
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/cli/SshShell.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/cli/SshShell.java
deleted file mode 100644 (file)
index 78903a7..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-package org.argeo.cms.ssh.cli;
-
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-import java.net.URI;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.util.List;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.apache.sshd.agent.SshAgent;
-import org.apache.sshd.agent.SshAgentFactory;
-import org.apache.sshd.agent.local.LocalAgentFactory;
-import org.apache.sshd.agent.unix.UnixAgentFactory;
-import org.apache.sshd.client.config.keys.ClientIdentityLoader;
-import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.config.keys.FilePasswordProvider;
-import org.argeo.api.cli.DescribedCommand;
-import org.argeo.cms.ssh.AbstractSsh;
-import org.argeo.cms.ssh.Ssh;
-
-public class SshShell implements DescribedCommand<String> {
-               private Option portOption;
-
-               @Override
-               public Options getOptions() {
-                       Options options = new Options();
-                       portOption = Option.builder().option("p").longOpt("port").hasArg().desc("port to connect to").build();
-                       options.addOption(portOption);
-                       return options;
-               }
-
-               @Override
-               public String apply(List<String> args) {
-                       CommandLine cl = toCommandLine(args);
-                       String portStr = cl.getOptionValue(portOption);
-                       if (portStr == null)
-                               portStr = "22";
-
-                       String host = cl.getArgList().get(0);
-
-                       String uriStr = "ssh://" + host + ":" + portStr + "/";
-                       // System.out.println(uriStr);
-                       URI uri = URI.create(uriStr);
-
-                       Ssh ssh = null;
-                       try {
-                               ssh = new Ssh(uri);
-                               boolean osAgent;
-                               SshAgent sshAgent;
-                               try {
-                                       String sshAuthSockentEnv = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
-                                       if (sshAuthSockentEnv != null) {
-                                               ssh.getSshClient().getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, sshAuthSockentEnv);
-                                               SshAgentFactory agentFactory = new UnixAgentFactory();
-                                               ssh.getSshClient().setAgentFactory(agentFactory);
-                                               sshAgent = agentFactory.createClient(null, ssh.getSshClient());
-                                               osAgent = true;
-                                       } else {
-                                               osAgent = false;
-                                       }
-                               } catch (Exception e) {
-                                       e.printStackTrace();
-                                       osAgent = false;
-                               }
-
-                               if (!osAgent) {
-                                       SshAgentFactory agentFactory = new LocalAgentFactory();
-                                       ssh.getSshClient().setAgentFactory(agentFactory);
-                                       sshAgent = agentFactory.createClient(null, ssh.getSshClient());
-                                       String keyPath = System.getProperty("user.home") + "/.ssh/id_rsa";
-
-                                       char[] keyPassword = AbstractSsh.readPassword();
-                                       NamedResource namedResource = new NamedResource() {
-
-                                               @Override
-                                               public String getName() {
-                                                       return keyPath;
-                                               }
-                                       };
-                                       KeyPair keyPair = ClientIdentityLoader.DEFAULT
-                                                       .loadClientIdentities(null, namedResource, FilePasswordProvider.of(new String(keyPassword)))
-                                                       .iterator().next();
-                                       sshAgent.addIdentity(keyPair, "NO COMMENT");
-                               }
-
-//                             char[] keyPassword = AbstractSsh.readPassword();
-//                             SshKeyPair keyPair = SshKeyPair.loadDefault(keyPassword);
-//                             Arrays.fill(keyPassword, '*');
-//                             ssh.setSshKeyPair(keyPair);
-//                             ssh.authenticate();
-                               ssh.verifyAuth();
-
-                               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-                               System.out.println("Ssh available in " + jvmUptime + " ms.");
-
-                               AbstractSsh.openShell(ssh);
-                       } catch (IOException | GeneralSecurityException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       } finally {
-                               if (ssh != null)
-                                       ssh.closeSession();
-                       }
-                       return null;
-               }
-
-               @Override
-               public String getDescription() {
-                       return "Launch a static CMS.";
-               }
-
-       }
\ No newline at end of file
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/package-info.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/package-info.java
deleted file mode 100644 (file)
index 9555662..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** SSH support. */
-package org.argeo.cms.ssh;
\ No newline at end of file
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/cli/FileSync.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/cli/FileSync.java
new file mode 100644 (file)
index 0000000..91279c5
--- /dev/null
@@ -0,0 +1,103 @@
+package org.argeo.cms.ux.cli;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.CommandArgsException;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.acr.fs.PathSync;
+import org.argeo.cms.acr.fs.SyncResult;
+
+public class FileSync implements DescribedCommand<SyncResult<Path>> {
+       final static Option deleteOption = Option.builder().longOpt("delete").desc("delete from target").build();
+       final static Option recursiveOption = Option.builder("r").longOpt("recursive").desc("recurse into directories")
+                       .build();
+       final static Option progressOption = Option.builder().longOpt("progress").hasArg(false).desc("show progress")
+                       .build();
+
+       @Override
+       public SyncResult<Path> apply(List<String> t) {
+               try {
+                       CommandLine line = toCommandLine(t);
+                       List<String> remaining = line.getArgList();
+                       if (remaining.size() == 0) {
+                               throw new CommandArgsException("There must be at least one argument");
+                       }
+                       URI sourceUri = new URI(remaining.get(0));
+                       URI targetUri;
+                       if (remaining.size() == 1) {
+                               targetUri = Paths.get(System.getProperty("user.dir")).toUri();
+                       } else {
+                               targetUri = new URI(remaining.get(1));
+                       }
+                       boolean delete = line.hasOption(deleteOption.getLongOpt());
+                       boolean recursive = line.hasOption(recursiveOption.getLongOpt());
+                       PathSync pathSync = new PathSync(sourceUri, targetUri, delete, recursive);
+                       return pathSync.call();
+               } catch (URISyntaxException e) {
+                       throw new CommandArgsException(e);
+               }
+       }
+
+       @Override
+       public Options getOptions() {
+               Options options = new Options();
+               options.addOption(recursiveOption);
+               options.addOption(deleteOption);
+               options.addOption(progressOption);
+               return options;
+       }
+
+       @Override
+       public String getUsage() {
+               return "[source URI] [target URI]";
+       }
+
+       public static void main(String[] args) {
+               DescribedCommand.mainImpl(new FileSync(), args);
+//             Options options = new Options();
+//             options.addOption("r", "recursive", false, "recurse into directories");
+//             options.addOption(Option.builder().longOpt("progress").hasArg(false).desc("show progress").build());
+//
+//             CommandLineParser parser = new DefaultParser();
+//             try {
+//                     CommandLine line = parser.parse(options, args);
+//                     List<String> remaining = line.getArgList();
+//                     if (remaining.size() == 0) {
+//                             System.err.println("There must be at least one argument");
+//                             printHelp(options);
+//                             System.exit(1);
+//                     }
+//                     URI sourceUri = new URI(remaining.get(0));
+//                     URI targetUri;
+//                     if (remaining.size() == 1) {
+//                             targetUri = Paths.get(System.getProperty("user.dir")).toUri();
+//                     } else {
+//                             targetUri = new URI(remaining.get(1));
+//                     }
+//                     PathSync pathSync = new PathSync(sourceUri, targetUri);
+//                     pathSync.run();
+//             } catch (Exception exp) {
+//                     exp.printStackTrace();
+//                     printHelp(options);
+//                     System.exit(1);
+//             }
+       }
+
+//     public static void printHelp(Options options) {
+//             HelpFormatter formatter = new HelpFormatter();
+//             formatter.printHelp("sync SRC [DEST]", options, true);
+//     }
+
+       @Override
+       public String getDescription() {
+               return "Synchronises files";
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/cli/FsCommands.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/cli/FsCommands.java
new file mode 100644 (file)
index 0000000..97d8c85
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.cms.ux.cli;
+
+import org.argeo.api.cli.CommandsCli;
+
+/** File utilities. */
+public class FsCommands extends CommandsCli {
+
+       public FsCommands(String commandName) {
+               super(commandName);
+               addCommand("sync", new FileSync());
+       }
+
+       @Override
+       public String getDescription() {
+               return "Utilities around files and file systems";
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FileSync.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FileSync.java
deleted file mode 100644 (file)
index 397caea..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.argeo.cms.acr.fs;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.argeo.api.cli.CommandArgsException;
-import org.argeo.api.cli.DescribedCommand;
-
-public class FileSync implements DescribedCommand<SyncResult<Path>> {
-       final static Option deleteOption = Option.builder().longOpt("delete").desc("delete from target").build();
-       final static Option recursiveOption = Option.builder("r").longOpt("recursive").desc("recurse into directories")
-                       .build();
-       final static Option progressOption = Option.builder().longOpt("progress").hasArg(false).desc("show progress")
-                       .build();
-
-       @Override
-       public SyncResult<Path> apply(List<String> t) {
-               try {
-                       CommandLine line = toCommandLine(t);
-                       List<String> remaining = line.getArgList();
-                       if (remaining.size() == 0) {
-                               throw new CommandArgsException("There must be at least one argument");
-                       }
-                       URI sourceUri = new URI(remaining.get(0));
-                       URI targetUri;
-                       if (remaining.size() == 1) {
-                               targetUri = Paths.get(System.getProperty("user.dir")).toUri();
-                       } else {
-                               targetUri = new URI(remaining.get(1));
-                       }
-                       boolean delete = line.hasOption(deleteOption.getLongOpt());
-                       boolean recursive = line.hasOption(recursiveOption.getLongOpt());
-                       PathSync pathSync = new PathSync(sourceUri, targetUri, delete, recursive);
-                       return pathSync.call();
-               } catch (URISyntaxException e) {
-                       throw new CommandArgsException(e);
-               }
-       }
-
-       @Override
-       public Options getOptions() {
-               Options options = new Options();
-               options.addOption(recursiveOption);
-               options.addOption(deleteOption);
-               options.addOption(progressOption);
-               return options;
-       }
-
-       @Override
-       public String getUsage() {
-               return "[source URI] [target URI]";
-       }
-
-       public static void main(String[] args) {
-               DescribedCommand.mainImpl(new FileSync(), args);
-//             Options options = new Options();
-//             options.addOption("r", "recursive", false, "recurse into directories");
-//             options.addOption(Option.builder().longOpt("progress").hasArg(false).desc("show progress").build());
-//
-//             CommandLineParser parser = new DefaultParser();
-//             try {
-//                     CommandLine line = parser.parse(options, args);
-//                     List<String> remaining = line.getArgList();
-//                     if (remaining.size() == 0) {
-//                             System.err.println("There must be at least one argument");
-//                             printHelp(options);
-//                             System.exit(1);
-//                     }
-//                     URI sourceUri = new URI(remaining.get(0));
-//                     URI targetUri;
-//                     if (remaining.size() == 1) {
-//                             targetUri = Paths.get(System.getProperty("user.dir")).toUri();
-//                     } else {
-//                             targetUri = new URI(remaining.get(1));
-//                     }
-//                     PathSync pathSync = new PathSync(sourceUri, targetUri);
-//                     pathSync.run();
-//             } catch (Exception exp) {
-//                     exp.printStackTrace();
-//                     printHelp(options);
-//                     System.exit(1);
-//             }
-       }
-
-//     public static void printHelp(Options options) {
-//             HelpFormatter formatter = new HelpFormatter();
-//             formatter.printHelp("sync SRC [DEST]", options, true);
-//     }
-
-       @Override
-       public String getDescription() {
-               return "Synchronises files";
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsCommands.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsCommands.java
deleted file mode 100644 (file)
index 088c1c3..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.cms.acr.fs;
-
-import org.argeo.api.cli.CommandsCli;
-
-/** File utilities. */
-public class FsCommands extends CommandsCli {
-
-       public FsCommands(String commandName) {
-               super(commandName);
-               addCommand("sync", new FileSync());
-       }
-
-       @Override
-       public String getDescription() {
-               return "Utilities around files and file systems";
-       }
-
-}
index e8a0dc2b72f82d01f3b68da95ea7d3ab44c43b28..9e00ed458ac0011480f034589e3297b8e89eb7c5 100644 (file)
@@ -27,7 +27,6 @@ import org.argeo.cms.internal.auth.ImpliedByPrincipal;
 import org.argeo.cms.internal.http.WebCmsSessionImpl;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.osgi.useradmin.AuthenticatingUser;
-import org.osgi.service.http.HttpContext;
 import org.osgi.service.useradmin.Authorization;
 
 /** Centralises security related registrations. */
@@ -140,8 +139,8 @@ class CmsAuthUtils {
                        String httpSessId = httpSession.getId();
                        boolean anonymous = authorization.getName() == null;
                        String remoteUser = !anonymous ? authorization.getName() : CmsConstants.ROLE_ANONYMOUS;
-                       request.setAttribute(HttpContext.REMOTE_USER, remoteUser);
-                       request.setAttribute(HttpContext.AUTHORIZATION, authorization);
+                       request.setAttribute(RemoteAuthRequest.REMOTE_USER, remoteUser);
+                       request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization);
 
                        CmsSessionImpl cmsSession;
                        CmsSessionImpl currentLocalSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessId);
index 2d1d14b4ee62afd9b64c037d3e2faca8a9be9ad7..be5d0e15e966355c55cb6e8ee70a4d980d729ae8 100644 (file)
@@ -4,6 +4,9 @@ import java.util.Locale;
 
 /** Transitional interface to decouple from the Servlet API. */
 public interface RemoteAuthRequest {
+       final static String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
+       final static String AUTHORIZATION = "org.osgi.service.useradmin.authorization";
+
        RemoteAuthSession getSession();
 
        RemoteAuthSession createSession();
index 3abcf8c9483d52176d9cb058723577f9c1a7963a..8f05096906e12d613cd77343c120a0235d4765b2 100644 (file)
@@ -14,13 +14,11 @@ import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
-import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.CmsDeployProperty;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.cms.internal.runtime.CmsStateImpl;
-import org.osgi.service.http.HttpContext;
 import org.osgi.service.useradmin.Authorization;
 
 /** Use the HTTP session as the basis for authentication. */
@@ -79,7 +77,7 @@ public class RemoteSessionLoginModule implements LoginModule {
                                        log.trace("Retrieved authorization from " + cmsSession);
                        }
                } else {
-                       authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
+                       authorization = (Authorization) request.getAttribute(RemoteAuthRequest.AUTHORIZATION);
                        if (authorization == null) {// search by session ID
                                RemoteAuthSession httpSession = request.getSession();
                                if (httpSession == null) {
@@ -110,7 +108,7 @@ public class RemoteSessionLoginModule implements LoginModule {
                } else {
                        if (log.isTraceEnabled())
                                log.trace("HTTP login: " + true);
-                       request.setAttribute(HttpContext.AUTHORIZATION, authorization);
+                       request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization);
                        return true;
                }
        }
index 2353250c5bf88284aab165b54109122e79c03770..1b94bdad12ca880806de1643c37d27fbc6f0cc52 100644 (file)
@@ -4,7 +4,6 @@ import org.argeo.api.cms.CmsDeployment;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
 import org.argeo.cms.CmsDeployProperty;
-import org.osgi.service.http.HttpService;
 
 /** Implementation of a CMS deployment. */
 public class CmsDeploymentImpl implements CmsDeployment {
@@ -12,7 +11,7 @@ public class CmsDeploymentImpl implements CmsDeployment {
 
        // Readiness
        private boolean httpExpected = false;
-       private HttpService httpService;
+//     private HttpService httpService;
 
        private CmsState cmsState;
 //     private DeployConfig deployConfig;
@@ -40,9 +39,9 @@ public class CmsDeploymentImpl implements CmsDeployment {
 //             return deployConfig.getProps(factoryPid, cn);
 //     }
 
-       public boolean isHttpAvailableOrNotExpected() {
-               return (httpExpected ? httpService != null : true);
-       }
+//     public boolean isHttpAvailableOrNotExpected() {
+//             return (httpExpected ? httpService != null : true);
+//     }
 
 //     private void loadIpaJaasConfiguration() {
 //             if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
@@ -70,8 +69,8 @@ public class CmsDeploymentImpl implements CmsDeployment {
                httpExpected = httpPort != null || httpsPort != null;
        }
 
-       public void setHttpService(HttpService httpService) {
-               this.httpService = httpService;
-       }
+//     public void setHttpService(HttpService httpService) {
+//             this.httpService = httpService;
+//     }
 
 }