diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
index 951be7e3b..1f3f35ada 100644
--- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
+++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
@@ -32,7 +32,8 @@ import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.controllers.AttachmentController;
import org.whispersystems.textsecuregcm.controllers.DeviceController;
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
-import org.whispersystems.textsecuregcm.controllers.FederationController;
+import org.whispersystems.textsecuregcm.controllers.FederationControllerV1;
+import org.whispersystems.textsecuregcm.controllers.FederationControllerV2;
import org.whispersystems.textsecuregcm.controllers.KeysControllerV1;
import org.whispersystems.textsecuregcm.controllers.KeysControllerV2;
import org.whispersystems.textsecuregcm.controllers.MessageController;
@@ -160,7 +161,8 @@ public class WhisperServerService extends Application.
- */
package org.whispersystems.textsecuregcm.controllers;
-import com.codahale.metrics.annotation.Timed;
-import com.google.common.base.Optional;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.whispersystems.textsecuregcm.entities.AccountCount;
-import org.whispersystems.textsecuregcm.entities.AttachmentUri;
-import org.whispersystems.textsecuregcm.entities.ClientContact;
-import org.whispersystems.textsecuregcm.entities.ClientContacts;
-import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
-import org.whispersystems.textsecuregcm.entities.PreKeyResponseV2;
-import org.whispersystems.textsecuregcm.entities.PreKeyV1;
-import org.whispersystems.textsecuregcm.entities.PreKeyResponseV1;
-import org.whispersystems.textsecuregcm.federation.FederatedPeer;
-import org.whispersystems.textsecuregcm.federation.NonLimitedAccount;
-import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
-import org.whispersystems.textsecuregcm.util.Util;
-import javax.validation.Valid;
-import javax.ws.rs.GET;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-
-import io.dropwizard.auth.Auth;
-
-@Path("/")
public class FederationController {
- private final Logger logger = LoggerFactory.getLogger(FederationController.class);
+ protected final AccountsManager accounts;
+ protected final AttachmentController attachmentController;
+ protected final MessageController messageController;
- private static final int ACCOUNT_CHUNK_SIZE = 10000;
-
- private final AccountsManager accounts;
- private final AttachmentController attachmentController;
- private final KeysControllerV1 keysControllerV1;
- private final KeysControllerV2 keysControllerV2;
- private final MessageController messageController;
-
- public FederationController(AccountsManager accounts,
+ public FederationController(AccountsManager accounts,
AttachmentController attachmentController,
- KeysControllerV1 keysControllerV1,
- KeysControllerV2 keysControllerV2,
- MessageController messageController)
+ MessageController messageController)
{
this.accounts = accounts;
this.attachmentController = attachmentController;
- this.keysControllerV1 = keysControllerV1;
- this.keysControllerV2 = keysControllerV2;
this.messageController = messageController;
}
-
- @Timed
- @GET
- @Path("/v1/federation/attachment/{attachmentId}")
- @Produces(MediaType.APPLICATION_JSON)
- public AttachmentUri getSignedAttachmentUri(@Auth FederatedPeer peer,
- @PathParam("attachmentId") long attachmentId)
- throws IOException
- {
- return attachmentController.redirectToAttachment(new NonLimitedAccount("Unknown", -1, peer.getName()),
- attachmentId, Optional.absent());
- }
-
- @Timed
- @GET
- @Path("/v1/federation/key/{number}")
- @Produces(MediaType.APPLICATION_JSON)
- public Optional getKey(@Auth FederatedPeer peer,
- @PathParam("number") String number)
- throws IOException
- {
- try {
- return keysControllerV1.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);
- }
- }
-
- @Timed
- @GET
- @Path("/v1/federation/key/{number}/{device}")
- @Produces(MediaType.APPLICATION_JSON)
- public Optional getKeysV1(@Auth FederatedPeer peer,
- @PathParam("number") String number,
- @PathParam("device") String device)
- throws IOException
- {
- try {
- return keysControllerV1.getDeviceKey(new NonLimitedAccount("Unknown", -1, peer.getName()),
- number, device, Optional.absent());
- } catch (RateLimitExceededException e) {
- logger.warn("Rate limiting on federated channel", e);
- throw new IOException(e);
- }
- }
-
- @Timed
- @GET
- @Path("/v2/federation/key/{number}/{device}")
- @Produces(MediaType.APPLICATION_JSON)
- public Optional getKeysV2(@Auth FederatedPeer peer,
- @PathParam("number") String number,
- @PathParam("device") String device)
- throws IOException
- {
- try {
- return keysControllerV2.getDeviceKeys(new NonLimitedAccount("Unknown", -1, peer.getName()),
- number, device, Optional.absent());
- } catch (RateLimitExceededException e) {
- logger.warn("Rate limiting on federated channel", e);
- throw new IOException(e);
- }
- }
-
- @Timed
- @PUT
- @Path("/v1/federation/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, sourceDeviceId, peer.getName()), destination, messages);
- } catch (RateLimitExceededException e) {
- logger.warn("Rate limiting on federated channel", e);
- throw new IOException(e);
- }
- }
-
- @Timed
- @GET
- @Path("/v1/federation/user_count")
- @Produces(MediaType.APPLICATION_JSON)
- public AccountCount getUserCount(@Auth FederatedPeer peer) {
- return new AccountCount((int)accounts.getCount());
- }
-
- @Timed
- @GET
- @Path("/v1/federation/user_tokens/{offset}")
- @Produces(MediaType.APPLICATION_JSON)
- public ClientContacts getUserTokens(@Auth FederatedPeer peer,
- @PathParam("offset") int offset)
- {
- List accountList = accounts.getAll(offset, ACCOUNT_CHUNK_SIZE);
- List clientContacts = new LinkedList<>();
-
- for (Account account : accountList) {
- byte[] token = Util.getContactToken(account.getNumber());
- ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
-
- if (!account.isActive()) {
- clientContact.setInactive(true);
- }
-
- clientContacts.add(clientContact);
- }
-
- return new ClientContacts(clientContacts);
- }
}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java
new file mode 100644
index 000000000..64fd31b2f
--- /dev/null
+++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java
@@ -0,0 +1,164 @@
+/**
+ * Copyright (C) 2013 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 .
+ */
+package org.whispersystems.textsecuregcm.controllers;
+
+import com.codahale.metrics.annotation.Timed;
+import com.google.common.base.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.textsecuregcm.entities.AccountCount;
+import org.whispersystems.textsecuregcm.entities.AttachmentUri;
+import org.whispersystems.textsecuregcm.entities.ClientContact;
+import org.whispersystems.textsecuregcm.entities.ClientContacts;
+import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
+import org.whispersystems.textsecuregcm.entities.PreKeyResponseV1;
+import org.whispersystems.textsecuregcm.entities.PreKeyV1;
+import org.whispersystems.textsecuregcm.federation.FederatedPeer;
+import org.whispersystems.textsecuregcm.federation.NonLimitedAccount;
+import org.whispersystems.textsecuregcm.storage.Account;
+import org.whispersystems.textsecuregcm.storage.AccountsManager;
+import org.whispersystems.textsecuregcm.util.Util;
+
+import javax.validation.Valid;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import io.dropwizard.auth.Auth;
+
+@Path("/v1/federation")
+public class FederationControllerV1 extends FederationController {
+
+ private final Logger logger = LoggerFactory.getLogger(FederationControllerV1.class);
+
+ private static final int ACCOUNT_CHUNK_SIZE = 10000;
+
+ private final KeysControllerV1 keysControllerV1;
+
+ public FederationControllerV1(AccountsManager accounts,
+ AttachmentController attachmentController,
+ MessageController messageController,
+ KeysControllerV1 keysControllerV1)
+ {
+ super(accounts, attachmentController, messageController);
+ this.keysControllerV1 = keysControllerV1;
+ }
+
+ @Timed
+ @GET
+ @Path("/attachment/{attachmentId}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public AttachmentUri getSignedAttachmentUri(@Auth FederatedPeer peer,
+ @PathParam("attachmentId") long attachmentId)
+ throws IOException
+ {
+ return attachmentController.redirectToAttachment(new NonLimitedAccount("Unknown", -1, peer.getName()),
+ attachmentId, Optional.absent());
+ }
+
+ @Timed
+ @GET
+ @Path("/key/{number}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Optional getKey(@Auth FederatedPeer peer,
+ @PathParam("number") String number)
+ throws IOException
+ {
+ try {
+ return keysControllerV1.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);
+ }
+ }
+
+ @Timed
+ @GET
+ @Path("/key/{number}/{device}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Optional getKeysV1(@Auth FederatedPeer peer,
+ @PathParam("number") String number,
+ @PathParam("device") String device)
+ throws IOException
+ {
+ try {
+ return keysControllerV1.getDeviceKey(new NonLimitedAccount("Unknown", -1, peer.getName()),
+ number, device, Optional.absent());
+ } catch (RateLimitExceededException e) {
+ logger.warn("Rate limiting on federated channel", e);
+ throw new IOException(e);
+ }
+ }
+
+ @Timed
+ @PUT
+ @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, sourceDeviceId, peer.getName()), destination, messages);
+ } catch (RateLimitExceededException e) {
+ logger.warn("Rate limiting on federated channel", e);
+ throw new IOException(e);
+ }
+ }
+
+ @Timed
+ @GET
+ @Path("/user_count")
+ @Produces(MediaType.APPLICATION_JSON)
+ public AccountCount getUserCount(@Auth FederatedPeer peer) {
+ return new AccountCount((int)accounts.getCount());
+ }
+
+ @Timed
+ @GET
+ @Path("/user_tokens/{offset}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public ClientContacts getUserTokens(@Auth FederatedPeer peer,
+ @PathParam("offset") int offset)
+ {
+ List accountList = accounts.getAll(offset, ACCOUNT_CHUNK_SIZE);
+ List clientContacts = new LinkedList<>();
+
+ for (Account account : accountList) {
+ byte[] token = Util.getContactToken(account.getNumber());
+ ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
+
+ if (!account.isActive()) {
+ clientContact.setInactive(true);
+ }
+
+ clientContacts.add(clientContact);
+ }
+
+ return new ClientContacts(clientContacts);
+ }
+}
diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV2.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV2.java
new file mode 100644
index 000000000..85bee34fb
--- /dev/null
+++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV2.java
@@ -0,0 +1,51 @@
+package org.whispersystems.textsecuregcm.controllers;
+
+import com.codahale.metrics.annotation.Timed;
+import com.google.common.base.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.textsecuregcm.entities.PreKeyResponseV2;
+import org.whispersystems.textsecuregcm.federation.FederatedPeer;
+import org.whispersystems.textsecuregcm.federation.NonLimitedAccount;
+import org.whispersystems.textsecuregcm.storage.AccountsManager;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+
+import io.dropwizard.auth.Auth;
+
+@Path("/v2/federation")
+public class FederationControllerV2 extends FederationController {
+
+ private final Logger logger = LoggerFactory.getLogger(FederationControllerV2.class);
+
+ private final KeysControllerV2 keysControllerV2;
+
+ public FederationControllerV2(AccountsManager accounts, AttachmentController attachmentController, MessageController messageController, KeysControllerV2 keysControllerV2) {
+ super(accounts, attachmentController, messageController);
+ this.keysControllerV2 = keysControllerV2;
+ }
+
+ @Timed
+ @GET
+ @Path("/key/{number}/{device}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Optional getKeysV2(@Auth FederatedPeer peer,
+ @PathParam("number") String number,
+ @PathParam("device") String device)
+ throws IOException
+ {
+ try {
+ return keysControllerV2.getDeviceKeys(new NonLimitedAccount("Unknown", -1, peer.getName()),
+ number, device, Optional.absent());
+ } catch (RateLimitExceededException e) {
+ logger.warn("Rate limiting on federated channel", e);
+ throw new IOException(e);
+ }
+ }
+
+}
diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java
index b43d7a7d9..0d8a60499 100644
--- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java
+++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java
@@ -4,13 +4,19 @@ 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 org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.whispersystems.textsecuregcm.controllers.FederationController;
+import org.whispersystems.textsecuregcm.controllers.FederationControllerV1;
+import org.whispersystems.textsecuregcm.controllers.FederationControllerV2;
+import org.whispersystems.textsecuregcm.controllers.KeysControllerV2;
import org.whispersystems.textsecuregcm.controllers.MessageController;
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
+import org.whispersystems.textsecuregcm.entities.PreKeyResponseItemV2;
+import org.whispersystems.textsecuregcm.entities.PreKeyResponseV2;
+import org.whispersystems.textsecuregcm.entities.SignedPreKey;
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
@@ -44,16 +50,19 @@ public class FederatedControllerTest {
private RateLimiters rateLimiters = mock(RateLimiters.class );
private RateLimiter rateLimiter = mock(RateLimiter.class );
+ private final SignedPreKey signedPreKey = new SignedPreKey(3333, "foo", "baar");
+ private final PreKeyResponseV2 preKeyResponseV2 = new PreKeyResponseV2("foo", new LinkedList());
+
private final ObjectMapper mapper = new ObjectMapper();
private final MessageController messageController = new MessageController(rateLimiters, pushSender, accountsManager, federatedClientManager);
+ private final KeysControllerV2 keysControllerV2 = mock(KeysControllerV2.class);
@Rule
public final ResourceTestRule resources = ResourceTestRule.builder()
.addProvider(AuthHelper.getAuthenticator())
- .addResource(new FederationController(accountsManager,
- null, null, null,
- messageController))
+ .addResource(new FederationControllerV1(accountsManager, null, messageController, null))
+ .addResource(new FederationControllerV2(accountsManager, null, messageController, keysControllerV2))
.build();
@@ -76,6 +85,10 @@ public class FederatedControllerTest {
when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount));
when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter);
+
+ when(keysControllerV2.getSignedKey(any(Account.class))).thenReturn(Optional.of(signedPreKey));
+ when(keysControllerV2.getDeviceKeys(any(Account.class), anyString(), anyString(), any(Optional.class)))
+ .thenReturn(Optional.of(preKeyResponseV2));
}
@Test
@@ -92,5 +105,14 @@ public class FederatedControllerTest {
verify(pushSender).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class));
}
+ @Test
+ public void testSignedPreKeyV2() throws Exception {
+ PreKeyResponseV2 response =
+ resources.client().resource("/v2/federation/key/+14152223333/1")
+ .header("Authorization", AuthHelper.getAuthHeader("cyanogen", "foofoo"))
+ .get(PreKeyResponseV2.class);
+
+ assertThat("good response", response.getIdentityKey().equals(preKeyResponseV2.getIdentityKey()));
+ }
}