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 \
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-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
+++ /dev/null
-output.. = bin/
-bin.includes = META-INF/,\
- .,\
- OSGI-INF/jettyServiceFactory.xml
-source.. = src/
+++ /dev/null
-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");
-// }
-
-}
+++ /dev/null
-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;
- }
-}
+++ /dev/null
-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;
- }
-}
+++ /dev/null
-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;
- }
-}
+++ /dev/null
-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();
- }
-
-}
+++ /dev/null
-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;
- }
-}
+++ /dev/null
-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;
- }
-
-}
+++ /dev/null
-/** Argeo CMS integration (JSON, web services). */
-package org.argeo.cms.integration;
\ No newline at end of file
+++ /dev/null
-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);
- }
-
-}
+++ /dev/null
-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);
- }
-}
+++ /dev/null
-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();
- }
-}
+++ /dev/null
-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);
- }
-
-}
+++ /dev/null
-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();
- }
-
-}
+++ /dev/null
-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() {
-
- }
-}
+++ /dev/null
-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());
- }
- }
-
-}
+++ /dev/null
-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();
- }
-
-}
+++ /dev/null
-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;
- }
-
-}
+++ /dev/null
-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";
-
-}
+++ /dev/null
-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);
- }
-
- }
-
-}
+++ /dev/null
-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");
- }
-}
+++ /dev/null
-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);
- }
- }
-}
+++ /dev/null
-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, "");
- }
-
-}
+++ /dev/null
-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);
- }
-
-}
+++ /dev/null
-/** Argeo CMS websocket integration. */
-package org.argeo.cms.websocket.javax.server;
\ No newline at end of file
+++ /dev/null
-<?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>
+++ /dev/null
-/bin/
-/target/
+++ /dev/null
-<?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>
+++ /dev/null
-/MANIFEST.MF
+++ /dev/null
-Fragment-Host: org.eclipse.equinox.http.jetty
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .
+++ /dev/null
-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);
- }
-
-}
+++ /dev/null
-/** Equinox Jetty extensions. */
-package org.argeo.equinox.jetty;
\ No newline at end of file
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+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
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/jettyServiceFactory.xml
+source.. = src/
--- /dev/null
+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");
+// }
+
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+/** Argeo CMS integration (JSON, web services). */
+package org.argeo.cms.integration;
\ No newline at end of file
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+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() {
+
+ }
+}
--- /dev/null
+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());
+ }
+ }
+
+}
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+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");
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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, "");
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+/** Argeo CMS websocket integration. */
+package org.argeo.cms.websocket.javax.server;
\ No newline at end of file
--- /dev/null
+<?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>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?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>
--- /dev/null
+/MANIFEST.MF
--- /dev/null
+<?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>
--- /dev/null
+Fragment-Host: org.eclipse.equinox.http.jetty
+
+Service-Component: \
+OSGI-INF/jettyServiceFactory.xml,\
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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";
+
+}
--- /dev/null
+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);
+ }
+
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+/** Equinox Jetty extensions. */
+package org.argeo.equinox.jetty;
\ No newline at end of file
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+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
--- /dev/null
+eclipse.preferences.version=1
+pluginProject.equinox=false
+pluginProject.extensions=false
+resolve.requirebundle=false
--- /dev/null
+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
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+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();
+ }
+
+ }
+}
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+Import-Package: org.postgresql;version="[42,43)"
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+<?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>
--- /dev/null
+/hostkey.ser
+/id_rsa
+/id_rsa.pub
--- /dev/null
+<?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>
--- /dev/null
+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
--- /dev/null
+<?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>
--- /dev/null
+Import-Package: \
+org.apache.sshd.server.forward,\
+org.apache.sshd.common.forward,\
+*
+
+Service-Component: \
+OSGI-INF/cmsSshServer.xml
--- /dev/null
+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
--- /dev/null
+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);
+ }
+ }
+
+}
--- /dev/null
+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]");
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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()));
+ }
+
+}
--- /dev/null
+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();
+ }
+
+ }
+
+}
--- /dev/null
+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.";
+ }
+
+
+}
--- /dev/null
+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
--- /dev/null
+/** SSH support. */
+package org.argeo.cms.ssh;
\ No newline at end of file
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-Import-Package: org.postgresql;version="[42,43)"
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .
+++ /dev/null
-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);
- }
-
-}
+++ /dev/null
-<?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>
+++ /dev/null
-/hostkey.ser
-/id_rsa
-/id_rsa.pub
+++ /dev/null
-<?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>
+++ /dev/null
-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
+++ /dev/null
-<?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>
+++ /dev/null
-Import-Package: \
-org.apache.sshd.server.forward,\
-org.apache.sshd.common.forward,\
-*
-
-Service-Component: \
-OSGI-INF/cmsSshServer.xml
+++ /dev/null
-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
+++ /dev/null
-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);
- }
- }
-
-}
+++ /dev/null
-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]");
- }
-
-}
+++ /dev/null
-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;
- }
-
-}
+++ /dev/null
-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);
- }
-
-}
+++ /dev/null
-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);
- }
-}
+++ /dev/null
-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()));
- }
-
-}
+++ /dev/null
-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();
- }
-
- }
-
-}
+++ /dev/null
-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.";
- }
-
-
-}
+++ /dev/null
-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
+++ /dev/null
-/** SSH support. */
-package org.argeo.cms.ssh;
\ No newline at end of file
--- /dev/null
+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";
+ }
+
+}
--- /dev/null
+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";
+ }
+
+}
+++ /dev/null
-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";
- }
-
-}
+++ /dev/null
-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";
- }
-
-}
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. */
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);
/** 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();
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. */
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) {
} else {
if (log.isTraceEnabled())
log.trace("HTTP login: " + true);
- request.setAttribute(HttpContext.AUTHORIZATION, authorization);
+ request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization);
return true;
}
}
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 {
// Readiness
private boolean httpExpected = false;
- private HttpService httpService;
+// private HttpService httpService;
private CmsState cmsState;
// private DeployConfig deployConfig;
// 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) {
httpExpected = httpPort != null || httpsPort != null;
}
- public void setHttpService(HttpService httpService) {
- this.httpService = httpService;
- }
+// public void setHttpService(HttpService httpService) {
+// this.httpService = httpService;
+// }
}