* COMPONENT PROPERTIES
*/
String CONTEXT_PATH = "context.path";
+ String CONTEXT_PUBLIC = "context.public";
String EVENT_TOPICS = "event.topics";
/*
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immmediate="true" name="Status Handler">
- <implementation class="org.argeo.cms.websocket.server.StatusEndpoints"/>
+ <implementation class="org.argeo.cms.websocket.server.StatusHandler"/>
<service>
<provide interface="com.sun.net.httpserver.HttpHandler"/>
</service>
<property name="context.path" type="String" value="/cms/status"/>
+ <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
</scr:component>
* 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";
+// 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";
+// final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
@Override
public boolean checkOrigin(String originHeaderValue) {
} else {
lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse);
}
- if (lc == null)
+ if (lc == null) {
rejectResponse(response, e);
+ return;
+ }
} finally {
Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.argeo.api.cms.CmsLog;
+
+@ServerEndpoint(value = "/ping", configurator = PublicWebSocketConfigurator.class)
+public class PingEndpoint {
+ private final static CmsLog log = CmsLog.getLog(PingEndpoint.class);
+
+ @OnError
+ public void onError(Throwable e) {
+ log.error("Cannot process ping", e);
+ }
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class PublicWebSocketConfigurator extends CmsWebSocketConfigurator {
+
+ @Override
+ protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+ return false;
+ }
+
+}
+++ /dev/null
-package org.argeo.cms.websocket.server;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-
-public class StatusEndpoints implements WebsocketEndpoints, HttpHandler {
-
- @Override
- public Set<Class<?>> getEndPoints() {
- Set<Class<?>> res = new HashSet<>();
- res.add(EventEndpoint.class);
- res.add(TestEndpoint.class);
- return res;
- }
-
- @Override
- public void handle(HttpExchange exchange) throws IOException {
- // web socket only
- exchange.sendResponseHeaders(200, -1);
- }
-
-}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+public class StatusHandler implements WebsocketEndpoints, HttpHandler {
+ private CmsState cmsState;
+
+ @Override
+ public Set<Class<?>> getEndPoints() {
+ Set<Class<?>> res = new HashSet<>();
+ res.add(PingEndpoint.class);
+ res.add(EventEndpoint.class);
+ res.add(TestEndpoint.class);
+ return res;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+
+ StringJoiner sb = new StringJoiner("\n");
+ CmsDeployProperty[] deployProperties = CmsDeployProperty.values();
+ Arrays.sort(deployProperties, (o1, o2) -> o1.name().compareTo(o2.name()));
+ for (CmsDeployProperty deployProperty : deployProperties) {
+ List<String> values = cmsState.getDeployProperties(deployProperty.getProperty());
+ for (int i = 0; i < values.size(); i++) {
+ String value = values.get(i);
+ if (value != null) {
+ String line = deployProperty.getProperty() + (i == 0 ? "" : "." + i) + "=" + value;
+ sb.add(line);
+ }
+ }
+ }
+
+ byte[] msg = sb.toString().getBytes(StandardCharsets.UTF_8);
+ exchange.sendResponseHeaders(200, msg.length);
+ exchange.getResponseBody().write(msg);
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+}
<implementation class="org.argeo.cms.equinox.http.jetty.EquinoxJettyServer"/>
<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"/>
- <reference bind="setDefaultAuthenticator" cardinality="1..1" interface="com.sun.net.httpserver.Authenticator" policy="static"/>
<service>
<provide interface="com.sun.net.httpserver.HttpServer"/>
</service>
private CmsState cmsState;
- private Authenticator defaultAuthenticator;
+// private Authenticator defaultAuthenticator;
protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException {
}
}
- @Override
- public synchronized HttpContext createContext(String path) {
- HttpContext httpContext = super.createContext(path);
- httpContext.setAuthenticator(defaultAuthenticator);
- return httpContext;
- }
+// @Override
+// public synchronized HttpContext createContext(String path) {
+// HttpContext httpContext = super.createContext(path);
+// httpContext.setAuthenticator(defaultAuthenticator);
+// return httpContext;
+// }
protected void enableWebSocket(ServletContextHandler servletContextHandler) {
String webSocketEnabled = getDeployProperty(CmsDeployProperty.WEBSOCKET_ENABLED);
this.cmsState = cmsState;
}
- public void setDefaultAuthenticator(Authenticator defaultAuthenticator) {
- this.defaultAuthenticator = defaultAuthenticator;
- }
+// public void setDefaultAuthenticator(Authenticator defaultAuthenticator) {
+// this.defaultAuthenticator = defaultAuthenticator;
+// }
}
<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"/>
+ <service>
+ <provide interface="org.argeo.cms.CmsSshd"/>
+ </service>
</scr:component>
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.argeo.api.cms.CmsLog;
import org.argeo.api.cms.CmsState;
import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.CmsSshd;
-public class CmsSshServer {
+public class CmsSshServer implements CmsSshd {
private final static CmsLog log = CmsLog.getLog(CmsSshServer.class);
private static final String DEFAULT_SSH_HOST_KEY_PATH = CmsConstants.NODE + '/' + CmsConstants.NODE + ".ser";
});
// Authentication
- //sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
+ // sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
sshd.setPublickeyAuthenticator(null);
// sshd.setKeyboardInteractiveAuthenticator(null);
JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator();
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="CMS Deployment">
<implementation class="org.argeo.cms.internal.runtime.CmsDeploymentImpl"/>
<reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
- <reference bind="setHttpServer" cardinality="0..1" interface="com.sun.net.httpserver.HttpServer" policy="static"/>
+ <reference bind="setCmsSshd" cardinality="0..1" interface="org.argeo.cms.CmsSshd" policy="dynamic"/>
+ <reference bind="setHttpServer" cardinality="0..1" interface="com.sun.net.httpserver.HttpServer" policy="dynamic"/>
<reference bind="addHttpHandler" unbind="removeHttpHandler" cardinality="0..1" interface="com.sun.net.httpserver.HttpHandler" policy="dynamic"/>
<service>
<provide interface="org.argeo.api.cms.CmsDeployment"/>
OSGI-INF/transactionManager.xml,\
OSGI-INF/cmsUserAdmin.xml,\
OSGI-INF/cmsUserManager.xml,\
-OSGI-INF/cmsAuthenticator.xml,\
OSGI-INF/cmsContentRepository.xml,\
OSGI-INF/cmsAcrHttpHandler.xml,\
OSGI-INF/cmsDeployment.xml,\
--- /dev/null
+package org.argeo.cms;
+
+/** Just a marker interface for the time being.*/
+public interface CmsSshd {
+
+}
// TODO move it to a service in order to avoid static synchronization
if (request != null) {
RemoteAuthSession httpSession = request.getSession();
- assert httpSession != null;
- String httpSessId = httpSession.getId();
+ String httpSessId = httpSession != null ? httpSession.getId() : null;
boolean anonymous = authorization.getName() == null;
String remoteUser = !anonymous ? authorization.getName() : CmsConstants.ROLE_ANONYMOUS;
request.setAttribute(RemoteAuthRequest.REMOTE_USER, remoteUser);
// anonymous
ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
- Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader());
+ Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
LoginContext lc = CmsAuth.ANONYMOUS
.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
lc.login();
CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
.header(HttpHeader.AUTHORIZATION.getName(), HttpHeader.NEGOTIATE + " " + token)
.buildAsync(uri, listener);
+
WebSocket webSocket = ws.get();
webSocket.request(Long.MAX_VALUE);
--- /dev/null
+package org.argeo.cms.client;
+
+import java.math.RoundingMode;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+
+/** Tests connectivity to the web socket server. */
+public class WsPing implements Runnable {
+ private final static int PING_FRAME_SIZE = 125;
+
+ private final URI uri;
+ private final UUID uuid;
+
+ private final DecimalFormat decimalFormat;
+
+ public WsPing(URI uri) {
+ this.uri = uri;
+ this.uuid = UUID.randomUUID();
+ decimalFormat = new DecimalFormat("0.0");
+ decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
+ }
+
+ @Override
+ public void run() {
+ try {
+ WebSocket.Listener listener = new WebSocket.Listener() {
+
+ @Override
+ public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+ long msb = message.getLong();
+ long lsb = message.getLong();
+ long end = System.nanoTime();
+ if (msb != uuid.getMostSignificantBits() || lsb != uuid.getLeastSignificantBits())
+ return null; // ignore
+ long begin = message.getLong();
+ double durationNs = end - begin;
+ double durationMs = durationNs / 1000000;
+ int size = message.remaining() + (3 * Long.BYTES);
+ System.out.println(
+ size + " bytes from " + uri + ": time=" + decimalFormat.format(durationMs) + " ms");
+ return null;
+ }
+
+ };
+
+ HttpClient client = HttpClient.newHttpClient();
+ CompletableFuture<WebSocket> ws = client.newWebSocketBuilder().buildAsync(uri, listener);
+ WebSocket webSocket = ws.get();
+ webSocket.request(Long.MAX_VALUE);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+
+ while (!webSocket.isInputClosed()) {
+ long begin = System.nanoTime();
+// ByteBuffer buffer = ByteBuffer.allocate(3 * Long.BYTES);
+ ByteBuffer buffer = ByteBuffer.allocate(PING_FRAME_SIZE);
+ buffer.putLong(uuid.getMostSignificantBits());
+ buffer.putLong(uuid.getLeastSignificantBits());
+ buffer.putLong(begin);
+ buffer.flip();
+ webSocket.sendPing(buffer);
+ Thread.sleep(1000);
+ }
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length == 0) {
+ System.err.println("usage: java " + WsPing.class.getName() + " <url>");
+ System.exit(1);
+ return;
+ }
+ URI uri = URI.create(args[0]);
+ new WsPing(uri).run();
+ }
+
+}
public RemoteCmsSessionImpl(UUID uuid, Subject initialSubject, Authorization authorization, Locale locale,
RemoteAuthRequest request) {
- super(uuid, initialSubject, authorization, locale, request.getSession().getId());
+ super(uuid, initialSubject, authorization, locale,
+ request.getSession() != null ? request.getSession().getId() : null);
httpSession = request.getSession();
}
public boolean isValid() {
if (isClosed())
return false;
+ if (httpSession == null)
+ return true;
return httpSession.isValid();
}
}
+++ /dev/null
-package org.argeo.cms.internal.http;
-
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.auth.RemoteAuthRequest;
-import org.argeo.cms.auth.RemoteAuthResponse;
-import org.argeo.cms.auth.SpnegoLoginModule;
-import org.argeo.util.http.HttpHeader;
-
-public class AbstractHttpAuthenticator {
- private final static CmsLog log = CmsLog.getLog(AbstractHttpAuthenticator.class);
-
-
-}
import javax.security.auth.login.LoginException;
import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsLog;
import org.argeo.cms.auth.CurrentUser;
import org.argeo.cms.auth.RemoteAuthCallbackHandler;
import org.argeo.cms.auth.RemoteAuthRequest;
// final static String HEADER_AUTHORIZATION = "Authorization";
// final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
- private final static CmsLog log = CmsLog.getLog(CmsAuthenticator.class);
+// private final static CmsLog log = CmsLog.getLog(CmsAuthenticator.class);
// TODO make it configurable
private final String httpAuthRealm = "Argeo";
--- /dev/null
+package org.argeo.cms.internal.http;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class PublicCmsAuthenticator extends CmsAuthenticator {
+
+ @Override
+ protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+ return false;
+ }
+
+}
//
// }.open();
- checkReadiness();
+ new Thread(() -> {
+ while (!checkReadiness()) {
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ }
+ }
+ }, "Check readiness").start();
+
+ // checkReadiness();
setInstance(this);
}
* Checks whether the deployment is available according to expectations, and
* mark it as available.
*/
- private void checkReadiness() {
+ private boolean checkReadiness() {
if (isAvailable())
- return;
- if (cmsDeployment != null && userAdmin != null) {
+ return true;
+ if (cmsDeployment == null)
+ return false;
+
+ if (((CmsDeploymentImpl) cmsDeployment).allExpectedServicesAvailable() && userAdmin != null) {
String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
availableSince = System.currentTimeMillis();
if (log.isTraceEnabled())
log.trace("Kernel initialization took " + initDuration + "ms");
tributeToFreeSoftware(initDuration);
+
+ return true;
} else {
- throw new IllegalStateException("Deployment is not available");
+ return false;
+ // throw new IllegalStateException("Deployment is not available");
}
}
import org.argeo.api.cms.CmsLog;
import org.argeo.api.cms.CmsState;
import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.CmsSshd;
+import org.argeo.cms.internal.http.CmsAuthenticator;
+import org.argeo.cms.internal.http.PublicCmsAuthenticator;
+import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
public class CmsDeploymentImpl implements CmsDeployment {
private final CmsLog log = CmsLog.getLog(getClass());
- // Readiness
-// private HttpService httpService;
-
private CmsState cmsState;
-// private DeployConfig deployConfig;
+ // Expectations
private boolean httpExpected = false;
+ private boolean sshdExpected = false;
+
+ // HTTP
private HttpServer httpServer;
private Map<String, HttpHandler> httpHandlers = new TreeMap<>();
+ private Map<String, CmsAuthenticator> httpAuthenticators = new TreeMap<>();
- public void start() {
-// httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
-// if (deployConfig.hasDomain()) {
-// loadIpaJaasConfiguration();
-// }
+ // SSHD
+ private CmsSshd cmsSshd;
+ public void start() {
log.debug(() -> "CMS deployment available");
}
-// public void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
-// deployConfig.putFactoryDeployConfig(factoryPid, props);
-// deployConfig.save();
-// try {
-// deployConfig.loadConfigs();
-// } catch (IOException e) {
-// throw new IllegalStateException(e);
-// }
-// }
-//
-// public Dictionary<String, Object> getProps(String factoryPid, String cn) {
-// return deployConfig.getProps(factoryPid, cn);
-// }
-
-// public boolean isHttpAvailableOrNotExpected() {
-// return (httpExpected ? httpService != null : true);
-// }
-
-// private void loadIpaJaasConfiguration() {
-// if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
-// String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
-// URL url = getClass().getClassLoader().getResource(jaasConfig);
-// KernelUtils.setJaasConfiguration(url);
-// log.debug("Set IPA JAAS configuration.");
-// }
-// }
-
public void stop() {
-// if (deployConfig != null) {
-// deployConfig.save();
-// }
}
-// public void setDeployConfig(DeployConfig deployConfig) {
-// this.deployConfig = deployConfig;
-// }
-
public void setCmsState(CmsState cmsState) {
this.cmsState = cmsState;
+
String httpPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTP_PORT.getProperty());
String httpsPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTPS_PORT.getProperty());
httpExpected = httpPort != null || httpsPort != null;
+
+ String sshdPort = this.cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
+ sshdExpected = sshdPort != null;
}
public void setHttpServer(HttpServer httpServer) {
// create contexts whose handles had already been published
for (String contextPath : httpHandlers.keySet()) {
HttpHandler httpHandler = httpHandlers.get(contextPath);
- httpServer.createContext(contextPath, httpHandler);
- log.debug(() -> "Added handler " + contextPath + " : " + httpHandler.getClass().getName());
+ CmsAuthenticator authenticator = httpAuthenticators.get(contextPath);
+ createHttpContext(contextPath, httpHandler, authenticator);
}
}
log.warn("Property " + CONTEXT_PATH + " not set on HTTP handler " + properties + ". Ignoring it.");
return;
}
+ boolean isPublic = Boolean.parseBoolean(properties.get(CmsConstants.CONTEXT_PUBLIC));
+ CmsAuthenticator authenticator = isPublic ? new PublicCmsAuthenticator() : new CmsAuthenticator();
httpHandlers.put(contextPath, httpHandler);
+ httpAuthenticators.put(contextPath, authenticator);
if (httpServer == null)
return;
- httpServer.createContext(contextPath, httpHandler);
- log.debug(() -> "Added handler " + contextPath + " : " + httpHandler.getClass().getName());
+ else
+ createHttpContext(contextPath, httpHandler, authenticator);
+ }
+ public void createHttpContext(String contextPath, HttpHandler httpHandler, CmsAuthenticator authenticator) {
+ HttpContext httpContext = httpServer.createContext(contextPath);
+ // we want to set the authenticator BEFORE the handler actually becomes active
+ httpContext.setAuthenticator(authenticator);
+ httpContext.setHandler(httpHandler);
+ log.debug(() -> "Added handler " + contextPath + " : " + httpHandler.getClass().getName());
}
public void removeHttpHandler(HttpHandler httpHandler, Map<String, String> properties) {
httpServer.removeContext(contextPath);
log.debug(() -> "Removed handler " + contextPath + " : " + httpHandler.getClass().getName());
}
-// public void setHttpService(HttpService httpService) {
-// this.httpService = httpService;
-// }
+
+ public boolean allExpectedServicesAvailable() {
+ if (httpExpected && httpServer == null)
+ return false;
+ if (sshdExpected && cmsSshd == null)
+ return false;
+ return true;
+ }
+
+ public void setCmsSshd(CmsSshd cmsSshd) {
+ this.cmsSshd = cmsSshd;
+ }
}