Make WebSocket optional, disabled by default. Add tests.
This commit is contained in:
parent
a9994ef5aa
commit
a6463df5bb
|
@ -29,6 +29,7 @@ import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
|
|||
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.S3Configuration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.WebsocketConfiguration;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
@ -83,6 +84,14 @@ public class WhisperServerConfiguration extends Configuration {
|
|||
@JsonProperty
|
||||
private GraphiteConfiguration graphite = new GraphiteConfiguration();
|
||||
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private WebsocketConfiguration websocket = new WebsocketConfiguration();
|
||||
|
||||
public WebsocketConfiguration getWebsocketConfiguration() {
|
||||
return websocket;
|
||||
}
|
||||
|
||||
public TwilioConfiguration getTwilioConfiguration() {
|
||||
return twilio;
|
||||
}
|
||||
|
|
|
@ -147,8 +147,11 @@ public class WhisperServerService extends Service<WhisperServerConfiguration> {
|
|||
environment.addResource(keysController);
|
||||
environment.addResource(messageController);
|
||||
|
||||
environment.addServlet(new WebsocketControllerFactory(deviceAuthenticator, storedMessageManager, pubSubManager),
|
||||
"/v1/websocket/");
|
||||
if (config.getWebsocketConfiguration().isEnabled()) {
|
||||
environment.addServlet(new WebsocketControllerFactory(deviceAuthenticator, storedMessageManager, pubSubManager),
|
||||
"/v1/websocket/");
|
||||
environment.addFilter(new CORSHeaderFilter(), "/*");
|
||||
}
|
||||
|
||||
environment.addHealthCheck(new RedisHealthCheck(redisClient));
|
||||
environment.addHealthCheck(new MemcacheHealthCheck(memcachedClient));
|
||||
|
@ -156,8 +159,6 @@ public class WhisperServerService extends Service<WhisperServerConfiguration> {
|
|||
environment.addProvider(new IOExceptionMapper());
|
||||
environment.addProvider(new RateLimitExceededExceptionMapper());
|
||||
|
||||
environment.addFilter(new CORSHeaderFilter(), "/*");
|
||||
|
||||
if (config.getGraphiteConfiguration().isEnabled()) {
|
||||
GraphiteReporter.enable(15, TimeUnit.SECONDS,
|
||||
config.getGraphiteConfiguration().getHost(),
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class WebsocketConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
private boolean enabled = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -12,6 +13,9 @@ public class MismatchedDevices {
|
|||
@JsonProperty
|
||||
public List<Long> extraDevices;
|
||||
|
||||
@VisibleForTesting
|
||||
public MismatchedDevices() {}
|
||||
|
||||
public MismatchedDevices(List<Long> missingDevices, List<Long> extraDevices) {
|
||||
this.missingDevices = missingDevices;
|
||||
this.extraDevices = extraDevices;
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.whispersystems.textsecuregcm.storage;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
@ -51,6 +52,13 @@ public class Account implements Serializable {
|
|||
this.supportsSms = supportsSms;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Account(String number, boolean supportsSms, List<Device> devices) {
|
||||
this.number = number;
|
||||
this.supportsSms = supportsSms;
|
||||
this.devices = devices;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Optional;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.yammer.dropwizard.testing.ResourceTest;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||
import org.whispersystems.textsecuregcm.entities.MismatchedDevices;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.yammer.dropwizard.testing.JsonHelpers.asJson;
|
||||
import static com.yammer.dropwizard.testing.JsonHelpers.jsonFixture;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MessageControllerTest extends ResourceTest {
|
||||
|
||||
private static final String SINGLE_DEVICE_RECIPIENT = "+14151111111";
|
||||
private static final String MULTI_DEVICE_RECIPIENT = "+14152222222";
|
||||
|
||||
private PushSender pushSender = mock(PushSender.class );
|
||||
private FederatedClientManager federatedClientManager = mock(FederatedClientManager.class);
|
||||
private AccountsManager accountsManager = mock(AccountsManager.class );
|
||||
private RateLimiters rateLimiters = mock(RateLimiters.class );
|
||||
private RateLimiter rateLimiter = mock(RateLimiter.class );
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
protected void setUpResources() throws Exception {
|
||||
addProvider(AuthHelper.getAuthenticator());
|
||||
|
||||
List<Device> singleDeviceList = new LinkedList<Device>() {{
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false));
|
||||
}};
|
||||
|
||||
List<Device> multiDeviceList = new LinkedList<Device>() {{
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false));
|
||||
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false));
|
||||
}};
|
||||
|
||||
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, false, singleDeviceList);
|
||||
Account multiDeviceAccount = new Account(MULTI_DEVICE_RECIPIENT, false, multiDeviceList);
|
||||
|
||||
when(accountsManager.get(eq(SINGLE_DEVICE_RECIPIENT))).thenReturn(Optional.of(singleDeviceAccount));
|
||||
when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount));
|
||||
|
||||
when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter);
|
||||
|
||||
addResource(new MessageController(rateLimiters, pushSender, accountsManager,
|
||||
federatedClientManager));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleDeviceLegacy() throws Exception {
|
||||
ClientResponse response =
|
||||
client().resource("/v1/messages/")
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.entity(mapper.readValue(jsonFixture("fixtures/legacy_message_single_device.json"), IncomingMessageList.class))
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.post(ClientResponse.class);
|
||||
|
||||
assertThat("Good Response", response.getStatus(), is(equalTo(200)));
|
||||
|
||||
verify(pushSender).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleDeviceCurrent() throws Exception {
|
||||
ClientResponse response =
|
||||
client().resource(String.format("/v1/messages/%s", SINGLE_DEVICE_RECIPIENT))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class))
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.put(ClientResponse.class);
|
||||
|
||||
assertThat("Good Response", response.getStatus(), is(equalTo(204)));
|
||||
|
||||
verify(pushSender).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiDeviceMissing() throws Exception {
|
||||
ClientResponse response =
|
||||
client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class))
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.put(ClientResponse.class);
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(409)));
|
||||
|
||||
assertThat("Good Response Body",
|
||||
asJson(response.getEntity(MismatchedDevices.class)),
|
||||
is(equalTo(jsonFixture("fixtures/missing_device_response.json"))));
|
||||
|
||||
verifyNoMoreInteractions(pushSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiDeviceExtra() throws Exception {
|
||||
ClientResponse response =
|
||||
client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.entity(mapper.readValue(jsonFixture("fixtures/current_message_extra_device.json"), IncomingMessageList.class))
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.put(ClientResponse.class);
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(409)));
|
||||
|
||||
assertThat("Good Response Body",
|
||||
asJson(response.getEntity(MismatchedDevices.class)),
|
||||
is(equalTo(jsonFixture("fixtures/missing_device_response2.json"))));
|
||||
|
||||
verifyNoMoreInteractions(pushSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiDevice() throws Exception {
|
||||
ClientResponse response =
|
||||
client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.entity(mapper.readValue(jsonFixture("fixtures/current_message_multi_device.json"), IncomingMessageList.class))
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.put(ClientResponse.class);
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(204)));
|
||||
|
||||
verify(pushSender, times(2)).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class));
|
||||
}
|
||||
|
||||
}
|
|
@ -33,7 +33,11 @@ public class AuthHelper {
|
|||
|
||||
when(credentials.verify("foo")).thenReturn(true);
|
||||
when(device.getAuthenticationCredentials()).thenReturn(credentials);
|
||||
when(device.getId()).thenReturn(1L);
|
||||
when(account.getDevice(anyLong())).thenReturn(Optional.of(device));
|
||||
when(account.getNumber()).thenReturn(VALID_NUMBER);
|
||||
when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device));
|
||||
when(account.getRelay()).thenReturn(Optional.<String>absent());
|
||||
when(accounts.get(VALID_NUMBER)).thenReturn(Optional.of(account));
|
||||
|
||||
return new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(new FederationConfiguration()),
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"messages" : [{
|
||||
"type" : 1,
|
||||
"destinationDeviceId" : 1,
|
||||
"body" : "Zm9vYmFyego",
|
||||
"timestamp" : 1234
|
||||
},
|
||||
{
|
||||
"type" : 1,
|
||||
"destinationDeviceId" : 3,
|
||||
"body" : "Zm9vYmFyego",
|
||||
"timestamp" : 1234
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"messages" : [{
|
||||
"type" : 1,
|
||||
"destinationDeviceId" : 1,
|
||||
"body" : "Zm9vYmFyego",
|
||||
"timestamp" : 1234
|
||||
},
|
||||
{
|
||||
"type" : 1,
|
||||
"destinationDeviceId" : 2,
|
||||
"body" : "Zm9vYmFyego",
|
||||
"timestamp" : 1234
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"messages" : [{
|
||||
"type" : 1,
|
||||
"destinationDeviceId" : 1,
|
||||
"body" : "Zm9vYmFyego",
|
||||
"timestamp" : 1234
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"messages" : [{
|
||||
"type": 1,
|
||||
"destination": "+14151111111",
|
||||
"body": "Zm9vYmFyego",
|
||||
"timestamp": "1234"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"missingDevices" : [2],
|
||||
"extraDevices" : []
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"missingDevices" : [2],
|
||||
"extraDevices" : [3]
|
||||
}
|
Loading…
Reference in New Issue