Skip to content
Snippets Groups Projects
Commit 9220f4d8 authored by Moxie Marlinspike's avatar Moxie Marlinspike
Browse files

Add websocket-resources as a module

parent 66917cd2
Branches
Tags
No related merge requests found
Showing
with 1529 additions and 26 deletions
...@@ -10,19 +10,21 @@ ...@@ -10,19 +10,21 @@
<modules> <modules>
<module>redis-dispatch</module> <module>redis-dispatch</module>
<module>websocket-resources</module>
<module>service</module> <module>service</module>
</modules> </modules>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>TextSecureServer</artifactId>
<version>2.52</version>
<properties> <properties>
<dropwizard.version>1.3.9</dropwizard.version> <dropwizard.version>1.3.9</dropwizard.version>
<jackson.api.version>2.9.8</jackson.api.version> <jackson.api.version>2.9.8</jackson.api.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<TextSecureServer.version>2.52</TextSecureServer.version>
</properties> </properties>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>TextSecureServer</artifactId>
<version>1.0</version>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>io.dropwizard</groupId> <groupId>io.dropwizard</groupId>
...@@ -62,6 +64,11 @@ ...@@ -62,6 +64,11 @@
</dependency> </dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-servlets</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency> <dependency>
<groupId>io.dropwizard</groupId> <groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId> <artifactId>dropwizard-testing</artifactId>
......
...@@ -5,13 +5,12 @@ ...@@ -5,13 +5,12 @@
<parent> <parent>
<artifactId>TextSecureServer</artifactId> <artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId> <groupId>org.whispersystems.textsecure</groupId>
<version>2.49</version> <version>1.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>redis-dispatch</artifactId> <artifactId>redis-dispatch</artifactId>
<version>2.49</version> <version>${TextSecureServer.version}</version>
</project> </project>
\ No newline at end of file
...@@ -5,13 +5,12 @@ ...@@ -5,13 +5,12 @@
<parent> <parent>
<artifactId>TextSecureServer</artifactId> <artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId> <groupId>org.whispersystems.textsecure</groupId>
<version>2.49</version> <version>1.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>service</artifactId> <artifactId>service</artifactId>
<version>2.49</version> <version>${TextSecureServer.version}</version>
<properties> <properties>
<resilience4j.version>0.14.1</resilience4j.version> <resilience4j.version>0.14.1</resilience4j.version>
...@@ -22,8 +21,14 @@ ...@@ -22,8 +21,14 @@
<dependency> <dependency>
<groupId>org.whispersystems.textsecure</groupId> <groupId>org.whispersystems.textsecure</groupId>
<artifactId>redis-dispatch</artifactId> <artifactId>redis-dispatch</artifactId>
<version>2.49</version> <version>${TextSecureServer.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>websocket-resources</artifactId>
<version>${TextSecureServer.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
...@@ -65,11 +70,6 @@ ...@@ -65,11 +70,6 @@
<artifactId>aws-java-sdk-sqs</artifactId> <artifactId>aws-java-sdk-sqs</artifactId>
<version>1.11.362</version> <version>1.11.362</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.6.1</version>
</dependency>
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
...@@ -78,22 +78,12 @@ ...@@ -78,22 +78,12 @@
<type>jar</type> <type>jar</type>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.twilio.sdk</groupId>
<artifactId>twilio-java-sdk</artifactId>
<version>4.4.4</version>
</dependency>
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<version>9.4-1201-jdbc41</version> <version>9.4-1201-jdbc41</version>
</dependency> </dependency>
<dependency>
<groupId>org.whispersystems</groupId>
<artifactId>websocket-resources</artifactId>
<version>0.5.10</version>
</dependency>
<dependency> <dependency>
<groupId>org.whispersystems</groupId> <groupId>org.whispersystems</groupId>
<artifactId>curve25519-java</artifactId> <artifactId>curve25519-java</artifactId>
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>websocket-resources</artifactId>
<version>${TextSecureServer.version}</version>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
<version>9.4.14.v20181114</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.6.1</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.websocket.messages.WebSocketMessage;
import org.whispersystems.websocket.messages.WebSocketMessageFactory;
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class WebSocketClient {
private static final Logger logger = LoggerFactory.getLogger(WebSocketClient.class);
private final Session session;
private final RemoteEndpoint remoteEndpoint;
private final WebSocketMessageFactory messageFactory;
private final Map<Long, SettableFuture<WebSocketResponseMessage>> pendingRequestMapper;
public WebSocketClient(Session session, RemoteEndpoint remoteEndpoint,
WebSocketMessageFactory messageFactory,
Map<Long, SettableFuture<WebSocketResponseMessage>> pendingRequestMapper)
{
this.session = session;
this.remoteEndpoint = remoteEndpoint;
this.messageFactory = messageFactory;
this.pendingRequestMapper = pendingRequestMapper;
}
public ListenableFuture<WebSocketResponseMessage> sendRequest(String verb, String path,
List<String> headers,
Optional<byte[]> body)
{
final long requestId = generateRequestId();
final SettableFuture<WebSocketResponseMessage> future = SettableFuture.create();
pendingRequestMapper.put(requestId, future);
WebSocketMessage requestMessage = messageFactory.createRequest(Optional.of(requestId), verb, path, headers, body);
try {
remoteEndpoint.sendBytes(ByteBuffer.wrap(requestMessage.toByteArray()), new WriteCallback() {
@Override
public void writeFailed(Throwable x) {
logger.debug("Write failed", x);
pendingRequestMapper.remove(requestId);
future.setException(x);
}
@Override
public void writeSuccess() {}
});
} catch (WebSocketException e) {
logger.debug("Write", e);
pendingRequestMapper.remove(requestId);
future.setException(e);
}
return future;
}
public void close(int code, String message) {
session.close(code, message);
}
public void hardDisconnectQuietly() {
try {
session.disconnect();
} catch (IOException e) {
// quietly we said
}
}
private long generateRequestId() {
return Math.abs(new SecureRandom().nextLong());
}
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.SettableFuture;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.websocket.messages.InvalidMessageException;
import org.whispersystems.websocket.messages.WebSocketMessage;
import org.whispersystems.websocket.messages.WebSocketMessageFactory;
import org.whispersystems.websocket.messages.WebSocketRequestMessage;
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
import org.whispersystems.websocket.servlet.LoggableRequest;
import org.whispersystems.websocket.servlet.LoggableResponse;
import org.whispersystems.websocket.servlet.NullServletResponse;
import org.whispersystems.websocket.servlet.WebSocketServletRequest;
import org.whispersystems.websocket.servlet.WebSocketServletResponse;
import org.whispersystems.websocket.session.WebSocketSessionContext;
import org.whispersystems.websocket.setup.WebSocketConnectListener;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class WebSocketResourceProvider implements WebSocketListener {
private static final Logger logger = LoggerFactory.getLogger(WebSocketResourceProvider.class);
private final Map<Long, SettableFuture<WebSocketResponseMessage>> requestMap = new ConcurrentHashMap<>();
private final Object authenticated;
private final WebSocketMessageFactory messageFactory;
private final Optional<WebSocketConnectListener> connectListener;
private final HttpServlet servlet;
private final RequestLog requestLog;
private final long idleTimeoutMillis;
private Session session;
private RemoteEndpoint remoteEndpoint;
private WebSocketSessionContext context;
public WebSocketResourceProvider(HttpServlet servlet,
RequestLog requestLog,
Object authenticated,
WebSocketMessageFactory messageFactory,
Optional<WebSocketConnectListener> connectListener,
long idleTimeoutMillis)
{
this.servlet = servlet;
this.requestLog = requestLog;
this.authenticated = authenticated;
this.messageFactory = messageFactory;
this.connectListener = connectListener;
this.idleTimeoutMillis = idleTimeoutMillis;
}
@Override
public void onWebSocketConnect(Session session) {
this.session = session;
this.remoteEndpoint = session.getRemote();
this.context = new WebSocketSessionContext(new WebSocketClient(session, remoteEndpoint, messageFactory, requestMap));
this.context.setAuthenticated(authenticated);
this.session.setIdleTimeout(idleTimeoutMillis);
if (connectListener.isPresent()) {
connectListener.get().onWebSocketConnect(this.context);
}
}
@Override
public void onWebSocketError(Throwable cause) {
logger.debug("onWebSocketError", cause);
close(session, 1011, "Server error");
}
@Override
public void onWebSocketBinary(byte[] payload, int offset, int length) {
try {
WebSocketMessage webSocketMessage = messageFactory.parseMessage(payload, offset, length);
switch (webSocketMessage.getType()) {
case REQUEST_MESSAGE:
handleRequest(webSocketMessage.getRequestMessage());
break;
case RESPONSE_MESSAGE:
handleResponse(webSocketMessage.getResponseMessage());
break;
default:
close(session, 1018, "Badly formatted");
break;
}
} catch (InvalidMessageException e) {
logger.debug("Parsing", e);
close(session, 1018, "Badly formatted");
}
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
if (context != null) {
context.notifyClosed(statusCode, reason);
for (long requestId : requestMap.keySet()) {
SettableFuture outstandingRequest = requestMap.remove(requestId);
if (outstandingRequest != null) {
outstandingRequest.setException(new IOException("Connection closed!"));
}
}
}
}
@Override
public void onWebSocketText(String message) {
logger.debug("onWebSocketText!");
}
private void handleRequest(WebSocketRequestMessage requestMessage) {
try {
HttpServletRequest servletRequest = createRequest(requestMessage, context);
HttpServletResponse servletResponse = createResponse(requestMessage);
servlet.service(servletRequest, servletResponse);
servletResponse.flushBuffer();
requestLog.log(new LoggableRequest(servletRequest), new LoggableResponse(servletResponse));
} catch (IOException | ServletException e) {
logger.warn("Servlet Error: " + requestMessage.getVerb() + " " + requestMessage.getPath() + "\n" + requestMessage.getBody(), e);
sendErrorResponse(requestMessage, Response.status(500).build());
}
}
private void handleResponse(WebSocketResponseMessage responseMessage) {
SettableFuture<WebSocketResponseMessage> future = requestMap.remove(responseMessage.getRequestId());
if (future != null) {
future.set(responseMessage);
}
}
private void close(Session session, int status, String message) {
session.close(status, message);
}
private HttpServletRequest createRequest(WebSocketRequestMessage message,
WebSocketSessionContext context)
{
return new WebSocketServletRequest(context, message, servlet.getServletContext());
}
private HttpServletResponse createResponse(WebSocketRequestMessage message) {
if (message.hasRequestId()) {
return new WebSocketServletResponse(remoteEndpoint, message.getRequestId(), messageFactory);
} else {
return new NullServletResponse();
}
}
private void sendErrorResponse(WebSocketRequestMessage requestMessage, Response error) {
if (requestMessage.hasRequestId()) {
List<String> headers = new LinkedList<>();
for (String key : error.getStringHeaders().keySet()) {
headers.add(key + ":" + error.getStringHeaders().getFirst(key));
}
WebSocketMessage response = messageFactory.createResponse(requestMessage.getRequestId(),
error.getStatus(),
"Error response",
headers,
Optional.<byte[]>empty());
remoteEndpoint.sendBytesByFuture(ByteBuffer.wrap(response.toByteArray()));
}
}
@VisibleForTesting
WebSocketSessionContext getContext() {
return context;
}
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.websocket.auth.AuthenticationException;
import org.whispersystems.websocket.auth.WebSocketAuthenticator;
import org.whispersystems.websocket.auth.WebSocketAuthenticator.AuthenticationResult;
import org.whispersystems.websocket.auth.internal.WebSocketAuthValueFactoryProvider;
import org.whispersystems.websocket.session.WebSocketSessionContextValueFactoryProvider;
import org.whispersystems.websocket.setup.WebSocketEnvironment;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.descriptor.JspConfigDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider;
public class WebSocketResourceProviderFactory extends WebSocketServlet implements WebSocketCreator {
private static final Logger logger = LoggerFactory.getLogger(WebSocketResourceProviderFactory.class);
private final WebSocketEnvironment environment;
public WebSocketResourceProviderFactory(WebSocketEnvironment environment)
throws ServletException
{
this.environment = environment;
environment.jersey().register(new WebSocketSessionContextValueFactoryProvider.Binder());
environment.jersey().register(new WebSocketAuthValueFactoryProvider.Binder());
environment.jersey().register(new JacksonMessageBodyProvider(environment.getObjectMapper()));
}
public void start() throws ServletException {
this.environment.getJerseyServletContainer().init(new WServletConfig());
}
@Override
public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response) {
try {
Optional<WebSocketAuthenticator> authenticator = Optional.ofNullable(environment.getAuthenticator());
Object authenticated = null;
if (authenticator.isPresent()) {
AuthenticationResult authenticationResult = authenticator.get().authenticate(request);
if (!authenticationResult.getUser().isPresent() && authenticationResult.isRequired()) {
response.sendForbidden("Unauthorized");
return null;
} else {
authenticated = authenticationResult.getUser().orElse(null);
}
}
return new WebSocketResourceProvider(this.environment.getJerseyServletContainer(),
this.environment.getRequestLog(),
authenticated,
this.environment.getMessageFactory(),
Optional.ofNullable(this.environment.getConnectListener()),
this.environment.getIdleTimeoutMillis());
} catch (AuthenticationException | IOException e) {
logger.warn("Authentication failure", e);
return null;
}
}
@Override
public void configure(WebSocketServletFactory factory) {
factory.setCreator(this);
}
private static class WServletConfig implements ServletConfig {
private final ServletContext context = new NoContext();
@Override
public String getServletName() {
return "WebSocketResourceServlet";
}
@Override
public ServletContext getServletContext() {
return context;
}
@Override
public String getInitParameter(String name) {
return null;
}
@Override
public Enumeration<String> getInitParameterNames() {
return new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public String nextElement() {
return null;
}
};
}
}
public static class NoContext extends AttributesMap implements ServletContext
{
private int effectiveMajorVersion = 3;
private int effectiveMinorVersion = 0;
@Override
public ServletContext getContext(String uripath)
{
return null;
}
@Override
public int getMajorVersion()
{
return 3;
}
@Override
public String getMimeType(String file)
{
return null;
}
@Override
public int getMinorVersion()
{
return 0;
}
@Override
public RequestDispatcher getNamedDispatcher(String name)
{
return null;
}
@Override
public RequestDispatcher getRequestDispatcher(String uriInContext)
{
return null;
}
@Override
public String getRealPath(String path)
{
return null;
}
@Override
public URL getResource(String path) throws MalformedURLException
{
return null;
}
@Override
public InputStream getResourceAsStream(String path)
{
return null;
}
@Override
public Set<String> getResourcePaths(String path)
{
return null;
}
@Override
public String getServerInfo()
{
return "websocketresources/" + Server.getVersion();
}
@Override
@Deprecated
public Servlet getServlet(String name) throws ServletException
{
return null;
}
@SuppressWarnings("unchecked")
@Override
@Deprecated
public Enumeration<String> getServletNames()
{
return Collections.enumeration(Collections.EMPTY_LIST);
}
@SuppressWarnings("unchecked")
@Override
@Deprecated
public Enumeration<Servlet> getServlets()
{
return Collections.enumeration(Collections.EMPTY_LIST);
}
@Override
public void log(Exception exception, String msg)
{
logger.warn(msg,exception);
}
@Override
public void log(String msg)
{
logger.info(msg);
}
@Override
public void log(String message, Throwable throwable)
{
logger.warn(message,throwable);
}
@Override
public String getInitParameter(String name)
{
return null;
}
@SuppressWarnings("unchecked")
@Override
public Enumeration<String> getInitParameterNames()
{
return Collections.enumeration(Collections.EMPTY_LIST);
}
@Override
public String getServletContextName()
{
return "No Context";
}
@Override
public String getContextPath()
{
return null;
}
@Override
public boolean setInitParameter(String name, String value)
{
return false;
}
@Override
public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
{
return null;
}
@Override
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter)
{
return null;
}
@Override
public FilterRegistration.Dynamic addFilter(String filterName, String className)
{
return null;
}
@Override
public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
{
return null;
}
@Override
public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
{
return null;
}
@Override
public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className)
{
return null;
}
@Override
public <T extends Filter> T createFilter(Class<T> c) throws ServletException
{
return null;
}
@Override
public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
{
return null;
}
@Override
public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
{
return null;
}
@Override
public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
{
return null;
}
@Override
public FilterRegistration getFilterRegistration(String filterName)
{
return null;
}
@Override
public Map<String, ? extends FilterRegistration> getFilterRegistrations()
{
return null;
}
@Override
public ServletRegistration getServletRegistration(String servletName)
{
return null;
}
@Override
public Map<String, ? extends ServletRegistration> getServletRegistrations()
{
return null;
}
@Override
public SessionCookieConfig getSessionCookieConfig()
{
return null;
}
@Override
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
{
}
@Override
public void addListener(String className)
{
}
@Override
public <T extends EventListener> void addListener(T t)
{
}
@Override
public void addListener(Class<? extends EventListener> listenerClass)
{
}
@Override
public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
{
try
{
return clazz.newInstance();
}
catch (InstantiationException e)
{
throw new ServletException(e);
}
catch (IllegalAccessException e)
{
throw new ServletException(e);
}
}
@Override
public ClassLoader getClassLoader()
{
AccessController.checkPermission(new RuntimePermission("getClassLoader"));
return WebSocketResourceProviderFactory.class.getClassLoader();
}
@Override
public int getEffectiveMajorVersion()
{
return effectiveMajorVersion;
}
@Override
public int getEffectiveMinorVersion()
{
return effectiveMinorVersion;
}
public void setEffectiveMajorVersion (int v)
{
this.effectiveMajorVersion = v;
}
public void setEffectiveMinorVersion (int v)
{
this.effectiveMinorVersion = v;
}
@Override
public JspConfigDescriptor getJspConfigDescriptor()
{
return null;
}
@Override
public void declareRoles(String... roleNames)
{
}
@Override
public String getVirtualServerName() {
return null;
}
}
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.auth;
public class AuthenticationException extends Exception {
public AuthenticationException(String s) {
super(s);
}
public AuthenticationException(Exception e) {
super(e);
}
}
/*
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.auth;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import java.util.Optional;
public interface WebSocketAuthenticator<T> {
AuthenticationResult<T> authenticate(UpgradeRequest request) throws AuthenticationException;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class AuthenticationResult<T> {
private final Optional<T> user;
private final boolean required;
public AuthenticationResult(Optional<T> user, boolean required) {
this.user = user;
this.required = required;
}
public Optional<T> getUser() {
return user;
}
public boolean isRequired() {
return required;
}
}
}
package org.whispersystems.websocket.auth.internal;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
import org.whispersystems.websocket.servlet.WebSocketServletRequest;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.WebApplicationException;
import java.security.Principal;
import java.util.Optional;
import io.dropwizard.auth.Auth;
@Singleton
public class WebSocketAuthValueFactoryProvider extends AbstractValueFactoryProvider {
@Inject
public WebSocketAuthValueFactoryProvider(MultivaluedParameterExtractorProvider mpep,
ServiceLocator injector)
{
super(mpep, injector, Parameter.Source.UNKNOWN);
}
@Override
public AbstractContainerRequestValueFactory<?> createValueFactory(final Parameter parameter) {
if (parameter.getAnnotation(Auth.class) == null) {
return null;
}
if (parameter.getRawType() == Optional.class) {
return new OptionalContainerRequestValueFactory(parameter);
} else {
return new StandardContainerRequestValueFactory(parameter);
}
}
private static class OptionalContainerRequestValueFactory extends AbstractContainerRequestValueFactory {
private final Parameter parameter;
private OptionalContainerRequestValueFactory(Parameter parameter) {
this.parameter = parameter;
}
@Override
public Object provide() {
Principal principal = getContainerRequest().getSecurityContext().getUserPrincipal();
if (principal != null && !(principal instanceof WebSocketServletRequest.ContextPrincipal)) {
throw new IllegalArgumentException("Can't inject non-ContextPrincipal into request");
}
if (principal == null) return Optional.empty();
else return Optional.ofNullable(((WebSocketServletRequest.ContextPrincipal)principal).getContext().getAuthenticated());
}
}
private static class StandardContainerRequestValueFactory extends AbstractContainerRequestValueFactory {
private final Parameter parameter;
private StandardContainerRequestValueFactory(Parameter parameter) {
this.parameter = parameter;
}
@Override
public Object provide() {
Principal principal = getContainerRequest().getSecurityContext().getUserPrincipal();
if (principal == null) {
throw new IllegalStateException("Cannot inject a custom principal into unauthenticated request");
}
if (!(principal instanceof WebSocketServletRequest.ContextPrincipal)) {
throw new IllegalArgumentException("Cannot inject a non-WebSocket AuthPrincipal into request");
}
Object authenticated = ((WebSocketServletRequest.ContextPrincipal)principal).getContext().getAuthenticated();
if (authenticated == null) {
throw new WebApplicationException("Authenticated resource", 401);
}
if (!parameter.getRawType().isAssignableFrom(authenticated.getClass())) {
throw new IllegalArgumentException("Authenticated principal is of the wrong type: " + authenticated.getClass() + " looking for: " + parameter.getRawType());
}
return parameter.getRawType().cast(authenticated);
}
}
@Singleton
private static class AuthInjectionResolver extends ParamInjectionResolver<Auth> {
public AuthInjectionResolver() {
super(WebSocketAuthValueFactoryProvider.class);
}
}
public static class Binder extends AbstractBinder {
public Binder() {
}
@Override
protected void configure() {
bind(WebSocketAuthValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(AuthInjectionResolver.class).to(new TypeLiteral<InjectionResolver<Auth>>() {
}).in(Singleton.class);
}
}
}
\ No newline at end of file
package org.whispersystems.websocket.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import io.dropwizard.request.logging.LogbackAccessRequestLogFactory;
import io.dropwizard.request.logging.RequestLogFactory;
public class WebSocketConfiguration {
@Valid
@NotNull
@JsonProperty
private RequestLogFactory requestLog = new LogbackAccessRequestLogFactory();
public RequestLogFactory getRequestLog() {
return requestLog;
}
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.messages;
public class InvalidMessageException extends Exception {
public InvalidMessageException(String s) {
super(s);
}
public InvalidMessageException(Exception e) {
super(e);
}
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.messages;
public interface WebSocketMessage {
public enum Type {
UNKNOWN_MESSAGE,
REQUEST_MESSAGE,
RESPONSE_MESSAGE
}
public Type getType();
public WebSocketRequestMessage getRequestMessage();
public WebSocketResponseMessage getResponseMessage();
public byte[] toByteArray();
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.messages;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public interface WebSocketMessageFactory {
public WebSocketMessage parseMessage(byte[] serialized, int offset, int len)
throws InvalidMessageException;
public WebSocketMessage createRequest(Optional<Long> requestId,
String verb, String path,
List<String> headers,
Optional<byte[]> body);
public WebSocketMessage createResponse(long requestId, int status, String message,
List<String> headers,
Optional<byte[]> body);
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.messages;
import java.util.Map;
import java.util.Optional;
public interface WebSocketRequestMessage {
public String getVerb();
public String getPath();
public Map<String,String> getHeaders();
public Optional<byte[]> getBody();
public long getRequestId();
public boolean hasRequestId();
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.messages;
import java.util.Map;
import java.util.Optional;
public interface WebSocketResponseMessage {
public long getRequestId();
public int getStatus();
public String getMessage();
public Map<String,String> getHeaders();
public Optional<byte[]> getBody();
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.messages.protobuf;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.websocket.messages.InvalidMessageException;
import org.whispersystems.websocket.messages.WebSocketMessage;
import org.whispersystems.websocket.messages.WebSocketRequestMessage;
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
public class ProtobufWebSocketMessage implements WebSocketMessage {
private final SubProtocol.WebSocketMessage message;
ProtobufWebSocketMessage(byte[] buffer, int offset, int length) throws InvalidMessageException {
try {
this.message = SubProtocol.WebSocketMessage.parseFrom(ByteString.copyFrom(buffer, offset, length));
if (getType() == Type.REQUEST_MESSAGE) {
if (!message.getRequest().hasVerb() || !message.getRequest().hasPath()) {
throw new InvalidMessageException("Missing required request attributes!");
}
} else if (getType() == Type.RESPONSE_MESSAGE) {
if (!message.getResponse().hasId() || !message.getResponse().hasStatus() || !message.getResponse().hasMessage()) {
throw new InvalidMessageException("Missing required response attributes!");
}
}
} catch (InvalidProtocolBufferException e) {
throw new InvalidMessageException(e);
}
}
ProtobufWebSocketMessage(SubProtocol.WebSocketMessage message) {
this.message = message;
}
@Override
public Type getType() {
if (message.getType().getNumber() == SubProtocol.WebSocketMessage.Type.REQUEST_VALUE &&
message.hasRequest())
{
return Type.REQUEST_MESSAGE;
} else if (message.getType().getNumber() == SubProtocol.WebSocketMessage.Type.RESPONSE_VALUE &&
message.hasResponse())
{
return Type.RESPONSE_MESSAGE;
} else {
return Type.UNKNOWN_MESSAGE;
}
}
@Override
public WebSocketRequestMessage getRequestMessage() {
return new ProtobufWebSocketRequestMessage(message.getRequest());
}
@Override
public WebSocketResponseMessage getResponseMessage() {
return new ProtobufWebSocketResponseMessage(message.getResponse());
}
@Override
public byte[] toByteArray() {
return message.toByteArray();
}
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.messages.protobuf;
import com.google.protobuf.ByteString;
import org.whispersystems.websocket.messages.InvalidMessageException;
import org.whispersystems.websocket.messages.WebSocketMessage;
import org.whispersystems.websocket.messages.WebSocketMessageFactory;
import java.util.List;
import java.util.Optional;
public class ProtobufWebSocketMessageFactory implements WebSocketMessageFactory {
@Override
public WebSocketMessage parseMessage(byte[] serialized, int offset, int len)
throws InvalidMessageException
{
return new ProtobufWebSocketMessage(serialized, offset, len);
}
@Override
public WebSocketMessage createRequest(Optional<Long> requestId,
String verb, String path,
List<String> headers,
Optional<byte[]> body)
{
SubProtocol.WebSocketRequestMessage.Builder requestMessage =
SubProtocol.WebSocketRequestMessage.newBuilder()
.setVerb(verb)
.setPath(path);
if (requestId.isPresent()) {
requestMessage.setId(requestId.get());
}
if (body.isPresent()) {
requestMessage.setBody(ByteString.copyFrom(body.get()));
}
if (headers != null) {
requestMessage.addAllHeaders(headers);
}
SubProtocol.WebSocketMessage message
= SubProtocol.WebSocketMessage.newBuilder()
.setType(SubProtocol.WebSocketMessage.Type.REQUEST)
.setRequest(requestMessage)
.build();
return new ProtobufWebSocketMessage(message);
}
@Override
public WebSocketMessage createResponse(long requestId, int status, String messageString, List<String> headers, Optional<byte[]> body) {
SubProtocol.WebSocketResponseMessage.Builder responseMessage =
SubProtocol.WebSocketResponseMessage.newBuilder()
.setId(requestId)
.setStatus(status)
.setMessage(messageString);
if (body.isPresent()) {
responseMessage.setBody(ByteString.copyFrom(body.get()));
}
if (headers != null) {
responseMessage.addAllHeaders(headers);
}
SubProtocol.WebSocketMessage message =
SubProtocol.WebSocketMessage.newBuilder()
.setType(SubProtocol.WebSocketMessage.Type.RESPONSE)
.setResponse(responseMessage)
.build();
return new ProtobufWebSocketMessage(message);
}
}
/**
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.messages.protobuf;
import org.whispersystems.websocket.messages.WebSocketRequestMessage;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class ProtobufWebSocketRequestMessage implements WebSocketRequestMessage {
private final SubProtocol.WebSocketRequestMessage message;
ProtobufWebSocketRequestMessage(SubProtocol.WebSocketRequestMessage message) {
this.message = message;
}
@Override
public String getVerb() {
return message.getVerb();
}
@Override
public String getPath() {
return message.getPath();
}
@Override
public Optional<byte[]> getBody() {
if (message.hasBody()) {
return Optional.of(message.getBody().toByteArray());
} else {
return Optional.empty();
}
}
@Override
public long getRequestId() {
return message.getId();
}
@Override
public boolean hasRequestId() {
return message.hasId();
}
@Override
public Map<String, String> getHeaders() {
Map<String, String> results = new HashMap<>();
for (String header : message.getHeadersList()) {
String[] tokenized = header.split(":");
if (tokenized.length == 2 && tokenized[0] != null && tokenized[1] != null) {
results.put(tokenized[0].trim().toLowerCase(), tokenized[1].trim());
}
}
return results;
}
}
/*
* Copyright (C) 2014 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.websocket.messages.protobuf;
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class ProtobufWebSocketResponseMessage implements WebSocketResponseMessage {
private final SubProtocol.WebSocketResponseMessage message;
public ProtobufWebSocketResponseMessage(SubProtocol.WebSocketResponseMessage message) {
this.message = message;
}
@Override
public long getRequestId() {
return message.getId();
}
@Override
public int getStatus() {
return message.getStatus();
}
@Override
public String getMessage() {
return message.getMessage();
}
@Override
public Optional<byte[]> getBody() {
if (message.hasBody()) {
return Optional.of(message.getBody().toByteArray());
} else {
return Optional.empty();
}
}
@Override
public Map<String, String> getHeaders() {
Map<String, String> results = new HashMap<>();
for (String header : message.getHeadersList()) {
String[] tokenized = header.split(":");
if (tokenized.length == 2 && tokenized[0] != null && tokenized[1] != null) {
results.put(tokenized[0].trim().toLowerCase(), tokenized[1].trim());
}
}
return results;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment