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.DeviceController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.FederationController;
|
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.controllers.MessageController;
|
||||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||||
|
@ -147,7 +148,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||||
accountsManager);
|
accountsManager);
|
||||||
|
|
||||||
AttachmentController attachmentController = new AttachmentController(rateLimiters, federatedClientManager, urlSigner);
|
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);
|
MessageController messageController = new MessageController(rateLimiters, pushSender, accountsManager, federatedClientManager);
|
||||||
|
|
||||||
environment.jersey().register(new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(config.getFederationConfiguration()),
|
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 AccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender));
|
||||||
environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, rateLimiters));
|
environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, rateLimiters));
|
||||||
environment.jersey().register(new DirectoryController(rateLimiters, directory));
|
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(attachmentController);
|
||||||
environment.jersey().register(keysController);
|
environment.jersey().register(keysControllerV1);
|
||||||
|
environment.jersey().register(keysControllerV2);
|
||||||
environment.jersey().register(messageController);
|
environment.jersey().register(messageController);
|
||||||
|
|
||||||
if (config.getWebsocketConfiguration().isEnabled()) {
|
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.ClientContact;
|
||||||
import org.whispersystems.textsecuregcm.entities.ClientContacts;
|
import org.whispersystems.textsecuregcm.entities.ClientContacts;
|
||||||
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
|
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
|
||||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
import org.whispersystems.textsecuregcm.entities.PreKeyResponseV2;
|
||||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
import org.whispersystems.textsecuregcm.entities.PreKeyV1;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.PreKeyResponseV1;
|
||||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||||
import org.whispersystems.textsecuregcm.federation.NonLimitedAccount;
|
import org.whispersystems.textsecuregcm.federation.NonLimitedAccount;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
|
@ -46,7 +47,7 @@ import java.util.List;
|
||||||
|
|
||||||
import io.dropwizard.auth.Auth;
|
import io.dropwizard.auth.Auth;
|
||||||
|
|
||||||
@Path("/v1/federation")
|
@Path("/")
|
||||||
public class FederationController {
|
public class FederationController {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(FederationController.class);
|
private final Logger logger = LoggerFactory.getLogger(FederationController.class);
|
||||||
|
@ -55,23 +56,26 @@ public class FederationController {
|
||||||
|
|
||||||
private final AccountsManager accounts;
|
private final AccountsManager accounts;
|
||||||
private final AttachmentController attachmentController;
|
private final AttachmentController attachmentController;
|
||||||
private final KeysController keysController;
|
private final KeysControllerV1 keysControllerV1;
|
||||||
|
private final KeysControllerV2 keysControllerV2;
|
||||||
private final MessageController messageController;
|
private final MessageController messageController;
|
||||||
|
|
||||||
public FederationController(AccountsManager accounts,
|
public FederationController(AccountsManager accounts,
|
||||||
AttachmentController attachmentController,
|
AttachmentController attachmentController,
|
||||||
KeysController keysController,
|
KeysControllerV1 keysControllerV1,
|
||||||
MessageController messageController)
|
KeysControllerV2 keysControllerV2,
|
||||||
|
MessageController messageController)
|
||||||
{
|
{
|
||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
this.attachmentController = attachmentController;
|
this.attachmentController = attachmentController;
|
||||||
this.keysController = keysController;
|
this.keysControllerV1 = keysControllerV1;
|
||||||
|
this.keysControllerV2 = keysControllerV2;
|
||||||
this.messageController = messageController;
|
this.messageController = messageController;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
@GET
|
@GET
|
||||||
@Path("/attachment/{attachmentId}")
|
@Path("/v1/federation/attachment/{attachmentId}")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public AttachmentUri getSignedAttachmentUri(@Auth FederatedPeer peer,
|
public AttachmentUri getSignedAttachmentUri(@Auth FederatedPeer peer,
|
||||||
@PathParam("attachmentId") long attachmentId)
|
@PathParam("attachmentId") long attachmentId)
|
||||||
|
@ -83,14 +87,15 @@ public class FederationController {
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
@GET
|
@GET
|
||||||
@Path("/key/{number}")
|
@Path("/v1/federation/key/{number}")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public PreKey getKey(@Auth FederatedPeer peer,
|
public Optional<PreKeyV1> getKey(@Auth FederatedPeer peer,
|
||||||
@PathParam("number") String number)
|
@PathParam("number") String number)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
try {
|
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) {
|
} catch (RateLimitExceededException e) {
|
||||||
logger.warn("Rate limiting on federated channel", e);
|
logger.warn("Rate limiting on federated channel", e);
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
|
@ -99,16 +104,34 @@ public class FederationController {
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
@GET
|
@GET
|
||||||
@Path("/key/{number}/{device}")
|
@Path("/v1/federation/key/{number}/{device}")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public UnstructuredPreKeyList getKeys(@Auth FederatedPeer peer,
|
public Optional<PreKeyResponseV1> getKeysV1(@Auth FederatedPeer peer,
|
||||||
@PathParam("number") String number,
|
@PathParam("number") String number,
|
||||||
@PathParam("device") String device)
|
@PathParam("device") String device)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return keysController.getDeviceKey(new NonLimitedAccount("Unknown", -1, peer.getName()),
|
return keysControllerV1.getDeviceKey(new NonLimitedAccount("Unknown", -1, peer.getName()),
|
||||||
number, device, Optional.<String>absent());
|
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) {
|
} catch (RateLimitExceededException e) {
|
||||||
logger.warn("Rate limiting on federated channel", e);
|
logger.warn("Rate limiting on federated channel", e);
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
|
@ -117,7 +140,7 @@ public class FederationController {
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
@PUT
|
@PUT
|
||||||
@Path("/messages/{source}/{sourceDeviceId}/{destination}")
|
@Path("/v1/federation/messages/{source}/{sourceDeviceId}/{destination}")
|
||||||
public void sendMessages(@Auth FederatedPeer peer,
|
public void sendMessages(@Auth FederatedPeer peer,
|
||||||
@PathParam("source") String source,
|
@PathParam("source") String source,
|
||||||
@PathParam("sourceDeviceId") long sourceDeviceId,
|
@PathParam("sourceDeviceId") long sourceDeviceId,
|
||||||
|
@ -136,7 +159,7 @@ public class FederationController {
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
@GET
|
@GET
|
||||||
@Path("/user_count")
|
@Path("/v1/federation/user_count")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public AccountCount getUserCount(@Auth FederatedPeer peer) {
|
public AccountCount getUserCount(@Auth FederatedPeer peer) {
|
||||||
return new AccountCount((int)accounts.getCount());
|
return new AccountCount((int)accounts.getCount());
|
||||||
|
@ -144,7 +167,7 @@ public class FederationController {
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
@GET
|
@GET
|
||||||
@Path("/user_tokens/{offset}")
|
@Path("/v1/federation/user_tokens/{offset}")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public ClientContacts getUserTokens(@Auth FederatedPeer peer,
|
public ClientContacts getUserTokens(@Auth FederatedPeer peer,
|
||||||
@PathParam("offset") int offset)
|
@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
|
* 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
|
* 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.codahale.metrics.annotation.Timed;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import org.slf4j.Logger;
|
import org.whispersystems.textsecuregcm.entities.PreKeyCount;
|
||||||
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.federation.FederatedClientManager;
|
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.KeyRecord;
|
||||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
|
||||||
import javax.ws.rs.Consumes;
|
|
||||||
import javax.ws.rs.GET;
|
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.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.dropwizard.auth.Auth;
|
import io.dropwizard.auth.Auth;
|
||||||
|
|
||||||
@Path("/v1/keys")
|
|
||||||
public class KeysController {
|
public class KeysController {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(KeysController.class);
|
protected final RateLimiters rateLimiters;
|
||||||
|
protected final Keys keys;
|
||||||
private final RateLimiters rateLimiters;
|
protected final AccountsManager accounts;
|
||||||
private final Keys keys;
|
protected final FederatedClientManager federatedClientManager;
|
||||||
private final AccountsManager accounts;
|
|
||||||
private final FederatedClientManager federatedClientManager;
|
|
||||||
|
|
||||||
public KeysController(RateLimiters rateLimiters, Keys keys, AccountsManager accounts,
|
public KeysController(RateLimiters rateLimiters, Keys keys, AccountsManager accounts,
|
||||||
FederatedClientManager federatedClientManager)
|
FederatedClientManager federatedClientManager)
|
||||||
|
@ -67,119 +52,65 @@ public class KeysController {
|
||||||
this.federatedClientManager = federatedClientManager;
|
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
|
@Timed
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@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());
|
int count = keys.getCount(account.getNumber(), account.getAuthenticatedDevice().get().getId());
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
count = count - 1;
|
count = count - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PreKeyStatus(count);
|
return new PreKeyCount(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Timed
|
protected TargetKeys getLocalKeys(String number, String deviceIdSelector)
|
||||||
@GET
|
throws NoSuchUserException
|
||||||
@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
|
|
||||||
{
|
{
|
||||||
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);
|
Optional<Account> destination = accounts.get(number);
|
||||||
|
|
||||||
if (!destination.isPresent() || !destination.get().isActive()) {
|
if (!destination.isPresent() || !destination.get().isActive()) {
|
||||||
return Optional.absent();
|
throw new NoSuchUserException("Target account is inactive");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (deviceIdSelector.equals("*")) {
|
if (deviceIdSelector.equals("*")) {
|
||||||
Optional<UnstructuredPreKeyList> preKeys = keys.get(number);
|
Optional<List<KeyRecord>> preKeys = keys.get(number);
|
||||||
return getActiveKeys(destination.get(), preKeys);
|
return new TargetKeys(destination.get(), preKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
long deviceId = Long.parseLong(deviceIdSelector);
|
long deviceId = Long.parseLong(deviceIdSelector);
|
||||||
Optional<Device> targetDevice = destination.get().getDevice(deviceId);
|
Optional<Device> targetDevice = destination.get().getDevice(deviceId);
|
||||||
|
|
||||||
if (!targetDevice.isPresent() || !targetDevice.get().isActive()) {
|
if (!targetDevice.isPresent() || !targetDevice.get().isActive()) {
|
||||||
return Optional.absent();
|
throw new NoSuchUserException("Target device is inactive.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<UnstructuredPreKeyList> preKeys = keys.get(number, deviceId);
|
Optional<List<KeyRecord>> preKeys = keys.get(number, deviceId);
|
||||||
return getActiveKeys(destination.get(), preKeys);
|
return new TargetKeys(destination.get(), preKeys);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new WebApplicationException(Response.status(422).build());
|
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()) {
|
public TargetKeys(Account destination, Optional<List<KeyRecord>> keys) {
|
||||||
Optional<Device> device = destination.getDevice(preKey.getDeviceId());
|
this.destination = destination;
|
||||||
|
this.keys = keys;
|
||||||
if (device.isPresent() && device.get().isActive()) {
|
|
||||||
preKey.setRegistrationId(device.get().getRegistrationId());
|
|
||||||
preKey.setIdentityKey(destination.getIdentityKey());
|
|
||||||
filteredKeys.add(preKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filteredKeys.isEmpty()) return Optional.absent();
|
public Optional<List<KeyRecord>> getKeys() {
|
||||||
else return Optional.of(new UnstructuredPreKeyList(filteredKeys));
|
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;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
public class PreKeyStatus {
|
public class PreKeyCount {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private int count;
|
private int count;
|
||||||
|
|
||||||
public PreKeyStatus(int count) {
|
public PreKeyCount(int count) {
|
||||||
this.count = count;
|
this.count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PreKeyStatus() {}
|
public PreKeyCount() {}
|
||||||
|
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return count;
|
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.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.hibernate.validator.constraints.NotEmpty;
|
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
@ -26,36 +25,36 @@ import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class UnstructuredPreKeyList {
|
public class PreKeyResponseV1 {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@NotNull
|
@NotNull
|
||||||
@Valid
|
@Valid
|
||||||
private List<PreKey> keys;
|
private List<PreKeyV1> keys;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public UnstructuredPreKeyList() {}
|
public PreKeyResponseV1() {}
|
||||||
|
|
||||||
public UnstructuredPreKeyList(PreKey preKey) {
|
public PreKeyResponseV1(PreKeyV1 preKey) {
|
||||||
this.keys = new LinkedList<PreKey>();
|
this.keys = new LinkedList<>();
|
||||||
this.keys.add(preKey);
|
this.keys.add(preKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UnstructuredPreKeyList(List<PreKey> preKeys) {
|
public PreKeyResponseV1(List<PreKeyV1> preKeys) {
|
||||||
this.keys = preKeys;
|
this.keys = preKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PreKey> getKeys() {
|
public List<PreKeyV1> getKeys() {
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (!(o instanceof UnstructuredPreKeyList) ||
|
if (!(o instanceof PreKeyResponseV1) ||
|
||||||
((UnstructuredPreKeyList) o).keys.size() != keys.size())
|
((PreKeyResponseV1) o).keys.size() != keys.size())
|
||||||
return false;
|
return false;
|
||||||
Iterator<PreKey> otherKeys = ((UnstructuredPreKeyList) o).keys.iterator();
|
Iterator<PreKeyV1> otherKeys = ((PreKeyResponseV1) o).keys.iterator();
|
||||||
for (PreKey key : keys) {
|
for (PreKeyV1 key : keys) {
|
||||||
if (!otherKeys.next().equals(key))
|
if (!otherKeys.next().equals(key))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +63,7 @@ public class UnstructuredPreKeyList {
|
||||||
|
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int ret = 0xFBA4C795 * keys.size();
|
int ret = 0xFBA4C795 * keys.size();
|
||||||
for (PreKey key : keys)
|
for (PreKeyV1 key : keys)
|
||||||
ret ^= key.getPublicKey().hashCode();
|
ret ^= key.getPublicKey().hashCode();
|
||||||
return ret;
|
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
|
* 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
|
* 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.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.hibernate.validator.constraints.NotEmpty;
|
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PreKeyList {
|
public class PreKeyStateV1 {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@NotNull
|
@NotNull
|
||||||
@Valid
|
@Valid
|
||||||
private PreKey lastResortKey;
|
private PreKeyV1 lastResortKey;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@NotNull
|
@NotNull
|
||||||
@Valid
|
@Valid
|
||||||
private List<PreKey> keys;
|
private List<PreKeyV1> keys;
|
||||||
|
|
||||||
public List<PreKey> getKeys() {
|
public List<PreKeyV1> getKeys() {
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setKeys(List<PreKey> keys) {
|
public void setKeys(List<PreKeyV1> keys) {
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PreKey getLastResortKey() {
|
public PreKeyV1 getLastResortKey() {
|
||||||
return lastResortKey;
|
return lastResortKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setLastResortKey(PreKey lastResortKey) {
|
public void setLastResortKey(PreKeyV1 lastResortKey) {
|
||||||
this.lastResortKey = 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
|
* 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
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -17,23 +17,14 @@
|
||||||
package org.whispersystems.textsecuregcm.entities;
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import javax.xml.bind.annotation.XmlTransient;
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||||
public class PreKey {
|
public class PreKeyV1 implements PreKeyBase {
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private long id;
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private String number;
|
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private long deviceId;
|
private long deviceId;
|
||||||
|
@ -50,89 +41,43 @@ public class PreKey {
|
||||||
@NotNull
|
@NotNull
|
||||||
private String identityKey;
|
private String identityKey;
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private boolean lastResort;
|
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private int registrationId;
|
private int registrationId;
|
||||||
|
|
||||||
public PreKey() {}
|
public PreKeyV1() {}
|
||||||
|
|
||||||
public PreKey(long id, String number, long deviceId, long keyId,
|
public PreKeyV1(long deviceId, long keyId, String publicKey, String identityKey, int registrationId)
|
||||||
String publicKey, boolean lastResort)
|
|
||||||
{
|
{
|
||||||
this.id = id;
|
this.deviceId = deviceId;
|
||||||
this.number = number;
|
this.keyId = keyId;
|
||||||
this.deviceId = deviceId;
|
this.publicKey = publicKey;
|
||||||
this.keyId = keyId;
|
this.identityKey = identityKey;
|
||||||
this.publicKey = publicKey;
|
this.registrationId = registrationId;
|
||||||
this.lastResort = lastResort;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public PreKey(long id, String number, long deviceId, long keyId,
|
public PreKeyV1(long deviceId, long keyId, String publicKey, String identityKey)
|
||||||
String publicKey, String identityKey, boolean lastResort)
|
|
||||||
{
|
{
|
||||||
this.id = id;
|
|
||||||
this.number = number;
|
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
this.keyId = keyId;
|
this.keyId = keyId;
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.identityKey = identityKey;
|
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() {
|
public String getPublicKey() {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPublicKey(String publicKey) {
|
@Override
|
||||||
this.publicKey = publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getKeyId() {
|
public long getKeyId() {
|
||||||
return keyId;
|
return keyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeyId(long keyId) {
|
|
||||||
this.keyId = keyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIdentityKey() {
|
public String getIdentityKey() {
|
||||||
return identityKey;
|
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) {
|
public void setDeviceId(long deviceId) {
|
||||||
this.deviceId = 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.ClientContact;
|
||||||
import org.whispersystems.textsecuregcm.entities.ClientContacts;
|
import org.whispersystems.textsecuregcm.entities.ClientContacts;
|
||||||
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
|
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 org.whispersystems.textsecuregcm.util.Base64;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
@ -62,11 +63,12 @@ public class FederatedClient {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(FederatedClient.class);
|
private final Logger logger = LoggerFactory.getLogger(FederatedClient.class);
|
||||||
|
|
||||||
private static final String USER_COUNT_PATH = "/v1/federation/user_count";
|
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 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 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 PREKEY_PATH_DEVICE_V1 = "/v1/federation/key/%s/%s";
|
||||||
private static final String ATTACHMENT_URI_PATH = "/v1/federation/attachment/%d";
|
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 FederatedPeer peer;
|
||||||
private final Client client;
|
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 {
|
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)
|
ClientResponse response = resource.accept(MediaType.APPLICATION_JSON)
|
||||||
.header("Authorization", authorizationHeader)
|
.header("Authorization", authorizationHeader)
|
||||||
|
@ -119,7 +121,7 @@ public class FederatedClient {
|
||||||
throw new WebApplicationException(clientResponseToResponse(response));
|
throw new WebApplicationException(clientResponseToResponse(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.of(response.getEntity(UnstructuredPreKeyList.class));
|
return Optional.of(response.getEntity(PreKeyResponseV1.class));
|
||||||
|
|
||||||
} catch (UniformInterfaceException | ClientHandlerException e) {
|
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||||
logger.warn("PreKey", 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() {
|
public int getUserCount() {
|
||||||
try {
|
try {
|
||||||
WebResource resource = client.resource(peer.getUrl()).path(USER_COUNT_PATH);
|
WebResource resource = client.resource(peer.getUrl()).path(USER_COUNT_PATH);
|
||||||
|
|
|
@ -40,6 +40,6 @@ public class NonLimitedAccount extends Account {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Device> getAuthenticatedDevice() {
|
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 class Account implements Serializable {
|
||||||
|
|
||||||
public static final int MEMCACHE_VERION = 3;
|
public static final int MEMCACHE_VERION = 4;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private long id;
|
private long id;
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
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 org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
@ -51,11 +53,15 @@ public class Device implements Serializable {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private int registrationId;
|
private int registrationId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private DeviceKey deviceKey;
|
||||||
|
|
||||||
public Device() {}
|
public Device() {}
|
||||||
|
|
||||||
public Device(long id, String authToken, String salt,
|
public Device(long id, String authToken, String salt,
|
||||||
String signalingKey, String gcmId, String apnId,
|
String signalingKey, String gcmId, String apnId,
|
||||||
boolean fetchesMessages, int registrationId)
|
boolean fetchesMessages, int registrationId,
|
||||||
|
DeviceKey deviceKey)
|
||||||
{
|
{
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.authToken = authToken;
|
this.authToken = authToken;
|
||||||
|
@ -65,6 +71,7 @@ public class Device implements Serializable {
|
||||||
this.apnId = apnId;
|
this.apnId = apnId;
|
||||||
this.fetchesMessages = fetchesMessages;
|
this.fetchesMessages = fetchesMessages;
|
||||||
this.registrationId = registrationId;
|
this.registrationId = registrationId;
|
||||||
|
this.deviceKey = deviceKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getApnId() {
|
public String getApnId() {
|
||||||
|
@ -131,4 +138,12 @@ public class Device implements Serializable {
|
||||||
public void setRegistrationId(int registrationId) {
|
public void setRegistrationId(int registrationId) {
|
||||||
this.registrationId = 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.Transaction;
|
||||||
import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
|
import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
|
||||||
import org.skife.jdbi.v2.tweak.ResultSetMapper;
|
import org.skife.jdbi.v2.tweak.ResultSetMapper;
|
||||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
import org.whispersystems.textsecuregcm.entities.PreKeyBase;
|
||||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
import org.whispersystems.textsecuregcm.entities.PreKeyV1;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.PreKeyV2;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
|
@ -40,6 +41,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class Keys {
|
public abstract class Keys {
|
||||||
|
@ -51,65 +53,64 @@ public abstract class Keys {
|
||||||
abstract void removeKey(@Bind("id") long id);
|
abstract void removeKey(@Bind("id") long id);
|
||||||
|
|
||||||
@SqlBatch("INSERT INTO keys (number, device_id, key_id, public_key, last_resort) VALUES " +
|
@SqlBatch("INSERT INTO keys (number, device_id, key_id, public_key, last_resort) VALUES " +
|
||||||
"(:number, :device_id, :key_id, :public_key, :last_resort)")
|
"(:number, :device_id, :key_id, :public_key, :last_resort)")
|
||||||
abstract void append(@PreKeyBinder List<PreKey> preKeys);
|
abstract void append(@PreKeyBinder List<KeyRecord> 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);
|
|
||||||
|
|
||||||
@SqlQuery("SELECT * FROM keys WHERE number = :number AND device_id = :device_id ORDER BY key_id ASC FOR UPDATE")
|
@SqlQuery("SELECT * FROM keys WHERE number = :number AND device_id = :device_id ORDER BY key_id ASC FOR UPDATE")
|
||||||
@Mapper(PreKeyMapper.class)
|
@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")
|
@SqlQuery("SELECT DISTINCT ON (number, device_id) * FROM keys WHERE number = :number ORDER BY number, device_id, key_id ASC")
|
||||||
@Mapper(PreKeyMapper.class)
|
@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")
|
@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);
|
public abstract int getCount(@Bind("number") String number, @Bind("device_id") long deviceId);
|
||||||
|
|
||||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||||
public void store(String number, long deviceId, List<PreKey> keys, PreKey lastResortKey) {
|
public void store(String number, long deviceId, List<? extends PreKeyBase> keys, PreKeyBase lastResortKey) {
|
||||||
for (PreKey key : keys) {
|
List<KeyRecord> records = new LinkedList<>();
|
||||||
key.setNumber(number);
|
|
||||||
key.setDeviceId(deviceId);
|
for (PreKeyBase key : keys) {
|
||||||
|
records.add(new KeyRecord(0, number, deviceId, key.getKeyId(), key.getPublicKey(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
lastResortKey.setNumber(number);
|
records.add(new KeyRecord(0, number, deviceId, lastResortKey.getKeyId(),
|
||||||
lastResortKey.setDeviceId(deviceId);
|
lastResortKey.getPublicKey(), true));
|
||||||
lastResortKey.setLastResort(true);
|
|
||||||
|
|
||||||
removeKeys(number, deviceId);
|
removeKeys(number, deviceId);
|
||||||
append(keys);
|
append(records);
|
||||||
append(lastResortKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||||
public Optional<UnstructuredPreKeyList> get(String number, long deviceId) {
|
public Optional<List<KeyRecord>> get(String number, long deviceId) {
|
||||||
PreKey preKey = retrieveFirst(number, deviceId);
|
final KeyRecord record = retrieveFirst(number, deviceId);
|
||||||
|
|
||||||
if (preKey != null && !preKey.isLastResort()) {
|
if (record != null && !record.isLastResort()) {
|
||||||
removeKey(preKey.getId());
|
removeKey(record.getId());
|
||||||
|
} else if (record == null) {
|
||||||
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preKey != null) return Optional.of(new UnstructuredPreKeyList(preKey));
|
List<KeyRecord> results = new LinkedList<>();
|
||||||
else return Optional.absent();
|
results.add(record);
|
||||||
|
|
||||||
|
return Optional.of(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||||
public Optional<UnstructuredPreKeyList> get(String number) {
|
public Optional<List<KeyRecord>> get(String number) {
|
||||||
List<PreKey> preKeys = retrieveFirst(number);
|
List<KeyRecord> preKeys = retrieveFirst(number);
|
||||||
|
|
||||||
if (preKeys != null) {
|
if (preKeys != null) {
|
||||||
for (PreKey preKey : preKeys) {
|
for (KeyRecord preKey : preKeys) {
|
||||||
if (!preKey.isLastResort()) {
|
if (!preKey.isLastResort()) {
|
||||||
removeKey(preKey.getId());
|
removeKey(preKey.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preKeys != null) return Optional.of(new UnstructuredPreKeyList(preKeys));
|
if (preKeys != null) return Optional.of(preKeys);
|
||||||
else return Optional.absent();
|
else return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,16 +121,16 @@ public abstract class Keys {
|
||||||
public static class PreKeyBinderFactory implements BinderFactory {
|
public static class PreKeyBinderFactory implements BinderFactory {
|
||||||
@Override
|
@Override
|
||||||
public Binder build(Annotation annotation) {
|
public Binder build(Annotation annotation) {
|
||||||
return new Binder<PreKeyBinder, PreKey>() {
|
return new Binder<PreKeyBinder, KeyRecord>() {
|
||||||
@Override
|
@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("id", record.getId());
|
||||||
sql.bind("number", preKey.getNumber());
|
sql.bind("number", record.getNumber());
|
||||||
sql.bind("device_id", preKey.getDeviceId());
|
sql.bind("device_id", record.getDeviceId());
|
||||||
sql.bind("key_id", preKey.getKeyId());
|
sql.bind("key_id", record.getKeyId());
|
||||||
sql.bind("public_key", preKey.getPublicKey());
|
sql.bind("public_key", record.getPublicKey());
|
||||||
sql.bind("last_resort", preKey.isLastResort() ? 1 : 0);
|
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
|
@Override
|
||||||
public PreKey map(int i, ResultSet resultSet, StatementContext statementContext)
|
public KeyRecord map(int i, ResultSet resultSet, StatementContext statementContext)
|
||||||
throws SQLException
|
throws SQLException
|
||||||
{
|
{
|
||||||
return new PreKey(resultSet.getLong("id"), resultSet.getString("number"), resultSet.getLong("device_id"),
|
return new KeyRecord(resultSet.getLong("id"), resultSet.getString("number"),
|
||||||
resultSet.getLong("key_id"), resultSet.getString("public_key"),
|
resultSet.getLong("device_id"), resultSet.getLong("key_id"),
|
||||||
resultSet.getInt("last_resort") == 1);
|
resultSet.getString("public_key"), resultSet.getInt("last_resort") == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class FederatedControllerTest {
|
||||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||||
.addProvider(AuthHelper.getAuthenticator())
|
.addProvider(AuthHelper.getAuthenticator())
|
||||||
.addResource(new FederationController(accountsManager,
|
.addResource(new FederationController(accountsManager,
|
||||||
null, null,
|
null, null, null,
|
||||||
messageController))
|
messageController))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -61,12 +61,12 @@ public class FederatedControllerTest {
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
List<Device> singleDeviceList = new LinkedList<Device>() {{
|
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>() {{
|
List<Device> multiDeviceList = new LinkedList<Device>() {{
|
||||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222));
|
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222, null));
|
||||||
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333));
|
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333, null));
|
||||||
}};
|
}};
|
||||||
|
|
||||||
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, false, singleDeviceList);
|
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, false, singleDeviceList);
|
||||||
|
|
|
@ -6,18 +6,22 @@ import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.whispersystems.textsecuregcm.controllers.KeysControllerV1;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.whispersystems.textsecuregcm.controllers.KeysControllerV2;
|
||||||
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
import org.whispersystems.textsecuregcm.entities.DeviceKey;
|
||||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
import org.whispersystems.textsecuregcm.entities.PreKeyCount;
|
||||||
import org.whispersystems.textsecuregcm.entities.PreKeyList;
|
import org.whispersystems.textsecuregcm.entities.PreKeyResponseV1;
|
||||||
import org.whispersystems.textsecuregcm.entities.PreKeyStatus;
|
import org.whispersystems.textsecuregcm.entities.PreKeyResponseV2;
|
||||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
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.RateLimiter;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.KeyRecord;
|
||||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
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_ID = 999;
|
||||||
private static int SAMPLE_REGISTRATION_ID2 = 1002;
|
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 Keys keys = mock(Keys.class );
|
||||||
private final AccountsManager accounts = mock(AccountsManager.class);
|
private final AccountsManager accounts = mock(AccountsManager.class);
|
||||||
private final Account existsAccount = mock(Account.class );
|
private final Account existsAccount = mock(Account.class );
|
||||||
|
@ -50,25 +62,47 @@ public class KeyControllerTest {
|
||||||
@Rule
|
@Rule
|
||||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||||
.addProvider(AuthHelper.getAuthenticator())
|
.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();
|
.build();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
Device sampleDevice = mock(Device.class );
|
final Device sampleDevice = mock(Device.class );
|
||||||
Device sampleDevice2 = mock(Device.class);
|
final Device sampleDevice2 = mock(Device.class);
|
||||||
Device sampleDevice3 = 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(sampleDevice.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID);
|
||||||
when(sampleDevice2.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID2);
|
when(sampleDevice2.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID2);
|
||||||
when(sampleDevice3.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(sampleDevice.isActive()).thenReturn(true);
|
||||||
when(sampleDevice2.isActive()).thenReturn(true);
|
when(sampleDevice2.isActive()).thenReturn(true);
|
||||||
when(sampleDevice3.isActive()).thenReturn(false);
|
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(1L)).thenReturn(Optional.of(sampleDevice));
|
||||||
when(existsAccount.getDevice(2L)).thenReturn(Optional.of(sampleDevice2));
|
when(existsAccount.getDevice(2L)).thenReturn(Optional.of(sampleDevice2));
|
||||||
when(existsAccount.getDevice(3L)).thenReturn(Optional.of(sampleDevice3));
|
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.isActive()).thenReturn(true);
|
||||||
when(existsAccount.getIdentityKey()).thenReturn("existsidentitykey");
|
when(existsAccount.getIdentityKey()).thenReturn("existsidentitykey");
|
||||||
|
|
||||||
|
@ -77,86 +111,155 @@ public class KeyControllerTest {
|
||||||
|
|
||||||
when(rateLimiters.getPreKeysLimiter()).thenReturn(rateLimiter);
|
when(rateLimiters.getPreKeysLimiter()).thenReturn(rateLimiter);
|
||||||
|
|
||||||
when(keys.get(eq(EXISTS_NUMBER), eq(1L))).thenAnswer(new Answer<Optional<UnstructuredPreKeyList>>() {
|
List<KeyRecord> singleDevice = new LinkedList<>();
|
||||||
@Override
|
singleDevice.add(SAMPLE_KEY);
|
||||||
public Optional<UnstructuredPreKeyList> answer(InvocationOnMock invocationOnMock) throws Throwable {
|
when(keys.get(eq(EXISTS_NUMBER), eq(1L))).thenReturn(Optional.of(singleDevice));
|
||||||
return Optional.of(new UnstructuredPreKeyList(cloneKey(SAMPLE_KEY)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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>>() {
|
List<KeyRecord> multiDevice = new LinkedList<>();
|
||||||
@Override
|
multiDevice.add(SAMPLE_KEY);
|
||||||
public Optional<UnstructuredPreKeyList> answer(InvocationOnMock invocationOnMock) throws Throwable {
|
multiDevice.add(SAMPLE_KEY2);
|
||||||
List<PreKey> allKeys = new LinkedList<>();
|
multiDevice.add(SAMPLE_KEY3);
|
||||||
allKeys.add(cloneKey(SAMPLE_KEY));
|
multiDevice.add(SAMPLE_KEY4);
|
||||||
allKeys.add(cloneKey(SAMPLE_KEY2));
|
when(keys.get(EXISTS_NUMBER)).thenReturn(Optional.of(multiDevice));
|
||||||
allKeys.add(cloneKey(SAMPLE_KEY3));
|
|
||||||
|
|
||||||
return Optional.of(new UnstructuredPreKeyList(allKeys));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
when(keys.getCount(eq(AuthHelper.VALID_NUMBER), eq(1L))).thenReturn(5);
|
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);
|
when(AuthHelper.VALID_ACCOUNT.getIdentityKey()).thenReturn(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validKeyStatusTest() throws Exception {
|
public void validKeyStatusTestV1() throws Exception {
|
||||||
PreKeyStatus result = resources.client().resource("/v1/keys")
|
PreKeyCount result = resources.client().resource("/v1/keys")
|
||||||
.header("Authorization",
|
.header("Authorization",
|
||||||
AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||||
.get(PreKeyStatus.class);
|
.get(PreKeyCount.class);
|
||||||
|
|
||||||
assertThat(result.getCount() == 4);
|
assertThat(result.getCount() == 4);
|
||||||
|
|
||||||
verify(keys).getCount(eq(AuthHelper.VALID_NUMBER), eq(1L));
|
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
|
@Test
|
||||||
public void validLegacyRequestTest() throws Exception {
|
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))
|
.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.getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId());
|
||||||
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
||||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||||
|
|
||||||
assertThat(result.getId() == 0);
|
|
||||||
assertThat(result.getNumber() == null);
|
|
||||||
|
|
||||||
verify(keys).get(eq(EXISTS_NUMBER), eq(1L));
|
verify(keys).get(eq(EXISTS_NUMBER), eq(1L));
|
||||||
verifyNoMoreInteractions(keys);
|
verifyNoMoreInteractions(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validMultiRequestTest() throws Exception {
|
public void validSingleRequestTestV2() throws Exception {
|
||||||
UnstructuredPreKeyList results = resources.client().resource(String.format("/v1/keys/%s/*", EXISTS_NUMBER))
|
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))
|
.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.getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId());
|
||||||
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
||||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||||
assertThat(result.getRegistrationId()).isEqualTo(SAMPLE_REGISTRATION_ID);
|
assertThat(result.getRegistrationId()).isEqualTo(SAMPLE_REGISTRATION_ID);
|
||||||
|
|
||||||
assertThat(result.getId() == 0);
|
|
||||||
assertThat(result.getNumber() == null);
|
|
||||||
|
|
||||||
result = results.getKeys().get(1);
|
result = results.getKeys().get(1);
|
||||||
assertThat(result.getKeyId()).isEqualTo(SAMPLE_KEY2.getKeyId());
|
assertThat(result.getKeyId()).isEqualTo(SAMPLE_KEY2.getKeyId());
|
||||||
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY2.getPublicKey());
|
assertThat(result.getPublicKey()).isEqualTo(SAMPLE_KEY2.getPublicKey());
|
||||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||||
assertThat(result.getRegistrationId()).isEqualTo(SAMPLE_REGISTRATION_ID2);
|
assertThat(result.getRegistrationId()).isEqualTo(SAMPLE_REGISTRATION_ID2);
|
||||||
|
|
||||||
assertThat(result.getId() == 0);
|
result = results.getKeys().get(2);
|
||||||
assertThat(result.getNumber() == null);
|
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));
|
verify(keys).get(eq(EXISTS_NUMBER));
|
||||||
verifyNoMoreInteractions(keys);
|
verifyNoMoreInteractions(keys);
|
||||||
|
@ -164,7 +267,7 @@ public class KeyControllerTest {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void invalidRequestTest() throws Exception {
|
public void invalidRequestTestV1() throws Exception {
|
||||||
ClientResponse response = resources.client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER))
|
ClientResponse response = resources.client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER))
|
||||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||||
.get(ClientResponse.class);
|
.get(ClientResponse.class);
|
||||||
|
@ -173,7 +276,25 @@ public class KeyControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 =
|
ClientResponse response =
|
||||||
resources.client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER))
|
resources.client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER))
|
||||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD))
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD))
|
||||||
|
@ -189,15 +310,31 @@ public class KeyControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void putKeysTest() throws Exception {
|
public void unauthorizedRequestTestV2() throws Exception {
|
||||||
final PreKey newKey = new PreKey(0, null, 1L, 31337, "foobar", "foobarbaz", false);
|
ClientResponse response =
|
||||||
final PreKey lastResortKey = new PreKey(0, null, 1L, 0xFFFFFF, "fooz", "foobarbaz", false);
|
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);
|
add(newKey);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
PreKeyList preKeyList = new PreKeyList();
|
PreKeyStateV1 preKeyList = new PreKeyStateV1();
|
||||||
preKeyList.setKeys(preKeys);
|
preKeyList.setKeys(preKeys);
|
||||||
preKeyList.setLastResortKey(lastResortKey);
|
preKeyList.setLastResortKey(lastResortKey);
|
||||||
|
|
||||||
|
@ -209,11 +346,11 @@ public class KeyControllerTest {
|
||||||
|
|
||||||
assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(204);
|
assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(204);
|
||||||
|
|
||||||
ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class );
|
ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class );
|
||||||
ArgumentCaptor<PreKey> lastResortCaptor = ArgumentCaptor.forClass(PreKey.class);
|
ArgumentCaptor<PreKeyV1> lastResortCaptor = ArgumentCaptor.forClass(PreKeyV1.class);
|
||||||
verify(keys).store(eq(AuthHelper.VALID_NUMBER), eq(1L), listCaptor.capture(), lastResortCaptor.capture());
|
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.size() == 1);
|
||||||
assertThat(capturedList.get(0).getIdentityKey().equals("foobarbaz"));
|
assertThat(capturedList.get(0).getIdentityKey().equals("foobarbaz"));
|
||||||
assertThat(capturedList.get(0).getKeyId() == 31337);
|
assertThat(capturedList.get(0).getKeyId() == 31337);
|
||||||
|
@ -226,9 +363,39 @@ public class KeyControllerTest {
|
||||||
verify(accounts).update(AuthHelper.VALID_ACCOUNT);
|
verify(accounts).update(AuthHelper.VALID_ACCOUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PreKey cloneKey(PreKey source) {
|
@Test
|
||||||
return new PreKey(source.getId(), source.getNumber(), source.getDeviceId(), source.getKeyId(),
|
public void putKeysTestV2() throws Exception {
|
||||||
source.getPublicKey(), source.getIdentityKey(), source.isLastResort());
|
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.google.common.base.Optional;
|
||||||
import com.sun.jersey.api.client.ClientResponse;
|
import com.sun.jersey.api.client.ClientResponse;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.ClassRule;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||||
|
@ -59,12 +58,12 @@ public class MessageControllerTest {
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
List<Device> singleDeviceList = new LinkedList<Device>() {{
|
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>() {{
|
List<Device> multiDeviceList = new LinkedList<Device>() {{
|
||||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222));
|
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222, null));
|
||||||
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333));
|
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333, null));
|
||||||
}};
|
}};
|
||||||
|
|
||||||
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, false, singleDeviceList);
|
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.junit.Test;
|
||||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
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 org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
@ -13,8 +14,8 @@ import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.*;
|
||||||
public class PreKeyTest {
|
public class PreKeyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void serializeToJSON() throws Exception {
|
public void serializeToJSONV1() throws Exception {
|
||||||
PreKey preKey = new PreKey(1, "+14152222222", 1, 1234, "test", "identityTest", false);
|
PreKeyV1 preKey = new PreKeyV1(1, 1234, "test", "identityTest");
|
||||||
preKey.setRegistrationId(987);
|
preKey.setRegistrationId(987);
|
||||||
|
|
||||||
assertThat("Basic Contact Serialization works",
|
assertThat("Basic Contact Serialization works",
|
||||||
|
@ -23,7 +24,7 @@ public class PreKeyTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void deserializeFromJSON() throws Exception {
|
public void deserializeFromJSONV() throws Exception {
|
||||||
ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"),
|
ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"),
|
||||||
"whisper", true);
|
"whisper", true);
|
||||||
|
|
||||||
|
@ -32,4 +33,13 @@ public class PreKeyTest {
|
||||||
is(contact));
|
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