Introduce V2 API for PreKey updates and requests.
1) A /v2/keys controller. 2) Separate wire protocol PreKey POJOs from database PreKey objects. 3) Separate wire protocol PreKey submission and response POJOs. 4) Introduce a new update/response JSON format for /v2/keys.
This commit is contained in:
parent
d9de015eab
commit
06f80c320d
|
@ -33,7 +33,8 @@ 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.KeysController;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeysControllerV1;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeysControllerV2;
|
||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||
|
@ -147,7 +148,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
accountsManager);
|
||||
|
||||
AttachmentController attachmentController = new AttachmentController(rateLimiters, federatedClientManager, urlSigner);
|
||||
KeysController keysController = new KeysController(rateLimiters, keys, accountsManager, federatedClientManager);
|
||||
KeysControllerV1 keysControllerV1 = new KeysControllerV1(rateLimiters, keys, accountsManager, federatedClientManager);
|
||||
KeysControllerV2 keysControllerV2 = new KeysControllerV2(rateLimiters, keys, accountsManager, federatedClientManager);
|
||||
MessageController messageController = new MessageController(rateLimiters, pushSender, accountsManager, federatedClientManager);
|
||||
|
||||
environment.jersey().register(new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(config.getFederationConfiguration()),
|
||||
|
@ -158,9 +160,10 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender));
|
||||
environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, rateLimiters));
|
||||
environment.jersey().register(new DirectoryController(rateLimiters, directory));
|
||||
environment.jersey().register(new FederationController(accountsManager, attachmentController, keysController, messageController));
|
||||
environment.jersey().register(new FederationController(accountsManager, attachmentController, keysControllerV1, keysControllerV2, messageController));
|
||||
environment.jersey().register(attachmentController);
|
||||
environment.jersey().register(keysController);
|
||||
environment.jersey().register(keysControllerV1);
|
||||
environment.jersey().register(keysControllerV2);
|
||||
environment.jersey().register(messageController);
|
||||
|
||||
if (config.getWebsocketConfiguration().isEnabled()) {
|
||||
|
|
|
@ -25,8 +25,9 @@ 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.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||
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;
|
||||
|
@ -46,7 +47,7 @@ import java.util.List;
|
|||
|
||||
import io.dropwizard.auth.Auth;
|
||||
|
||||
@Path("/v1/federation")
|
||||
@Path("/")
|
||||
public class FederationController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FederationController.class);
|
||||
|
@ -55,23 +56,26 @@ public class FederationController {
|
|||
|
||||
private final AccountsManager accounts;
|
||||
private final AttachmentController attachmentController;
|
||||
private final KeysController keysController;
|
||||
private final KeysControllerV1 keysControllerV1;
|
||||
private final KeysControllerV2 keysControllerV2;
|
||||
private final MessageController messageController;
|
||||
|
||||
public FederationController(AccountsManager accounts,
|
||||
AttachmentController attachmentController,
|
||||
KeysController keysController,
|
||||
MessageController messageController)
|
||||
KeysControllerV1 keysControllerV1,
|
||||
KeysControllerV2 keysControllerV2,
|
||||
MessageController messageController)
|
||||
{
|
||||
this.accounts = accounts;
|
||||
this.attachmentController = attachmentController;
|
||||
this.keysController = keysController;
|
||||
this.keysControllerV1 = keysControllerV1;
|
||||
this.keysControllerV2 = keysControllerV2;
|
||||
this.messageController = messageController;
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/attachment/{attachmentId}")
|
||||
@Path("/v1/federation/attachment/{attachmentId}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public AttachmentUri getSignedAttachmentUri(@Auth FederatedPeer peer,
|
||||
@PathParam("attachmentId") long attachmentId)
|
||||
|
@ -83,14 +87,15 @@ public class FederationController {
|
|||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/key/{number}")
|
||||
@Path("/v1/federation/key/{number}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public PreKey getKey(@Auth FederatedPeer peer,
|
||||
@PathParam("number") String number)
|
||||
public Optional<PreKeyV1> getKey(@Auth FederatedPeer peer,
|
||||
@PathParam("number") String number)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
return keysController.get(new NonLimitedAccount("Unknown", -1, peer.getName()), number, Optional.<String>absent());
|
||||
return keysControllerV1.get(new NonLimitedAccount("Unknown", -1, peer.getName()),
|
||||
number, Optional.<String>absent());
|
||||
} catch (RateLimitExceededException e) {
|
||||
logger.warn("Rate limiting on federated channel", e);
|
||||
throw new IOException(e);
|
||||
|
@ -99,16 +104,34 @@ public class FederationController {
|
|||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/key/{number}/{device}")
|
||||
@Path("/v1/federation/key/{number}/{device}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public UnstructuredPreKeyList getKeys(@Auth FederatedPeer peer,
|
||||
@PathParam("number") String number,
|
||||
@PathParam("device") String device)
|
||||
public Optional<PreKeyResponseV1> getKeysV1(@Auth FederatedPeer peer,
|
||||
@PathParam("number") String number,
|
||||
@PathParam("device") String device)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
return keysController.getDeviceKey(new NonLimitedAccount("Unknown", -1, peer.getName()),
|
||||
number, device, Optional.<String>absent());
|
||||
return keysControllerV1.getDeviceKey(new NonLimitedAccount("Unknown", -1, peer.getName()),
|
||||
number, device, Optional.<String>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<PreKeyResponseV2> getKeysV2(@Auth FederatedPeer peer,
|
||||
@PathParam("number") String number,
|
||||
@PathParam("device") String device)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
return keysControllerV2.getDeviceKey(new NonLimitedAccount("Unknown", -1, peer.getName()),
|
||||
number, device, Optional.<String>absent());
|
||||
} catch (RateLimitExceededException e) {
|
||||
logger.warn("Rate limiting on federated channel", e);
|
||||
throw new IOException(e);
|
||||
|
@ -117,7 +140,7 @@ public class FederationController {
|
|||
|
||||
@Timed
|
||||
@PUT
|
||||
@Path("/messages/{source}/{sourceDeviceId}/{destination}")
|
||||
@Path("/v1/federation/messages/{source}/{sourceDeviceId}/{destination}")
|
||||
public void sendMessages(@Auth FederatedPeer peer,
|
||||
@PathParam("source") String source,
|
||||
@PathParam("sourceDeviceId") long sourceDeviceId,
|
||||
|
@ -136,7 +159,7 @@ public class FederationController {
|
|||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/user_count")
|
||||
@Path("/v1/federation/user_count")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public AccountCount getUserCount(@Auth FederatedPeer peer) {
|
||||
return new AccountCount((int)accounts.getCount());
|
||||
|
@ -144,7 +167,7 @@ public class FederationController {
|
|||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/user_tokens/{offset}")
|
||||
@Path("/v1/federation/user_tokens/{offset}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ClientContacts getUserTokens(@Auth FederatedPeer peer,
|
||||
@PathParam("offset") int offset)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* 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
|
||||
|
@ -18,45 +18,30 @@ 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.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyList;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyStatus;
|
||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyCount;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.KeyRecord;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
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.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
|
||||
@Path("/v1/keys")
|
||||
public class KeysController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KeysController.class);
|
||||
|
||||
private final RateLimiters rateLimiters;
|
||||
private final Keys keys;
|
||||
private final AccountsManager accounts;
|
||||
private final FederatedClientManager federatedClientManager;
|
||||
protected final RateLimiters rateLimiters;
|
||||
protected final Keys keys;
|
||||
protected final AccountsManager accounts;
|
||||
protected final FederatedClientManager federatedClientManager;
|
||||
|
||||
public KeysController(RateLimiters rateLimiters, Keys keys, AccountsManager accounts,
|
||||
FederatedClientManager federatedClientManager)
|
||||
|
@ -67,119 +52,65 @@ public class KeysController {
|
|||
this.federatedClientManager = federatedClientManager;
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void setKeys(@Auth Account account, @Valid PreKeyList preKeys) {
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
String identityKey = preKeys.getLastResortKey().getIdentityKey();
|
||||
|
||||
if (!identityKey.equals(account.getIdentityKey())) {
|
||||
account.setIdentityKey(identityKey);
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
keys.store(account.getNumber(), device.getId(), preKeys.getKeys(), preKeys.getLastResortKey());
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public PreKeyStatus getStatus(@Auth Account account) {
|
||||
public PreKeyCount getStatus(@Auth Account account) {
|
||||
int count = keys.getCount(account.getNumber(), account.getAuthenticatedDevice().get().getId());
|
||||
|
||||
if (count > 0) {
|
||||
count = count - 1;
|
||||
}
|
||||
|
||||
return new PreKeyStatus(count);
|
||||
return new PreKeyCount(count);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/{number}/{device_id}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public UnstructuredPreKeyList getDeviceKey(@Auth Account account,
|
||||
@PathParam("number") String number,
|
||||
@PathParam("device_id") String deviceId,
|
||||
@QueryParam("relay") Optional<String> relay)
|
||||
throws RateLimitExceededException
|
||||
protected TargetKeys getLocalKeys(String number, String deviceIdSelector)
|
||||
throws NoSuchUserException
|
||||
{
|
||||
try {
|
||||
if (account.isRateLimited()) {
|
||||
rateLimiters.getPreKeysLimiter().validate(account.getNumber() + "__" + number + "." + deviceId);
|
||||
}
|
||||
|
||||
Optional<UnstructuredPreKeyList> results;
|
||||
|
||||
if (!relay.isPresent()) results = getLocalKeys(number, deviceId);
|
||||
else results = federatedClientManager.getClient(relay.get()).getKeys(number, deviceId);
|
||||
|
||||
if (results.isPresent()) return results.get();
|
||||
else throw new WebApplicationException(Response.status(404).build());
|
||||
} catch (NoSuchPeerException e) {
|
||||
throw new WebApplicationException(Response.status(404).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/{number}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public PreKey get(@Auth Account account,
|
||||
@PathParam("number") String number,
|
||||
@QueryParam("relay") Optional<String> relay)
|
||||
throws RateLimitExceededException
|
||||
{
|
||||
UnstructuredPreKeyList results = getDeviceKey(account, number, String.valueOf(Device.MASTER_ID), relay);
|
||||
return results.getKeys().get(0);
|
||||
}
|
||||
|
||||
private Optional<UnstructuredPreKeyList> getLocalKeys(String number, String deviceIdSelector) {
|
||||
Optional<Account> destination = accounts.get(number);
|
||||
|
||||
if (!destination.isPresent() || !destination.get().isActive()) {
|
||||
return Optional.absent();
|
||||
throw new NoSuchUserException("Target account is inactive");
|
||||
}
|
||||
|
||||
try {
|
||||
if (deviceIdSelector.equals("*")) {
|
||||
Optional<UnstructuredPreKeyList> preKeys = keys.get(number);
|
||||
return getActiveKeys(destination.get(), preKeys);
|
||||
Optional<List<KeyRecord>> preKeys = keys.get(number);
|
||||
return new TargetKeys(destination.get(), preKeys);
|
||||
}
|
||||
|
||||
long deviceId = Long.parseLong(deviceIdSelector);
|
||||
Optional<Device> targetDevice = destination.get().getDevice(deviceId);
|
||||
|
||||
if (!targetDevice.isPresent() || !targetDevice.get().isActive()) {
|
||||
return Optional.absent();
|
||||
throw new NoSuchUserException("Target device is inactive.");
|
||||
}
|
||||
|
||||
Optional<UnstructuredPreKeyList> preKeys = keys.get(number, deviceId);
|
||||
return getActiveKeys(destination.get(), preKeys);
|
||||
Optional<List<KeyRecord>> preKeys = keys.get(number, deviceId);
|
||||
return new TargetKeys(destination.get(), preKeys);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new WebApplicationException(Response.status(422).build());
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<UnstructuredPreKeyList> getActiveKeys(Account destination,
|
||||
Optional<UnstructuredPreKeyList> preKeys)
|
||||
{
|
||||
if (!preKeys.isPresent()) return Optional.absent();
|
||||
|
||||
List<PreKey> filteredKeys = new LinkedList<>();
|
||||
public static class TargetKeys {
|
||||
private final Account destination;
|
||||
private final Optional<List<KeyRecord>> keys;
|
||||
|
||||
for (PreKey preKey : preKeys.get().getKeys()) {
|
||||
Optional<Device> device = destination.getDevice(preKey.getDeviceId());
|
||||
|
||||
if (device.isPresent() && device.get().isActive()) {
|
||||
preKey.setRegistrationId(device.get().getRegistrationId());
|
||||
preKey.setIdentityKey(destination.getIdentityKey());
|
||||
filteredKeys.add(preKey);
|
||||
}
|
||||
public TargetKeys(Account destination, Optional<List<KeyRecord>> keys) {
|
||||
this.destination = destination;
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
if (filteredKeys.isEmpty()) return Optional.absent();
|
||||
else return Optional.of(new UnstructuredPreKeyList(filteredKeys));
|
||||
public Optional<List<KeyRecord>> getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public Account getDestination() {
|
||||
return destination;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* 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.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.PreKeyResponseV1;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyStateV1;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyV1;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.KeyRecord;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
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.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
|
||||
@Path("/v1/keys")
|
||||
public class KeysControllerV1 extends KeysController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KeysControllerV1.class);
|
||||
|
||||
public KeysControllerV1(RateLimiters rateLimiters, Keys keys, AccountsManager accounts,
|
||||
FederatedClientManager federatedClientManager)
|
||||
{
|
||||
super(rateLimiters, keys, accounts, federatedClientManager);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void setKeys(@Auth Account account, @Valid PreKeyStateV1 preKeys) {
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
String identityKey = preKeys.getLastResortKey().getIdentityKey();
|
||||
|
||||
if (!identityKey.equals(account.getIdentityKey())) {
|
||||
account.setIdentityKey(identityKey);
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
keys.store(account.getNumber(), device.getId(), preKeys.getKeys(), preKeys.getLastResortKey());
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/{number}/{device_id}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Optional<PreKeyResponseV1> getDeviceKey(@Auth Account account,
|
||||
@PathParam("number") String number,
|
||||
@PathParam("device_id") String deviceId,
|
||||
@QueryParam("relay") Optional<String> relay)
|
||||
throws RateLimitExceededException
|
||||
{
|
||||
try {
|
||||
if (account.isRateLimited()) {
|
||||
rateLimiters.getPreKeysLimiter().validate(account.getNumber() + "__" + number + "." + deviceId);
|
||||
}
|
||||
|
||||
if (relay.isPresent()) {
|
||||
return federatedClientManager.getClient(relay.get()).getKeysV1(number, deviceId);
|
||||
}
|
||||
|
||||
TargetKeys targetKeys = getLocalKeys(number, deviceId);
|
||||
|
||||
if (!targetKeys.getKeys().isPresent()) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
List<PreKeyV1> preKeys = new LinkedList<>();
|
||||
Account destination = targetKeys.getDestination();
|
||||
|
||||
for (KeyRecord record : targetKeys.getKeys().get()) {
|
||||
Optional<Device> device = destination.getDevice(record.getDeviceId());
|
||||
if (device.isPresent() && device.get().isActive()) {
|
||||
preKeys.add(new PreKeyV1(record.getDeviceId(), record.getKeyId(),
|
||||
record.getPublicKey(), destination.getIdentityKey(),
|
||||
device.get().getRegistrationId()));
|
||||
}
|
||||
}
|
||||
|
||||
if (preKeys.isEmpty()) return Optional.absent();
|
||||
else return Optional.of(new PreKeyResponseV1(preKeys));
|
||||
} catch (NoSuchPeerException | NoSuchUserException e) {
|
||||
throw new WebApplicationException(Response.status(404).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/{number}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Optional<PreKeyV1> get(@Auth Account account,
|
||||
@PathParam("number") String number,
|
||||
@QueryParam("relay") Optional<String> relay)
|
||||
throws RateLimitExceededException
|
||||
{
|
||||
Optional<PreKeyResponseV1> results = getDeviceKey(account, number, String.valueOf(Device.MASTER_ID), relay);
|
||||
|
||||
if (results.isPresent()) return Optional.of(results.get().getKeys().get(0));
|
||||
else return Optional.absent();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* 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.textsecuregcm.controllers;
|
||||
|
||||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import com.google.common.base.Optional;
|
||||
import org.whispersystems.textsecuregcm.entities.DeviceKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyResponseItemV2;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyResponseV2;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyStateV2;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyV2;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.KeyRecord;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
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.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
|
||||
@Path("/v2/keys")
|
||||
public class KeysControllerV2 extends KeysController {
|
||||
|
||||
public KeysControllerV2(RateLimiters rateLimiters, Keys keys, AccountsManager accounts,
|
||||
FederatedClientManager federatedClientManager)
|
||||
{
|
||||
super(rateLimiters, keys, accounts, federatedClientManager);
|
||||
}
|
||||
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void setKeys(@Auth Account account, @Valid PreKeyStateV2 preKeys) {
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
boolean updateAccount = false;
|
||||
|
||||
if (!preKeys.getDeviceKey().equals(device.getDeviceKey())) {
|
||||
device.setDeviceKey(preKeys.getDeviceKey());
|
||||
updateAccount = true;
|
||||
}
|
||||
|
||||
if (!preKeys.getIdentityKey().equals(account.getIdentityKey())) {
|
||||
account.setIdentityKey(preKeys.getIdentityKey());
|
||||
updateAccount = true;
|
||||
}
|
||||
|
||||
if (updateAccount) {
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
keys.store(account.getNumber(), device.getId(), preKeys.getPreKeys(), preKeys.getLastResortKey());
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Path("/device")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void setDeviceKey(@Auth Account account, @Valid DeviceKey deviceKey) {
|
||||
Device device = account.getAuthenticatedDevice().get();
|
||||
device.setDeviceKey(deviceKey);
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/{number}/{device_id}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Optional<PreKeyResponseV2> getDeviceKey(@Auth Account account,
|
||||
@PathParam("number") String number,
|
||||
@PathParam("device_id") String deviceId,
|
||||
@QueryParam("relay") Optional<String> relay)
|
||||
throws RateLimitExceededException
|
||||
{
|
||||
try {
|
||||
if (account.isRateLimited()) {
|
||||
rateLimiters.getPreKeysLimiter().validate(account.getNumber() + "__" + number + "." + deviceId);
|
||||
}
|
||||
|
||||
if (relay.isPresent()) {
|
||||
return federatedClientManager.getClient(relay.get()).getKeysV2(number, deviceId);
|
||||
}
|
||||
|
||||
TargetKeys targetKeys = getLocalKeys(number, deviceId);
|
||||
Account destination = targetKeys.getDestination();
|
||||
List<PreKeyResponseItemV2> devices = new LinkedList<>();
|
||||
|
||||
for (Device device : destination.getDevices()) {
|
||||
if (device.isActive() && (deviceId.equals("*") || device.getId() == Long.parseLong(deviceId))) {
|
||||
DeviceKey deviceKey = device.getDeviceKey();
|
||||
PreKeyV2 preKey = null;
|
||||
|
||||
if (targetKeys.getKeys().isPresent()) {
|
||||
for (KeyRecord keyRecord : targetKeys.getKeys().get()) {
|
||||
if (keyRecord.getDeviceId() == device.getId()) {
|
||||
preKey = new PreKeyV2(keyRecord.getKeyId(), keyRecord.getPublicKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceKey != null || preKey != null) {
|
||||
devices.add(new PreKeyResponseItemV2(device.getId(), device.getRegistrationId(), deviceKey, preKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (devices.isEmpty()) return Optional.absent();
|
||||
else return Optional.of(new PreKeyResponseV2(destination.getIdentityKey(), devices));
|
||||
} catch (NoSuchPeerException | NoSuchUserException e) {
|
||||
throw new WebApplicationException(Response.status(404).build());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class DeviceKey extends PreKeyV2 implements Serializable {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String signature;
|
||||
|
||||
public DeviceKey() {}
|
||||
|
||||
public DeviceKey(long keyId, String publicKey, String signature) {
|
||||
super(keyId, publicKey);
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object == null || !(object instanceof DeviceKey)) return false;
|
||||
DeviceKey that = (DeviceKey) object;
|
||||
|
||||
if (signature == null) {
|
||||
return super.equals(object) && that.signature == null;
|
||||
} else {
|
||||
return super.equals(object) && this.signature.equals(that.signature);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (signature == null) {
|
||||
return super.hashCode();
|
||||
} else {
|
||||
return super.hashCode() ^ signature.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
public interface PreKeyBase {
|
||||
|
||||
public long getKeyId();
|
||||
public String getPublicKey();
|
||||
|
||||
}
|
|
@ -3,16 +3,16 @@ package org.whispersystems.textsecuregcm.entities;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class PreKeyStatus {
|
||||
public class PreKeyCount {
|
||||
|
||||
@JsonProperty
|
||||
private int count;
|
||||
|
||||
public PreKeyStatus(int count) {
|
||||
public PreKeyCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public PreKeyStatus() {}
|
||||
public PreKeyCount() {}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* 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.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
public class PreKeyResponseItemV2 {
|
||||
|
||||
@JsonProperty
|
||||
private long deviceId;
|
||||
|
||||
@JsonProperty
|
||||
private int registrationId;
|
||||
|
||||
@JsonProperty
|
||||
private DeviceKey deviceKey;
|
||||
|
||||
@JsonProperty
|
||||
private PreKeyV2 preKey;
|
||||
|
||||
public PreKeyResponseItemV2() {}
|
||||
|
||||
public PreKeyResponseItemV2(long deviceId, int registrationId, DeviceKey deviceKey, PreKeyV2 preKey) {
|
||||
this.deviceId = deviceId;
|
||||
this.registrationId = registrationId;
|
||||
this.deviceKey = deviceKey;
|
||||
this.preKey = preKey;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public DeviceKey getDeviceKey() {
|
||||
return deviceKey;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public PreKeyV2 getPreKey() {
|
||||
return preKey;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getRegistrationId() {
|
||||
return registrationId;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public long getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,6 @@ package org.whispersystems.textsecuregcm.entities;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
@ -26,36 +25,36 @@ import java.util.Iterator;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class UnstructuredPreKeyList {
|
||||
public class PreKeyResponseV1 {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<PreKey> keys;
|
||||
private List<PreKeyV1> keys;
|
||||
|
||||
@VisibleForTesting
|
||||
public UnstructuredPreKeyList() {}
|
||||
public PreKeyResponseV1() {}
|
||||
|
||||
public UnstructuredPreKeyList(PreKey preKey) {
|
||||
this.keys = new LinkedList<PreKey>();
|
||||
public PreKeyResponseV1(PreKeyV1 preKey) {
|
||||
this.keys = new LinkedList<>();
|
||||
this.keys.add(preKey);
|
||||
}
|
||||
|
||||
public UnstructuredPreKeyList(List<PreKey> preKeys) {
|
||||
public PreKeyResponseV1(List<PreKeyV1> preKeys) {
|
||||
this.keys = preKeys;
|
||||
}
|
||||
|
||||
public List<PreKey> getKeys() {
|
||||
public List<PreKeyV1> getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof UnstructuredPreKeyList) ||
|
||||
((UnstructuredPreKeyList) o).keys.size() != keys.size())
|
||||
if (!(o instanceof PreKeyResponseV1) ||
|
||||
((PreKeyResponseV1) o).keys.size() != keys.size())
|
||||
return false;
|
||||
Iterator<PreKey> otherKeys = ((UnstructuredPreKeyList) o).keys.iterator();
|
||||
for (PreKey key : keys) {
|
||||
Iterator<PreKeyV1> otherKeys = ((PreKeyResponseV1) o).keys.iterator();
|
||||
for (PreKeyV1 key : keys) {
|
||||
if (!otherKeys.next().equals(key))
|
||||
return false;
|
||||
}
|
||||
|
@ -64,7 +63,7 @@ public class UnstructuredPreKeyList {
|
|||
|
||||
public int hashCode() {
|
||||
int ret = 0xFBA4C795 * keys.size();
|
||||
for (PreKey key : keys)
|
||||
for (PreKeyV1 key : keys)
|
||||
ret ^= key.getPublicKey().hashCode();
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* 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.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PreKeyResponseV2 {
|
||||
|
||||
@JsonProperty
|
||||
private String identityKey;
|
||||
|
||||
@JsonProperty
|
||||
private List<PreKeyResponseItemV2> devices;
|
||||
|
||||
public PreKeyResponseV2() {}
|
||||
|
||||
public PreKeyResponseV2(String identityKey, List<PreKeyResponseItemV2> devices) {
|
||||
this.identityKey = identityKey;
|
||||
this.devices = devices;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public String getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public List<PreKeyResponseItemV2> getDevices() {
|
||||
return devices;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* 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
|
||||
|
@ -18,39 +18,38 @@ package org.whispersystems.textsecuregcm.entities;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public class PreKeyList {
|
||||
public class PreKeyStateV1 {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private PreKey lastResortKey;
|
||||
private PreKeyV1 lastResortKey;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<PreKey> keys;
|
||||
private List<PreKeyV1> keys;
|
||||
|
||||
public List<PreKey> getKeys() {
|
||||
public List<PreKeyV1> getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setKeys(List<PreKey> keys) {
|
||||
public void setKeys(List<PreKeyV1> keys) {
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
public PreKey getLastResortKey() {
|
||||
public PreKeyV1 getLastResortKey() {
|
||||
return lastResortKey;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setLastResortKey(PreKey lastResortKey) {
|
||||
public void setLastResortKey(PreKeyV1 lastResortKey) {
|
||||
this.lastResortKey = lastResortKey;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* 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.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public class PreKeyStateV2 {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<PreKeyV2> preKeys;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private DeviceKey deviceKey;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private PreKeyV2 lastResortKey;
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String identityKey;
|
||||
|
||||
public PreKeyStateV2() {}
|
||||
|
||||
@VisibleForTesting
|
||||
public PreKeyStateV2(String identityKey, DeviceKey deviceKey, List<PreKeyV2> keys, PreKeyV2 lastResortKey) {
|
||||
this.identityKey = identityKey;
|
||||
this.deviceKey = deviceKey;
|
||||
this.preKeys = keys;
|
||||
this.lastResortKey = lastResortKey;
|
||||
}
|
||||
|
||||
public List<PreKeyV2> getPreKeys() {
|
||||
return preKeys;
|
||||
}
|
||||
|
||||
public DeviceKey getDeviceKey() {
|
||||
return deviceKey;
|
||||
}
|
||||
|
||||
public String getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public PreKeyV2 getLastResortKey() {
|
||||
return lastResortKey;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* 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
|
||||
|
@ -17,23 +17,14 @@
|
|||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
import java.io.Serializable;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||
public class PreKey {
|
||||
|
||||
@JsonIgnore
|
||||
private long id;
|
||||
|
||||
@JsonIgnore
|
||||
private String number;
|
||||
public class PreKeyV1 implements PreKeyBase {
|
||||
|
||||
@JsonProperty
|
||||
private long deviceId;
|
||||
|
@ -50,89 +41,43 @@ public class PreKey {
|
|||
@NotNull
|
||||
private String identityKey;
|
||||
|
||||
@JsonProperty
|
||||
private boolean lastResort;
|
||||
|
||||
@JsonProperty
|
||||
private int registrationId;
|
||||
|
||||
public PreKey() {}
|
||||
public PreKeyV1() {}
|
||||
|
||||
public PreKey(long id, String number, long deviceId, long keyId,
|
||||
String publicKey, boolean lastResort)
|
||||
public PreKeyV1(long deviceId, long keyId, String publicKey, String identityKey, int registrationId)
|
||||
{
|
||||
this.id = id;
|
||||
this.number = number;
|
||||
this.deviceId = deviceId;
|
||||
this.keyId = keyId;
|
||||
this.publicKey = publicKey;
|
||||
this.lastResort = lastResort;
|
||||
this.deviceId = deviceId;
|
||||
this.keyId = keyId;
|
||||
this.publicKey = publicKey;
|
||||
this.identityKey = identityKey;
|
||||
this.registrationId = registrationId;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public PreKey(long id, String number, long deviceId, long keyId,
|
||||
String publicKey, String identityKey, boolean lastResort)
|
||||
public PreKeyV1(long deviceId, long keyId, String publicKey, String identityKey)
|
||||
{
|
||||
this.id = id;
|
||||
this.number = number;
|
||||
this.deviceId = deviceId;
|
||||
this.keyId = keyId;
|
||||
this.publicKey = publicKey;
|
||||
this.identityKey = identityKey;
|
||||
this.lastResort = lastResort;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(String number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public void setKeyId(long keyId) {
|
||||
this.keyId = keyId;
|
||||
}
|
||||
|
||||
public String getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public void setIdentityKey(String identityKey) {
|
||||
this.identityKey = identityKey;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public boolean isLastResort() {
|
||||
return lastResort;
|
||||
}
|
||||
|
||||
public void setLastResort(boolean lastResort) {
|
||||
this.lastResort = lastResort;
|
||||
}
|
||||
|
||||
public void setDeviceId(long deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class PreKeyV2 implements PreKeyBase {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private long keyId;
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String publicKey;
|
||||
|
||||
public PreKeyV2() {}
|
||||
|
||||
public PreKeyV2(long keyId, String publicKey)
|
||||
{
|
||||
this.keyId = keyId;
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public void setKeyId(long keyId) {
|
||||
this.keyId = keyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object == null || !(object instanceof PreKeyV2)) return false;
|
||||
PreKeyV2 that = (PreKeyV2)object;
|
||||
|
||||
if (publicKey == null) {
|
||||
return this.keyId == that.keyId && that.publicKey == null;
|
||||
} else {
|
||||
return this.keyId == that.keyId && this.publicKey.equals(that.publicKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (publicKey == null) {
|
||||
return (int)this.keyId;
|
||||
} else {
|
||||
return ((int)this.keyId) ^ publicKey.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -36,7 +36,8 @@ 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.UnstructuredPreKeyList;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyResponseV1;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyResponseV2;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
@ -62,11 +63,12 @@ public class FederatedClient {
|
|||
|
||||
private final Logger logger = LoggerFactory.getLogger(FederatedClient.class);
|
||||
|
||||
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/%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";
|
||||
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/%d/%s";
|
||||
private static final String PREKEY_PATH_DEVICE_V1 = "/v1/federation/key/%s/%s";
|
||||
private static final String PREKEY_PATH_DEVICE_V2 = "/v2/federation/key/%s/%s";
|
||||
private static final String ATTACHMENT_URI_PATH = "/v1/federation/attachment/%d";
|
||||
|
||||
private final FederatedPeer peer;
|
||||
private final Client client;
|
||||
|
@ -107,9 +109,9 @@ public class FederatedClient {
|
|||
}
|
||||
}
|
||||
|
||||
public Optional<UnstructuredPreKeyList> getKeys(String destination, String device) {
|
||||
public Optional<PreKeyResponseV1> getKeysV1(String destination, String device) {
|
||||
try {
|
||||
WebResource resource = client.resource(peer.getUrl()).path(String.format(PREKEY_PATH_DEVICE, destination, device));
|
||||
WebResource resource = client.resource(peer.getUrl()).path(String.format(PREKEY_PATH_DEVICE_V1, destination, device));
|
||||
|
||||
ClientResponse response = resource.accept(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", authorizationHeader)
|
||||
|
@ -119,7 +121,7 @@ public class FederatedClient {
|
|||
throw new WebApplicationException(clientResponseToResponse(response));
|
||||
}
|
||||
|
||||
return Optional.of(response.getEntity(UnstructuredPreKeyList.class));
|
||||
return Optional.of(response.getEntity(PreKeyResponseV1.class));
|
||||
|
||||
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||
logger.warn("PreKey", e);
|
||||
|
@ -127,6 +129,27 @@ public class FederatedClient {
|
|||
}
|
||||
}
|
||||
|
||||
public Optional<PreKeyResponseV2> getKeysV2(String destination, String device) {
|
||||
try {
|
||||
WebResource resource = client.resource(peer.getUrl()).path(String.format(PREKEY_PATH_DEVICE_V2, destination, device));
|
||||
|
||||
ClientResponse response = resource.accept(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", authorizationHeader)
|
||||
.get(ClientResponse.class);
|
||||
|
||||
if (response.getStatus() < 200 || response.getStatus() >= 300) {
|
||||
throw new WebApplicationException(clientResponseToResponse(response));
|
||||
}
|
||||
|
||||
return Optional.of(response.getEntity(PreKeyResponseV2.class));
|
||||
|
||||
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||
logger.warn("PreKey", e);
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getUserCount() {
|
||||
try {
|
||||
WebResource resource = client.resource(peer.getUrl()).path(USER_COUNT_PATH);
|
||||
|
|
|
@ -40,6 +40,6 @@ public class NonLimitedAccount extends Account {
|
|||
|
||||
@Override
|
||||
public Optional<Device> getAuthenticatedDevice() {
|
||||
return Optional.of(new Device(deviceId, null, null, null, null, null, false, 0));
|
||||
return Optional.of(new Device(deviceId, null, null, null, null, null, false, 0, null));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import java.util.List;
|
|||
|
||||
public class Account implements Serializable {
|
||||
|
||||
public static final int MEMCACHE_VERION = 3;
|
||||
public static final int MEMCACHE_VERION = 4;
|
||||
|
||||
@JsonIgnore
|
||||
private long id;
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.whispersystems.textsecuregcm.storage;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
||||
import org.whispersystems.textsecuregcm.entities.DeviceKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyV2;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
@ -51,11 +53,15 @@ public class Device implements Serializable {
|
|||
@JsonProperty
|
||||
private int registrationId;
|
||||
|
||||
@JsonProperty
|
||||
private DeviceKey deviceKey;
|
||||
|
||||
public Device() {}
|
||||
|
||||
public Device(long id, String authToken, String salt,
|
||||
String signalingKey, String gcmId, String apnId,
|
||||
boolean fetchesMessages, int registrationId)
|
||||
boolean fetchesMessages, int registrationId,
|
||||
DeviceKey deviceKey)
|
||||
{
|
||||
this.id = id;
|
||||
this.authToken = authToken;
|
||||
|
@ -65,6 +71,7 @@ public class Device implements Serializable {
|
|||
this.apnId = apnId;
|
||||
this.fetchesMessages = fetchesMessages;
|
||||
this.registrationId = registrationId;
|
||||
this.deviceKey = deviceKey;
|
||||
}
|
||||
|
||||
public String getApnId() {
|
||||
|
@ -131,4 +138,12 @@ public class Device implements Serializable {
|
|||
public void setRegistrationId(int registrationId) {
|
||||
this.registrationId = registrationId;
|
||||
}
|
||||
|
||||
public DeviceKey getDeviceKey() {
|
||||
return deviceKey;
|
||||
}
|
||||
|
||||
public void setDeviceKey(DeviceKey deviceKey) {
|
||||
this.deviceKey = deviceKey;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
public class KeyRecord {
|
||||
|
||||
private long id;
|
||||
private String number;
|
||||
private long deviceId;
|
||||
private long keyId;
|
||||
private String publicKey;
|
||||
private boolean lastResort;
|
||||
|
||||
public KeyRecord(long id, String number, long deviceId, long keyId,
|
||||
String publicKey, boolean lastResort)
|
||||
{
|
||||
this.id = id;
|
||||
this.number = number;
|
||||
this.deviceId = deviceId;
|
||||
this.keyId = keyId;
|
||||
this.publicKey = publicKey;
|
||||
this.lastResort = lastResort;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public long getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public boolean isLastResort() {
|
||||
return lastResort;
|
||||
}
|
||||
}
|
|
@ -30,8 +30,9 @@ import org.skife.jdbi.v2.sqlobject.SqlUpdate;
|
|||
import org.skife.jdbi.v2.sqlobject.Transaction;
|
||||
import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
|
||||
import org.skife.jdbi.v2.tweak.ResultSetMapper;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyBase;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyV1;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyV2;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
@ -40,6 +41,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class Keys {
|
||||
|
@ -51,65 +53,64 @@ public abstract class Keys {
|
|||
abstract void removeKey(@Bind("id") long id);
|
||||
|
||||
@SqlBatch("INSERT INTO keys (number, device_id, key_id, public_key, last_resort) VALUES " +
|
||||
"(:number, :device_id, :key_id, :public_key, :last_resort)")
|
||||
abstract void append(@PreKeyBinder List<PreKey> preKeys);
|
||||
|
||||
@SqlUpdate("INSERT INTO keys (number, device_id, key_id, public_key, last_resort) VALUES " +
|
||||
"(:number, :device_id, :key_id, :public_key, :last_resort)")
|
||||
abstract void append(@PreKeyBinder PreKey preKey);
|
||||
"(:number, :device_id, :key_id, :public_key, :last_resort)")
|
||||
abstract void append(@PreKeyBinder List<KeyRecord> preKeys);
|
||||
|
||||
@SqlQuery("SELECT * FROM keys WHERE number = :number AND device_id = :device_id ORDER BY key_id ASC FOR UPDATE")
|
||||
@Mapper(PreKeyMapper.class)
|
||||
abstract PreKey retrieveFirst(@Bind("number") String number, @Bind("device_id") long deviceId);
|
||||
abstract KeyRecord retrieveFirst(@Bind("number") String number, @Bind("device_id") long deviceId);
|
||||
|
||||
@SqlQuery("SELECT DISTINCT ON (number, device_id) * FROM keys WHERE number = :number ORDER BY number, device_id, key_id ASC")
|
||||
@Mapper(PreKeyMapper.class)
|
||||
abstract List<PreKey> retrieveFirst(@Bind("number") String number);
|
||||
abstract List<KeyRecord> retrieveFirst(@Bind("number") String number);
|
||||
|
||||
@SqlQuery("SELECT COUNT(*) FROM keys WHERE number = :number AND device_id = :device_id")
|
||||
public abstract int getCount(@Bind("number") String number, @Bind("device_id") long deviceId);
|
||||
|
||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||
public void store(String number, long deviceId, List<PreKey> keys, PreKey lastResortKey) {
|
||||
for (PreKey key : keys) {
|
||||
key.setNumber(number);
|
||||
key.setDeviceId(deviceId);
|
||||
public void store(String number, long deviceId, List<? extends PreKeyBase> keys, PreKeyBase lastResortKey) {
|
||||
List<KeyRecord> records = new LinkedList<>();
|
||||
|
||||
for (PreKeyBase key : keys) {
|
||||
records.add(new KeyRecord(0, number, deviceId, key.getKeyId(), key.getPublicKey(), false));
|
||||
}
|
||||
|
||||
lastResortKey.setNumber(number);
|
||||
lastResortKey.setDeviceId(deviceId);
|
||||
lastResortKey.setLastResort(true);
|
||||
records.add(new KeyRecord(0, number, deviceId, lastResortKey.getKeyId(),
|
||||
lastResortKey.getPublicKey(), true));
|
||||
|
||||
removeKeys(number, deviceId);
|
||||
append(keys);
|
||||
append(lastResortKey);
|
||||
append(records);
|
||||
}
|
||||
|
||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||
public Optional<UnstructuredPreKeyList> get(String number, long deviceId) {
|
||||
PreKey preKey = retrieveFirst(number, deviceId);
|
||||
public Optional<List<KeyRecord>> get(String number, long deviceId) {
|
||||
final KeyRecord record = retrieveFirst(number, deviceId);
|
||||
|
||||
if (preKey != null && !preKey.isLastResort()) {
|
||||
removeKey(preKey.getId());
|
||||
if (record != null && !record.isLastResort()) {
|
||||
removeKey(record.getId());
|
||||
} else if (record == null) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
if (preKey != null) return Optional.of(new UnstructuredPreKeyList(preKey));
|
||||
else return Optional.absent();
|
||||
List<KeyRecord> results = new LinkedList<>();
|
||||
results.add(record);
|
||||
|
||||
return Optional.of(results);
|
||||
}
|
||||
|
||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||
public Optional<UnstructuredPreKeyList> get(String number) {
|
||||
List<PreKey> preKeys = retrieveFirst(number);
|
||||
public Optional<List<KeyRecord>> get(String number) {
|
||||
List<KeyRecord> preKeys = retrieveFirst(number);
|
||||
|
||||
if (preKeys != null) {
|
||||
for (PreKey preKey : preKeys) {
|
||||
for (KeyRecord preKey : preKeys) {
|
||||
if (!preKey.isLastResort()) {
|
||||
removeKey(preKey.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (preKeys != null) return Optional.of(new UnstructuredPreKeyList(preKeys));
|
||||
if (preKeys != null) return Optional.of(preKeys);
|
||||
else return Optional.absent();
|
||||
}
|
||||
|
||||
|
@ -120,16 +121,16 @@ public abstract class Keys {
|
|||
public static class PreKeyBinderFactory implements BinderFactory {
|
||||
@Override
|
||||
public Binder build(Annotation annotation) {
|
||||
return new Binder<PreKeyBinder, PreKey>() {
|
||||
return new Binder<PreKeyBinder, KeyRecord>() {
|
||||
@Override
|
||||
public void bind(SQLStatement<?> sql, PreKeyBinder accountBinder, PreKey preKey)
|
||||
public void bind(SQLStatement<?> sql, PreKeyBinder accountBinder, KeyRecord record)
|
||||
{
|
||||
sql.bind("id", preKey.getId());
|
||||
sql.bind("number", preKey.getNumber());
|
||||
sql.bind("device_id", preKey.getDeviceId());
|
||||
sql.bind("key_id", preKey.getKeyId());
|
||||
sql.bind("public_key", preKey.getPublicKey());
|
||||
sql.bind("last_resort", preKey.isLastResort() ? 1 : 0);
|
||||
sql.bind("id", record.getId());
|
||||
sql.bind("number", record.getNumber());
|
||||
sql.bind("device_id", record.getDeviceId());
|
||||
sql.bind("key_id", record.getKeyId());
|
||||
sql.bind("public_key", record.getPublicKey());
|
||||
sql.bind("last_resort", record.isLastResort() ? 1 : 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -137,14 +138,14 @@ public abstract class Keys {
|
|||
}
|
||||
|
||||
|
||||
public static class PreKeyMapper implements ResultSetMapper<PreKey> {
|
||||
public static class PreKeyMapper implements ResultSetMapper<KeyRecord> {
|
||||
@Override
|
||||
public PreKey map(int i, ResultSet resultSet, StatementContext statementContext)
|
||||
public KeyRecord map(int i, ResultSet resultSet, StatementContext statementContext)
|
||||
throws SQLException
|
||||
{
|
||||
return new PreKey(resultSet.getLong("id"), resultSet.getString("number"), resultSet.getLong("device_id"),
|
||||
resultSet.getLong("key_id"), resultSet.getString("public_key"),
|
||||
resultSet.getInt("last_resort") == 1);
|
||||
return new KeyRecord(resultSet.getLong("id"), resultSet.getString("number"),
|
||||
resultSet.getLong("device_id"), resultSet.getLong("key_id"),
|
||||
resultSet.getString("public_key"), resultSet.getInt("last_resort") == 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ public class FederatedControllerTest {
|
|||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthenticator())
|
||||
.addResource(new FederationController(accountsManager,
|
||||
null, null,
|
||||
null, null, null,
|
||||
messageController))
|
||||
.build();
|
||||
|
||||
|
@ -61,12 +61,12 @@ public class FederatedControllerTest {
|
|||
@Before
|
||||
public void setup() throws Exception {
|
||||
List<Device> singleDeviceList = new LinkedList<Device>() {{
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 111));
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 111, null));
|
||||
}};
|
||||
|
||||
List<Device> multiDeviceList = new LinkedList<Device>() {{
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222));
|
||||
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333));
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222, null));
|
||||
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333, null));
|
||||
}};
|
||||
|
||||
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, false, singleDeviceList);
|
||||
|
|
|
@ -6,18 +6,22 @@ import org.junit.Before;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyList;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyStatus;
|
||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeysControllerV1;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeysControllerV2;
|
||||
import org.whispersystems.textsecuregcm.entities.DeviceKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyCount;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyResponseV1;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyResponseV2;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyStateV1;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyStateV2;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyV1;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyV2;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.KeyRecord;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
|
||||
|
@ -36,10 +40,18 @@ public class KeyControllerTest {
|
|||
|
||||
private static int SAMPLE_REGISTRATION_ID = 999;
|
||||
private static int SAMPLE_REGISTRATION_ID2 = 1002;
|
||||
private static int SAMPLE_REGISTRATION_ID4 = 1555;
|
||||
|
||||
private final KeyRecord SAMPLE_KEY = new KeyRecord(1, EXISTS_NUMBER, Device.MASTER_ID, 1234, "test1", false);
|
||||
private final KeyRecord SAMPLE_KEY2 = new KeyRecord(2, EXISTS_NUMBER, 2, 5667, "test3", false );
|
||||
private final KeyRecord SAMPLE_KEY3 = new KeyRecord(3, EXISTS_NUMBER, 3, 334, "test5", false );
|
||||
private final KeyRecord SAMPLE_KEY4 = new KeyRecord(4, EXISTS_NUMBER, 4, 336, "test6", false );
|
||||
|
||||
|
||||
private final DeviceKey SAMPLE_DEVICE_KEY = new DeviceKey(1111, "foofoo", "sig11");
|
||||
private final DeviceKey SAMPLE_DEVICE_KEY2 = new DeviceKey(2222, "foobar", "sig22");
|
||||
private final DeviceKey SAMPLE_DEVICE_KEY3 = new DeviceKey(3333, "barfoo", "sig33");
|
||||
|
||||
private final PreKey SAMPLE_KEY = new PreKey(1, EXISTS_NUMBER, Device.MASTER_ID, 1234, "test1", "test2", false);
|
||||
private final PreKey SAMPLE_KEY2 = new PreKey(2, EXISTS_NUMBER, 2, 5667, "test3", "test4,", false );
|
||||
private final PreKey SAMPLE_KEY3 = new PreKey(3, EXISTS_NUMBER, 3, 334, "test5", "test6", false );
|
||||
private final Keys keys = mock(Keys.class );
|
||||
private final AccountsManager accounts = mock(AccountsManager.class);
|
||||
private final Account existsAccount = mock(Account.class );
|
||||
|
@ -50,25 +62,47 @@ public class KeyControllerTest {
|
|||
@Rule
|
||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthenticator())
|
||||
.addResource(new KeysController(rateLimiters, keys, accounts, null))
|
||||
.addResource(new KeysControllerV1(rateLimiters, keys, accounts, null))
|
||||
.addResource(new KeysControllerV2(rateLimiters, keys, accounts, null))
|
||||
.build();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Device sampleDevice = mock(Device.class );
|
||||
Device sampleDevice2 = mock(Device.class);
|
||||
Device sampleDevice3 = mock(Device.class);
|
||||
final Device sampleDevice = mock(Device.class );
|
||||
final Device sampleDevice2 = mock(Device.class);
|
||||
final Device sampleDevice3 = mock(Device.class);
|
||||
final Device sampleDevice4 = mock(Device.class);
|
||||
|
||||
List<Device> allDevices = new LinkedList<Device>() {{
|
||||
add(sampleDevice);
|
||||
add(sampleDevice2);
|
||||
add(sampleDevice3);
|
||||
add(sampleDevice4);
|
||||
}};
|
||||
|
||||
when(sampleDevice.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID);
|
||||
when(sampleDevice2.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID2);
|
||||
when(sampleDevice3.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID2);
|
||||
when(sampleDevice4.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID4);
|
||||
when(sampleDevice.isActive()).thenReturn(true);
|
||||
when(sampleDevice2.isActive()).thenReturn(true);
|
||||
when(sampleDevice3.isActive()).thenReturn(false);
|
||||
when(sampleDevice4.isActive()).thenReturn(true);
|
||||
when(sampleDevice.getDeviceKey()).thenReturn(SAMPLE_DEVICE_KEY);
|
||||
when(sampleDevice2.getDeviceKey()).thenReturn(SAMPLE_DEVICE_KEY2);
|
||||
when(sampleDevice3.getDeviceKey()).thenReturn(SAMPLE_DEVICE_KEY3);
|
||||
when(sampleDevice4.getDeviceKey()).thenReturn(null);
|
||||
when(sampleDevice.getId()).thenReturn(1L);
|
||||
when(sampleDevice2.getId()).thenReturn(2L);
|
||||
when(sampleDevice3.getId()).thenReturn(3L);
|
||||
when(sampleDevice4.getId()).thenReturn(4L);
|
||||
|
||||
when(existsAccount.getDevice(1L)).thenReturn(Optional.of(sampleDevice));
|
||||
when(existsAccount.getDevice(2L)).thenReturn(Optional.of(sampleDevice2));
|
||||
when(existsAccount.getDevice(3L)).thenReturn(Optional.of(sampleDevice3));
|
||||
when(existsAccount.getDevice(4L)).thenReturn(Optional.of(sampleDevice4));
|
||||
when(existsAccount.getDevice(22L)).thenReturn(Optional.<Device>absent());
|
||||
when(existsAccount.getDevices()).thenReturn(allDevices);
|
||||
when(existsAccount.isActive()).thenReturn(true);
|
||||
when(existsAccount.getIdentityKey()).thenReturn("existsidentitykey");
|
||||
|
||||
|
@ -77,86 +111,155 @@ public class KeyControllerTest {
|
|||
|
||||
when(rateLimiters.getPreKeysLimiter()).thenReturn(rateLimiter);
|
||||
|
||||
when(keys.get(eq(EXISTS_NUMBER), eq(1L))).thenAnswer(new Answer<Optional<UnstructuredPreKeyList>>() {
|
||||
@Override
|
||||
public Optional<UnstructuredPreKeyList> answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
return Optional.of(new UnstructuredPreKeyList(cloneKey(SAMPLE_KEY)));
|
||||
}
|
||||
});
|
||||
List<KeyRecord> singleDevice = new LinkedList<>();
|
||||
singleDevice.add(SAMPLE_KEY);
|
||||
when(keys.get(eq(EXISTS_NUMBER), eq(1L))).thenReturn(Optional.of(singleDevice));
|
||||
|
||||
when(keys.get(eq(NOT_EXISTS_NUMBER), eq(1L))).thenReturn(Optional.<UnstructuredPreKeyList>absent());
|
||||
when(keys.get(eq(NOT_EXISTS_NUMBER), eq(1L))).thenReturn(Optional.<List<KeyRecord>>absent());
|
||||
|
||||
when(keys.get(EXISTS_NUMBER)).thenAnswer(new Answer<Optional<UnstructuredPreKeyList>>() {
|
||||
@Override
|
||||
public Optional<UnstructuredPreKeyList> answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
List<PreKey> allKeys = new LinkedList<>();
|
||||
allKeys.add(cloneKey(SAMPLE_KEY));
|
||||
allKeys.add(cloneKey(SAMPLE_KEY2));
|
||||
allKeys.add(cloneKey(SAMPLE_KEY3));
|
||||
|
||||
return Optional.of(new UnstructuredPreKeyList(allKeys));
|
||||
}
|
||||
});
|
||||
List<KeyRecord> multiDevice = new LinkedList<>();
|
||||
multiDevice.add(SAMPLE_KEY);
|
||||
multiDevice.add(SAMPLE_KEY2);
|
||||
multiDevice.add(SAMPLE_KEY3);
|
||||
multiDevice.add(SAMPLE_KEY4);
|
||||
when(keys.get(EXISTS_NUMBER)).thenReturn(Optional.of(multiDevice));
|
||||
|
||||
when(keys.getCount(eq(AuthHelper.VALID_NUMBER), eq(1L))).thenReturn(5);
|
||||
|
||||
when(AuthHelper.VALID_DEVICE.getDeviceKey()).thenReturn(new DeviceKey(89898, "zoofarb", "sigvalid"));
|
||||
when(AuthHelper.VALID_ACCOUNT.getIdentityKey()).thenReturn(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validKeyStatusTest() throws Exception {
|
||||
PreKeyStatus result = resources.client().resource("/v1/keys")
|
||||
public void validKeyStatusTestV1() throws Exception {
|
||||
PreKeyCount result = resources.client().resource("/v1/keys")
|
||||
.header("Authorization",
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(PreKeyStatus.class);
|
||||
.get(PreKeyCount.class);
|
||||
|
||||
assertThat(result.getCount() == 4);
|
||||
|
||||
verify(keys).getCount(eq(AuthHelper.VALID_NUMBER), eq(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validKeyStatusTestV2() throws Exception {
|
||||
PreKeyCount result = resources.client().resource("/v2/keys")
|
||||
.header("Authorization",
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(PreKeyCount.class);
|
||||
|
||||
assertThat(result.getCount() == 4);
|
||||
|
||||
verify(keys).getCount(eq(AuthHelper.VALID_NUMBER), eq(1L));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void validLegacyRequestTest() throws Exception {
|
||||
PreKey result = resources.client().resource(String.format("/v1/keys/%s", EXISTS_NUMBER))
|
||||
PreKeyV1 result = resources.client().resource(String.format("/v1/keys/%s", EXISTS_NUMBER))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(PreKey.class);
|
||||
.get(PreKeyV1.class);
|
||||
|
||||
assertThat(result.getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId());
|
||||
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||
|
||||
assertThat(result.getId() == 0);
|
||||
assertThat(result.getNumber() == null);
|
||||
|
||||
verify(keys).get(eq(EXISTS_NUMBER), eq(1L));
|
||||
verifyNoMoreInteractions(keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validMultiRequestTest() throws Exception {
|
||||
UnstructuredPreKeyList results = resources.client().resource(String.format("/v1/keys/%s/*", EXISTS_NUMBER))
|
||||
public void validSingleRequestTestV2() throws Exception {
|
||||
PreKeyResponseV2 result = resources.client().resource(String.format("/v2/keys/%s/1", EXISTS_NUMBER))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(PreKeyResponseV2.class);
|
||||
|
||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||
assertThat(result.getDevices().size()).isEqualTo(1);
|
||||
assertThat(result.getDevices().get(0).getPreKey().getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId());
|
||||
assertThat(result.getDevices().get(0).getPreKey().getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
||||
assertThat(result.getDevices().get(0).getDeviceKey()).isEqualTo(existsAccount.getDevice(1).get().getDeviceKey());
|
||||
|
||||
verify(keys).get(eq(EXISTS_NUMBER), eq(1L));
|
||||
verifyNoMoreInteractions(keys);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void validMultiRequestTestV1() throws Exception {
|
||||
PreKeyResponseV1 results = resources.client().resource(String.format("/v1/keys/%s/*", EXISTS_NUMBER))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(UnstructuredPreKeyList.class);
|
||||
.get(PreKeyResponseV1.class);
|
||||
|
||||
assertThat(results.getKeys().size()).isEqualTo(2);
|
||||
assertThat(results.getKeys().size()).isEqualTo(3);
|
||||
|
||||
PreKey result = results.getKeys().get(0);
|
||||
PreKeyV1 result = results.getKeys().get(0);
|
||||
|
||||
assertThat(result.getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId());
|
||||
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||
assertThat(result.getRegistrationId()).isEqualTo(SAMPLE_REGISTRATION_ID);
|
||||
|
||||
assertThat(result.getId() == 0);
|
||||
assertThat(result.getNumber() == null);
|
||||
|
||||
result = results.getKeys().get(1);
|
||||
assertThat(result.getKeyId()).isEqualTo(SAMPLE_KEY2.getKeyId());
|
||||
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY2.getPublicKey());
|
||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||
assertThat(result.getRegistrationId()).isEqualTo(SAMPLE_REGISTRATION_ID2);
|
||||
|
||||
assertThat(result.getId() == 0);
|
||||
assertThat(result.getNumber() == null);
|
||||
result = results.getKeys().get(2);
|
||||
assertThat(result.getKeyId()).isEqualTo(SAMPLE_KEY4.getKeyId());
|
||||
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY4.getPublicKey());
|
||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||
assertThat(result.getRegistrationId()).isEqualTo(SAMPLE_REGISTRATION_ID4);
|
||||
|
||||
verify(keys).get(eq(EXISTS_NUMBER));
|
||||
verifyNoMoreInteractions(keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validMultiRequestTestV2() throws Exception {
|
||||
PreKeyResponseV2 results = resources.client().resource(String.format("/v2/keys/%s/*", EXISTS_NUMBER))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(PreKeyResponseV2.class);
|
||||
|
||||
assertThat(results.getDevices().size()).isEqualTo(3);
|
||||
assertThat(results.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||
|
||||
PreKeyV2 deviceKey = results.getDevices().get(0).getDeviceKey();
|
||||
PreKeyV2 preKey = results.getDevices().get(0).getPreKey();
|
||||
long registrationId = results.getDevices().get(0).getRegistrationId();
|
||||
long deviceId = results.getDevices().get(0).getDeviceId();
|
||||
|
||||
assertThat(preKey.getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId());
|
||||
assertThat(preKey.getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
||||
assertThat(registrationId).isEqualTo(SAMPLE_REGISTRATION_ID);
|
||||
assertThat(deviceKey.getKeyId()).isEqualTo(SAMPLE_DEVICE_KEY.getKeyId());
|
||||
assertThat(deviceKey.getPublicKey()).isEqualTo(SAMPLE_DEVICE_KEY.getPublicKey());
|
||||
assertThat(deviceId).isEqualTo(1);
|
||||
|
||||
deviceKey = results.getDevices().get(1).getDeviceKey();
|
||||
preKey = results.getDevices().get(1).getPreKey();
|
||||
registrationId = results.getDevices().get(1).getRegistrationId();
|
||||
deviceId = results.getDevices().get(1).getDeviceId();
|
||||
|
||||
assertThat(preKey.getKeyId()).isEqualTo(SAMPLE_KEY2.getKeyId());
|
||||
assertThat(preKey.getPublicKey()).isEqualTo(SAMPLE_KEY2.getPublicKey());
|
||||
assertThat(registrationId).isEqualTo(SAMPLE_REGISTRATION_ID2);
|
||||
assertThat(deviceKey.getKeyId()).isEqualTo(SAMPLE_DEVICE_KEY2.getKeyId());
|
||||
assertThat(deviceKey.getPublicKey()).isEqualTo(SAMPLE_DEVICE_KEY2.getPublicKey());
|
||||
assertThat(deviceId).isEqualTo(2);
|
||||
|
||||
deviceKey = results.getDevices().get(2).getDeviceKey();
|
||||
preKey = results.getDevices().get(2).getPreKey();
|
||||
registrationId = results.getDevices().get(2).getRegistrationId();
|
||||
deviceId = results.getDevices().get(2).getDeviceId();
|
||||
|
||||
assertThat(preKey.getKeyId()).isEqualTo(SAMPLE_KEY4.getKeyId());
|
||||
assertThat(preKey.getPublicKey()).isEqualTo(SAMPLE_KEY4.getPublicKey());
|
||||
assertThat(registrationId).isEqualTo(SAMPLE_REGISTRATION_ID4);
|
||||
assertThat(deviceKey).isNull();
|
||||
assertThat(deviceId).isEqualTo(4);
|
||||
|
||||
verify(keys).get(eq(EXISTS_NUMBER));
|
||||
verifyNoMoreInteractions(keys);
|
||||
|
@ -164,7 +267,7 @@ public class KeyControllerTest {
|
|||
|
||||
|
||||
@Test
|
||||
public void invalidRequestTest() throws Exception {
|
||||
public void invalidRequestTestV1() throws Exception {
|
||||
ClientResponse response = resources.client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(ClientResponse.class);
|
||||
|
@ -173,7 +276,25 @@ public class KeyControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void unauthorizedRequestTest() throws Exception {
|
||||
public void invalidRequestTestV2() throws Exception {
|
||||
ClientResponse response = resources.client().resource(String.format("/v2/keys/%s", NOT_EXISTS_NUMBER))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(ClientResponse.class);
|
||||
|
||||
assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void anotherInvalidRequestTestV2() throws Exception {
|
||||
ClientResponse response = resources.client().resource(String.format("/v2/keys/%s/22", EXISTS_NUMBER))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(ClientResponse.class);
|
||||
|
||||
assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unauthorizedRequestTestV1() throws Exception {
|
||||
ClientResponse response =
|
||||
resources.client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD))
|
||||
|
@ -189,15 +310,31 @@ public class KeyControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void putKeysTest() throws Exception {
|
||||
final PreKey newKey = new PreKey(0, null, 1L, 31337, "foobar", "foobarbaz", false);
|
||||
final PreKey lastResortKey = new PreKey(0, null, 1L, 0xFFFFFF, "fooz", "foobarbaz", false);
|
||||
public void unauthorizedRequestTestV2() throws Exception {
|
||||
ClientResponse response =
|
||||
resources.client().resource(String.format("/v2/keys/%s/1", EXISTS_NUMBER))
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD))
|
||||
.get(ClientResponse.class);
|
||||
|
||||
List<PreKey> preKeys = new LinkedList<PreKey>() {{
|
||||
assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(401);
|
||||
|
||||
response =
|
||||
resources.client().resource(String.format("/v2/keys/%s/1", EXISTS_NUMBER))
|
||||
.get(ClientResponse.class);
|
||||
|
||||
assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(401);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putKeysTestV1() throws Exception {
|
||||
final PreKeyV1 newKey = new PreKeyV1(1L, 31337, "foobar", "foobarbaz");
|
||||
final PreKeyV1 lastResortKey = new PreKeyV1(1L, 0xFFFFFF, "fooz", "foobarbaz");
|
||||
|
||||
List<PreKeyV1> preKeys = new LinkedList<PreKeyV1>() {{
|
||||
add(newKey);
|
||||
}};
|
||||
|
||||
PreKeyList preKeyList = new PreKeyList();
|
||||
PreKeyStateV1 preKeyList = new PreKeyStateV1();
|
||||
preKeyList.setKeys(preKeys);
|
||||
preKeyList.setLastResortKey(lastResortKey);
|
||||
|
||||
|
@ -209,11 +346,11 @@ public class KeyControllerTest {
|
|||
|
||||
assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(204);
|
||||
|
||||
ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class );
|
||||
ArgumentCaptor<PreKey> lastResortCaptor = ArgumentCaptor.forClass(PreKey.class);
|
||||
ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class );
|
||||
ArgumentCaptor<PreKeyV1> lastResortCaptor = ArgumentCaptor.forClass(PreKeyV1.class);
|
||||
verify(keys).store(eq(AuthHelper.VALID_NUMBER), eq(1L), listCaptor.capture(), lastResortCaptor.capture());
|
||||
|
||||
List<PreKey> capturedList = listCaptor.getValue();
|
||||
List<PreKeyV1> capturedList = listCaptor.getValue();
|
||||
assertThat(capturedList.size() == 1);
|
||||
assertThat(capturedList.get(0).getIdentityKey().equals("foobarbaz"));
|
||||
assertThat(capturedList.get(0).getKeyId() == 31337);
|
||||
|
@ -226,9 +363,39 @@ public class KeyControllerTest {
|
|||
verify(accounts).update(AuthHelper.VALID_ACCOUNT);
|
||||
}
|
||||
|
||||
private PreKey cloneKey(PreKey source) {
|
||||
return new PreKey(source.getId(), source.getNumber(), source.getDeviceId(), source.getKeyId(),
|
||||
source.getPublicKey(), source.getIdentityKey(), source.isLastResort());
|
||||
@Test
|
||||
public void putKeysTestV2() throws Exception {
|
||||
final PreKeyV2 preKey = new PreKeyV2(31337, "foobar");
|
||||
final PreKeyV2 lastResortKey = new PreKeyV2(31339, "barbar");
|
||||
final DeviceKey deviceKey = new DeviceKey(31338, "foobaz", "myvalidsig");
|
||||
final String identityKey = "barbar";
|
||||
|
||||
List<PreKeyV2> preKeys = new LinkedList<PreKeyV2>() {{
|
||||
add(preKey);
|
||||
}};
|
||||
|
||||
PreKeyStateV2 preKeyState = new PreKeyStateV2(identityKey, deviceKey, preKeys, lastResortKey);
|
||||
|
||||
ClientResponse response =
|
||||
resources.client().resource("/v2/keys")
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.put(ClientResponse.class, preKeyState);
|
||||
|
||||
assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(204);
|
||||
|
||||
ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(keys).store(eq(AuthHelper.VALID_NUMBER), eq(1L), listCaptor.capture(), eq(lastResortKey));
|
||||
|
||||
List<PreKeyV2> capturedList = listCaptor.getValue();
|
||||
assertThat(capturedList.size() == 1);
|
||||
assertThat(capturedList.get(0).getKeyId() == 31337);
|
||||
assertThat(capturedList.get(0).getPublicKey().equals("foobar"));
|
||||
|
||||
verify(AuthHelper.VALID_ACCOUNT).setIdentityKey(eq("barbar"));
|
||||
verify(AuthHelper.VALID_DEVICE).setDeviceKey(eq(deviceKey));
|
||||
verify(accounts).update(AuthHelper.VALID_ACCOUNT);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
import com.google.common.base.Optional;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||
|
@ -59,12 +58,12 @@ public class MessageControllerTest {
|
|||
@Before
|
||||
public void setup() throws Exception {
|
||||
List<Device> singleDeviceList = new LinkedList<Device>() {{
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 111));
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 111, null));
|
||||
}};
|
||||
|
||||
List<Device> multiDeviceList = new LinkedList<Device>() {{
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222));
|
||||
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333));
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222, null));
|
||||
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333, null));
|
||||
}};
|
||||
|
||||
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, false, singleDeviceList);
|
||||
|
|
|
@ -2,7 +2,8 @@ package org.whispersystems.textsecuregcm.tests.entities;
|
|||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyV1;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyV2;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
|
@ -13,8 +14,8 @@ import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.*;
|
|||
public class PreKeyTest {
|
||||
|
||||
@Test
|
||||
public void serializeToJSON() throws Exception {
|
||||
PreKey preKey = new PreKey(1, "+14152222222", 1, 1234, "test", "identityTest", false);
|
||||
public void serializeToJSONV1() throws Exception {
|
||||
PreKeyV1 preKey = new PreKeyV1(1, 1234, "test", "identityTest");
|
||||
preKey.setRegistrationId(987);
|
||||
|
||||
assertThat("Basic Contact Serialization works",
|
||||
|
@ -23,7 +24,7 @@ public class PreKeyTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void deserializeFromJSON() throws Exception {
|
||||
public void deserializeFromJSONV() throws Exception {
|
||||
ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"),
|
||||
"whisper", true);
|
||||
|
||||
|
@ -32,4 +33,13 @@ public class PreKeyTest {
|
|||
is(contact));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeToJSONV2() throws Exception {
|
||||
PreKeyV2 preKey = new PreKeyV2(1234, "test");
|
||||
|
||||
assertThat("PreKeyV2 Serialization works",
|
||||
asJson(preKey),
|
||||
is(equalTo(jsonFixture("fixtures/prekey_v2.json"))));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"keyId" : 1234,
|
||||
"publicKey" : "test"
|
||||
}
|
Loading…
Reference in New Issue