diff --git a/pom.xml b/pom.xml
index a45c3b3e6..740ddb7d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
org.whispersystems.textsecure
TextSecureServer
- 0.4
+ 0.5
diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java
index a6ce2e785..e9714720f 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java
@@ -76,7 +76,7 @@ public class FederationController {
@PathParam("attachmentId") long attachmentId)
throws IOException
{
- return attachmentController.redirectToAttachment(new NonLimitedAccount("Unknown", peer.getName()),
+ return attachmentController.redirectToAttachment(new NonLimitedAccount("Unknown", -1, peer.getName()),
attachmentId, Optional.absent());
}
@@ -89,7 +89,7 @@ public class FederationController {
throws IOException
{
try {
- return keysController.get(new NonLimitedAccount("Unknown", peer.getName()), number, Optional.absent());
+ return keysController.get(new NonLimitedAccount("Unknown", -1, peer.getName()), number, Optional.absent());
} catch (RateLimitExceededException e) {
logger.warn("Rate limiting on federated channel", e);
throw new IOException(e);
@@ -106,7 +106,7 @@ public class FederationController {
throws IOException
{
try {
- return keysController.getDeviceKey(new NonLimitedAccount("Unknown", peer.getName()),
+ return keysController.getDeviceKey(new NonLimitedAccount("Unknown", -1, peer.getName()),
number, device, Optional.absent());
} catch (RateLimitExceededException e) {
logger.warn("Rate limiting on federated channel", e);
@@ -116,16 +116,17 @@ public class FederationController {
@Timed
@PUT
- @Path("/messages/{source}/{destination}")
- public void sendMessages(@Auth FederatedPeer peer,
- @PathParam("source") String source,
- @PathParam("destination") String destination,
- @Valid IncomingMessageList messages)
+ @Path("/messages/{source}/{sourceDeviceId}/{destination}")
+ public void sendMessages(@Auth FederatedPeer peer,
+ @PathParam("source") String source,
+ @PathParam("sourceDeviceId") long sourceDeviceId,
+ @PathParam("destination") String destination,
+ @Valid IncomingMessageList messages)
throws IOException
{
try {
messages.setRelay(null);
- messageController.sendMessage(new NonLimitedAccount(source, peer.getName()), destination, messages);
+ messageController.sendMessage(new NonLimitedAccount(source, sourceDeviceId, peer.getName()), destination, messages);
} catch (RateLimitExceededException e) {
logger.warn("Rate limiting on federated channel", e);
throw new IOException(e);
diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java
index 7a2ee06ba..551437693 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java
@@ -186,7 +186,8 @@ public class MessageController {
{
try {
FederatedClient client = federatedClientManager.getClient(messages.getRelay());
- client.sendMessages(source.getNumber(), destinationName, messages);
+ client.sendMessages(source.getNumber(), source.getAuthenticatedDevice().get().getId(),
+ destinationName, messages);
} catch (NoSuchPeerException e) {
throw new NoSuchUserException(e);
}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java b/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java
index b8909c2dd..8168285e8 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java
@@ -64,7 +64,7 @@ public class FederatedClient {
private static final String USER_COUNT_PATH = "/v1/federation/user_count";
private static final String USER_TOKENS_PATH = "/v1/federation/user_tokens/%d";
- private static final String RELAY_MESSAGE_PATH = "/v1/federation/messages/%s/%s";
+ private static final String RELAY_MESSAGE_PATH = "/v1/federation/messages/%s/%d/%s";
private static final String PREKEY_PATH_DEVICE = "/v1/federation/key/%s/%s";
private static final String ATTACHMENT_URI_PATH = "/v1/federation/attachment/%d";
@@ -155,11 +155,11 @@ public class FederatedClient {
}
}
- public void sendMessages(String source, String destination, IncomingMessageList messages)
+ public void sendMessages(String source, long sourceDeviceId, String destination, IncomingMessageList messages)
throws IOException
{
try {
- WebResource resource = client.resource(peer.getUrl()).path(String.format(RELAY_MESSAGE_PATH, source, destination));
+ WebResource resource = client.resource(peer.getUrl()).path(String.format(RELAY_MESSAGE_PATH, source, sourceDeviceId, destination));
ClientResponse response = resource.type(MediaType.APPLICATION_JSON)
.header("Authorization", authorizationHeader)
.entity(messages)
diff --git a/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java b/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java
index 09a2986bf..6b3090adf 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java
@@ -4,6 +4,7 @@ package org.whispersystems.textsecuregcm.federation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.base.Optional;
import org.whispersystems.textsecuregcm.storage.Account;
+import org.whispersystems.textsecuregcm.storage.Device;
public class NonLimitedAccount extends Account {
@@ -13,20 +14,32 @@ public class NonLimitedAccount extends Account {
@JsonIgnore
private final String relay;
- public NonLimitedAccount(String number, String relay) {
- this.number = number;
- this.relay = relay;
+ @JsonIgnore
+ private final long deviceId;
+
+ public NonLimitedAccount(String number, long deviceId, String relay) {
+ this.number = number;
+ this.deviceId = deviceId;
+ this.relay = relay;
}
+ @Override
public String getNumber() {
return number;
}
+ @Override
public boolean isRateLimited() {
return false;
}
+ @Override
public Optional getRelay() {
return Optional.of(relay);
}
+
+ @Override
+ public Optional getAuthenticatedDevice() {
+ return Optional.of(new Device(deviceId, null, null, null, null, null, false, 0));
+ }
}
diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java
new file mode 100644
index 000000000..c00d396a1
--- /dev/null
+++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java
@@ -0,0 +1,89 @@
+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.FederationController;
+import org.whispersystems.textsecuregcm.controllers.MessageController;
+import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
+import org.whispersystems.textsecuregcm.entities.MessageProtos;
+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.jsonFixture;
+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.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class FederatedControllerTest 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 singleDeviceList = new LinkedList() {{
+ add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 111));
+ }};
+
+ List multiDeviceList = new LinkedList() {{
+ add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222));
+ add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333));
+ }};
+
+ 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);
+
+ MessageController messageController = new MessageController(rateLimiters, pushSender, accountsManager, federatedClientManager);
+ addResource(new FederationController(accountsManager, null, null, messageController));
+ }
+
+ @Test
+ public void testSingleDeviceCurrent() throws Exception {
+ ClientResponse response =
+ client().resource(String.format("/v1/federation/messages/+14152223333/1/%s", SINGLE_DEVICE_RECIPIENT))
+ .header("Authorization", AuthHelper.getAuthHeader("cyanogen", "foofoo"))
+ .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));
+ }
+
+
+}
diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java b/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java
index 775ee6189..ba34e9854 100644
--- a/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java
+++ b/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java
@@ -13,6 +13,8 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.util.Base64;
import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.mock;
@@ -40,7 +42,14 @@ public class AuthHelper {
when(account.getRelay()).thenReturn(Optional.absent());
when(accounts.get(VALID_NUMBER)).thenReturn(Optional.of(account));
- return new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(new FederationConfiguration()),
+ List peer = new LinkedList() {{
+ add(new FederatedPeer("cyanogen", "https://foo", "foofoo", "bazzzzz"));
+ }};
+
+ FederationConfiguration federationConfiguration = mock(FederationConfiguration.class);
+ when(federationConfiguration.getPeers()).thenReturn(peer);
+
+ return new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(federationConfiguration),
FederatedPeer.class,
new AccountAuthenticator(accounts),
Account.class, "WhisperServer");