From a940a66aca249a1ce7dea66d43b0e2816845d7d1 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Mon, 17 Feb 2020 11:47:04 +0100 Subject: [PATCH] Improve integration. --- .../cms/integration/CmsExceptionsChain.java | 1 + .../cms/integration/CmsLoginServlet.java | 22 ------ .../integration/CmsPrivateServletContext.java | 73 +++++++++++++++++++ .../argeo/cms/integration/JcrReadServlet.java | 24 +++++- .../cms/internal/auth/ImpliedByPrincipal.java | 26 ++++++- ...stants.java => InternalHttpConstants.java} | 4 +- .../internal/kernel/CmsWorkspaceIndexer.java | 8 +- .../cms/internal/kernel/DeployConfig.java | 10 +++ .../argeo/cms/internal/kernel/InitUtils.java | 38 +++++----- .../websocket/CmsWebSocketConfigurator.java | 16 +++- .../equinox/jetty/CmsJettyCustomizer.java | 7 +- 11 files changed, 174 insertions(+), 55 deletions(-) create mode 100644 org.argeo.cms/src/org/argeo/cms/integration/CmsPrivateServletContext.java rename org.argeo.cms/src/org/argeo/cms/internal/http/{HttpConstants.java => InternalHttpConstants.java} (89%) diff --git a/org.argeo.cms/src/org/argeo/cms/integration/CmsExceptionsChain.java b/org.argeo.cms/src/org/argeo/cms/integration/CmsExceptionsChain.java index 2ee1c30dd..12ac96da2 100644 --- a/org.argeo.cms/src/org/argeo/cms/integration/CmsExceptionsChain.java +++ b/org.argeo.cms/src/org/argeo/cms/integration/CmsExceptionsChain.java @@ -69,6 +69,7 @@ public class CmsExceptionsChain extends ExceptionsChain { // 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(); // } // } diff --git a/org.argeo.cms/src/org/argeo/cms/integration/CmsLoginServlet.java b/org.argeo.cms/src/org/argeo/cms/integration/CmsLoginServlet.java index 2adc5371d..cb3a3484a 100644 --- a/org.argeo.cms/src/org/argeo/cms/integration/CmsLoginServlet.java +++ b/org.argeo.cms/src/org/argeo/cms/integration/CmsLoginServlet.java @@ -31,7 +31,6 @@ public class CmsLoginServlet extends HttpServlet { public final static String PARAM_PASSWORD = "password"; private static final long serialVersionUID = 2478080654328751539L; -// private Gson gson = new GsonBuilder().setPrettyPrinting().create(); private ObjectMapper objectMapper = new ObjectMapper(); @Override @@ -80,27 +79,6 @@ public class CmsLoginServlet extends HttpServlet { JsonGenerator jg = objectMapper.getFactory().createGenerator(response.getWriter()); jg.writeObject(cmsSessionDescriptor); -// JsonWriter jsonWriter = gson.newJsonWriter(response.getWriter()); -// jsonWriter.beginObject(); -// // Authorization -// jsonWriter.name("username").value(authorization.getName()); -// jsonWriter.name("displayName").value(authorization.toString()); -// // Roles -// jsonWriter.name("roles").beginArray(); -// for (String role : authorization.getRoles()) -// if (!role.equals(authorization.getName())) -// jsonWriter.value(role); -// jsonWriter.endArray(); -// // CMS session -// jsonWriter.name("cmsSession").beginObject(); -// jsonWriter.name("uuid").value(cmsSessionId.getUuid().toString()); -// jsonWriter.endObject(); -// -// // extensions -// enrichJson(jsonWriter); -// -// jsonWriter.endObject(); - String redirectTo = redirectTo(request); if (redirectTo != null) response.sendRedirect(redirectTo); diff --git a/org.argeo.cms/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/org.argeo.cms/src/org/argeo/cms/integration/CmsPrivateServletContext.java new file mode 100644 index 000000000..c968d779d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/integration/CmsPrivateServletContext.java @@ -0,0 +1,73 @@ +package org.argeo.cms.integration; + +import static org.argeo.node.NodeConstants.LOGIN_CONTEXT_USER; + +import java.io.IOException; +import java.security.AccessControlContext; +import java.security.AccessController; +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.cms.auth.HttpRequestCallbackHandler; +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 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 request, HttpServletResponse response) throws IOException { + LoginContext lc = null; + + String pathInfo = request.getPathInfo(); + String servletPath = request.getServletPath(); + if ((pathInfo != null && (servletPath + pathInfo).equals(loginPage)) || servletPath.contentEquals(loginServlet)) + return true; + try { + lc = new LoginContext(LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request, response)); + lc.login(); + } catch (LoginException e) { + lc = processUnauthorized(request, response); + if (lc == null) + return false; + } + Subject.doAs(lc.getSubject(), new PrivilegedAction() { + + @Override + public Void run() { + request.setAttribute(REMOTE_USER, AccessController.getContext()); + return null; + } + + }); + + return true; + } + + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + try { + response.sendRedirect(loginPage); + } catch (IOException e) { + throw new RuntimeException("Cannot redirect to login page", e); + } + return null; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/integration/JcrReadServlet.java b/org.argeo.cms/src/org/argeo/cms/integration/JcrReadServlet.java index 026da108a..83393ec8e 100644 --- a/org.argeo.cms/src/org/argeo/cms/integration/JcrReadServlet.java +++ b/org.argeo.cms/src/org/argeo/cms/integration/JcrReadServlet.java @@ -4,6 +4,9 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.security.AccessControlContext; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -20,6 +23,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.nodetype.NodeType; +import javax.security.auth.Subject; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -31,6 +35,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.api.JackrabbitValue; import org.argeo.jcr.JcrUtils; +import org.osgi.service.http.context.ServletContextHelper; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; @@ -123,7 +128,24 @@ public class JcrReadServlet extends HttpServlet { protected Session openJcrSession(HttpServletRequest req, HttpServletResponse resp, Repository repository, String workspace) throws RepositoryException { - return workspace != null ? repository.login(workspace) : repository.login(); + AccessControlContext acc = (AccessControlContext) req.getAttribute(ServletContextHelper.REMOTE_USER); + Subject subject = Subject.getSubject(acc); + try { + return Subject.doAs(subject, new PrivilegedExceptionAction() { + + @Override + public Session run() throws RepositoryException { + return repository.login(workspace); + } + + }); + } catch (PrivilegedActionException e) { + if (e.getException() instanceof RepositoryException) + throw (RepositoryException) e.getException(); + else + throw new RuntimeException(e.getException()); + } +// return workspace != null ? repository.login(workspace) : repository.login(); } protected String getWorkspace(HttpServletRequest req) { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java index 7386d5fe5..5afacf69d 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java @@ -2,6 +2,7 @@ package org.argeo.cms.internal.auth; import java.security.Principal; import java.util.Collections; +import java.util.Dictionary; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; @@ -11,6 +12,7 @@ import javax.naming.ldap.LdapName; import org.argeo.cms.CmsException; import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Role; /** * A {@link Principal} which has been implied by an {@link Authorization}. If it @@ -20,10 +22,12 @@ import org.osgi.service.useradmin.Authorization; * identity is removed, the related {@link ImpliedByPrincipal}s can thus be * removed. */ -public final class ImpliedByPrincipal implements Principal { +public final class ImpliedByPrincipal implements Principal, Role { private final LdapName name; private Set causes = new HashSet(); + private int type = Role.ROLE; + public ImpliedByPrincipal(String name, Principal userPrincipal) { try { this.name = new LdapName(name); @@ -60,6 +64,26 @@ public final class ImpliedByPrincipal implements Principal { return Collections.enumeration(causes); } + /* + * USER ADMIN + */ + + @Override + /** Type of {@link Role}, if known. */ + public int getType() { + return type; + } + + @Override + /** Not supported for the time being. */ + public Dictionary getProperties() { + throw new UnsupportedOperationException(); + } + + /* + * OBJECT + */ + @Override public int hashCode() { return name.hashCode(); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/HttpConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java similarity index 89% rename from org.argeo.cms/src/org/argeo/cms/internal/http/HttpConstants.java rename to org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java index 51760fcff..c888c291b 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/HttpConstants.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java @@ -1,7 +1,7 @@ package org.argeo.cms.internal.http; /** Compatible with Jetty. */ -public interface HttpConstants { +public interface InternalHttpConstants { static final String HTTP_ENABLED = "http.enabled"; static final String HTTP_PORT = "http.port"; static final String HTTP_HOST = "http.host"; @@ -18,6 +18,6 @@ public interface HttpConstants { static final String SSL_KEYSTORETYPE = "ssl.keystoretype"; static final String JETTY_PROPERTY_PREFIX = "org.eclipse.equinox.http.jetty."; // Argeo specific - static final String WEB_SOCKET_ENABLED = "websocket.enabled"; + static final String WEBSOCKET_ENABLED = "websocket.enabled"; } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java index 5bfa4e42a..7782b4291 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java @@ -30,9 +30,9 @@ class CmsWorkspaceIndexer implements EventListener { // private final static String MIX_ETAG = "mix:etag"; private final static String JCR_ETAG = "jcr:etag"; - private final static String JCR_LAST_MODIFIED = "jcr:lastModified"; - private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy"; - private final static String JCR_MIXIN_TYPES = "jcr:mixinTypes"; +// private final static String JCR_LAST_MODIFIED = "jcr:lastModified"; +// private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy"; +// private final static String JCR_MIXIN_TYPES = "jcr:mixinTypes"; private final static String JCR_DATA = "jcr:data"; private final static String JCR_CONTENT = "jcr:data"; @@ -173,7 +173,7 @@ class CmsWorkspaceIndexer implements EventListener { return; if (log.isTraceEnabled()) log.trace("NODE_REMOVED " + eventPath); - String parentPath = JcrUtils.parentPath(removeNodePath); +// String parentPath = JcrUtils.parentPath(removeNodePath); // session.refresh(true); // setLastModified(parentPath, event); // session.save(); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java index 8bdf00ceb..d52fe663b 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java @@ -16,10 +16,13 @@ import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; +import javax.websocket.server.ServerEndpointConfig; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsException; +import org.argeo.cms.internal.http.InternalHttpConstants; +import org.argeo.cms.websocket.CmsWebSocketConfigurator; import org.argeo.naming.AttributesDictionary; import org.argeo.naming.LdifParser; import org.argeo.naming.LdifWriter; @@ -146,6 +149,13 @@ class DeployConfig implements ConfigurationListener { .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT)); if (!webServerConfig.isEmpty()) { webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS); + + // TODO centralise with Jetty extender + Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED); + if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) { + bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null); + webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true"); + } } int tryCount = 60; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java index 2a58d94c6..3dd1ed989 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java @@ -22,7 +22,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsException; -import org.argeo.cms.internal.http.HttpConstants; +import org.argeo.cms.internal.http.InternalHttpConstants; import org.argeo.cms.internal.jcr.RepoConf; import org.argeo.node.NodeConstants; import org.argeo.osgi.useradmin.UserAdminConf; @@ -66,56 +66,56 @@ class InitUtils { String httpPort = getFrameworkProp("org.osgi.service.http.port"); String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure"); /// TODO make it more generic - String httpHost = getFrameworkProp(HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.HTTP_HOST); - String httpsHost = getFrameworkProp(HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.HTTPS_HOST); + String httpHost = getFrameworkProp(InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST); + String httpsHost = getFrameworkProp(InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST); String webSocketEnabled = getFrameworkProp( - HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.WEB_SOCKET_ENABLED); + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED); final Hashtable props = new Hashtable(); // try { if (httpPort != null || httpsPort != null) { boolean httpEnabled = httpPort != null; - props.put(HttpConstants.HTTP_ENABLED, httpEnabled); + props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled); boolean httpsEnabled = httpsPort != null; - props.put(HttpConstants.HTTPS_ENABLED, httpsEnabled); + props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled); if (httpEnabled) { - props.put(HttpConstants.HTTP_PORT, httpPort); + props.put(InternalHttpConstants.HTTP_PORT, httpPort); if (httpHost != null) - props.put(HttpConstants.HTTP_HOST, httpHost); + props.put(InternalHttpConstants.HTTP_HOST, httpHost); } if (httpsEnabled) { - props.put(HttpConstants.HTTPS_PORT, httpsPort); + props.put(InternalHttpConstants.HTTPS_PORT, httpsPort); if (httpsHost != null) - props.put(HttpConstants.HTTPS_HOST, httpsHost); + props.put(InternalHttpConstants.HTTPS_HOST, httpsHost); // server certificate Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH); String keyStorePassword = getFrameworkProp( - HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.SSL_PASSWORD); + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD); if (keyStorePassword == null) keyStorePassword = "changeit"; if (!Files.exists(keyStorePath)) createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12); - props.put(HttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12); - props.put(HttpConstants.SSL_KEYSTORE, keyStorePath.toString()); - props.put(HttpConstants.SSL_PASSWORD, keyStorePassword); + props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12); + props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString()); + props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword); // client certificate authentication String wantClientAuth = getFrameworkProp( - HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.SSL_WANTCLIENTAUTH); + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH); if (wantClientAuth != null) - props.put(HttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(wantClientAuth)); + props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(wantClientAuth)); String needClientAuth = getFrameworkProp( - HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.SSL_NEEDCLIENTAUTH); + InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH); if (needClientAuth != null) - props.put(HttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth)); + props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth)); } // web socket if (webSocketEnabled != null && webSocketEnabled.equals("true")) - props.put(HttpConstants.WEB_SOCKET_ENABLED, true); + props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true); props.put(NodeConstants.CN, NodeConstants.DEFAULT); } diff --git a/org.argeo.cms/src/org/argeo/cms/websocket/CmsWebSocketConfigurator.java b/org.argeo.cms/src/org/argeo/cms/websocket/CmsWebSocketConfigurator.java index fc6513df5..f72527af1 100644 --- a/org.argeo.cms/src/org/argeo/cms/websocket/CmsWebSocketConfigurator.java +++ b/org.argeo.cms/src/org/argeo/cms/websocket/CmsWebSocketConfigurator.java @@ -1,7 +1,10 @@ package org.argeo.cms.websocket; +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.servlet.http.HttpSession; import javax.websocket.Extension; @@ -14,6 +17,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.auth.HttpRequestCallbackHandler; import org.argeo.node.NodeConstants; +import org.osgi.service.http.context.ServletContextHelper; /** Customises the initialisation of a new web socket. */ public class CmsWebSocketConfigurator extends Configurator { @@ -71,7 +75,15 @@ public class CmsWebSocketConfigurator extends Configurator { lc.login(); if (log.isDebugEnabled()) log.debug("Web socket logged-in as " + lc.getSubject()); - sec.getUserProperties().put(WEBSOCKET_SUBJECT, lc.getSubject()); + Subject.doAs(lc.getSubject(), new PrivilegedAction() { + + @Override + public Void run() { + sec.getUserProperties().put(ServletContextHelper.REMOTE_USER, AccessController.getContext()); + return null; + } + + }); } catch (Exception e) { rejectResponse(response, e); } @@ -86,6 +98,6 @@ public class CmsWebSocketConfigurator extends Configurator { 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"); +// throw new IllegalStateException("Web socket cannot be authenticated"); } } diff --git a/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java b/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java index e6d6c6970..46f0280bd 100644 --- a/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java +++ b/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java @@ -17,13 +17,12 @@ import org.osgi.framework.FrameworkUtil; public class CmsJettyCustomizer extends JettyCustomizer { private BundleContext bc = FrameworkUtil.getBundle(CmsJettyCustomizer.class).getBundleContext(); + public final static String WEBSOCKET_ENABLED = "websocket.enabled"; + @Override public Object customizeContext(Object context, Dictionary settings) { // WebSocket - Object webSocketEnabled = settings.get("websocket.enabled"); - if (webSocketEnabled == null) { - webSocketEnabled = bc.getProperty("org.eclipse.equinox.http.jetty.websocket.enabled"); - } + Object webSocketEnabled = settings.get(WEBSOCKET_ENABLED); if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) { ServletContextHandler servletContextHandler = (ServletContextHandler) context; WebSocketServerContainerInitializer.configure(servletContextHandler, new Configurator() { -- 2.30.2