Write identity key into 'account' object.
This is the beginning of a migration to storing one identity key per account, instead of the braindead duplication we're doing now. Part one of a two-part deployment in the schema migration process.
This commit is contained in:
parent
f14c181840
commit
437eb8de37
|
@ -7,3 +7,5 @@ run.sh
|
||||||
local.yml
|
local.yml
|
||||||
config/production.yml
|
config/production.yml
|
||||||
config/federated.yml
|
config/federated.yml
|
||||||
|
config/staging.yml
|
||||||
|
.opsmanage
|
||||||
|
|
|
@ -71,6 +71,13 @@ public class KeysController {
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public void setKeys(@Auth Account account, @Valid PreKeyList preKeys) {
|
public void setKeys(@Auth Account account, @Valid PreKeyList preKeys) {
|
||||||
Device device = account.getAuthenticatedDevice().get();
|
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());
|
keys.store(account.getNumber(), device.getId(), preKeys.getKeys(), preKeys.getLastResortKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.whispersystems.textsecuregcm.entities;
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.hibernate.validator.constraints.NotEmpty;
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
@ -27,6 +28,7 @@ public class PreKeyList {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@Valid
|
||||||
private PreKey lastResortKey;
|
private PreKey lastResortKey;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@ -38,7 +40,17 @@ public class PreKeyList {
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setKeys(List<PreKey> keys) {
|
||||||
|
this.keys = keys;
|
||||||
|
}
|
||||||
|
|
||||||
public PreKey getLastResortKey() {
|
public PreKey getLastResortKey() {
|
||||||
return lastResortKey;
|
return lastResortKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setLastResortKey(PreKey lastResortKey) {
|
||||||
|
this.lastResortKey = lastResortKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import java.util.List;
|
||||||
|
|
||||||
public class Account implements Serializable {
|
public class Account implements Serializable {
|
||||||
|
|
||||||
public static final int MEMCACHE_VERION = 2;
|
public static final int MEMCACHE_VERION = 3;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private long id;
|
private long id;
|
||||||
|
@ -42,16 +42,14 @@ public class Account implements Serializable {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private List<Device> devices = new LinkedList<>();
|
private List<Device> devices = new LinkedList<>();
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private String identityKey;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private Optional<Device> authenticatedDevice;
|
private Optional<Device> authenticatedDevice;
|
||||||
|
|
||||||
public Account() {}
|
public Account() {}
|
||||||
|
|
||||||
public Account(String number, boolean supportsSms) {
|
|
||||||
this.number = number;
|
|
||||||
this.supportsSms = supportsSms;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public Account(String number, boolean supportsSms, List<Device> devices) {
|
public Account(String number, boolean supportsSms, List<Device> devices) {
|
||||||
this.number = number;
|
this.number = number;
|
||||||
|
@ -142,4 +140,12 @@ public class Account implements Serializable {
|
||||||
public Optional<String> getRelay() {
|
public Optional<String> getRelay() {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIdentityKey(String identityKey) {
|
||||||
|
this.identityKey = identityKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdentityKey() {
|
||||||
|
return identityKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ import com.google.common.base.Optional;
|
||||||
import com.sun.jersey.api.client.ClientResponse;
|
import com.sun.jersey.api.client.ClientResponse;
|
||||||
import com.yammer.dropwizard.testing.ResourceTest;
|
import com.yammer.dropwizard.testing.ResourceTest;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
||||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.PreKeyList;
|
||||||
import org.whispersystems.textsecuregcm.entities.PreKeyStatus;
|
import org.whispersystems.textsecuregcm.entities.PreKeyStatus;
|
||||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||||
|
@ -16,6 +18,7 @@ import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
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;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -32,9 +35,11 @@ public class KeyControllerTest extends ResourceTest {
|
||||||
|
|
||||||
private final PreKey SAMPLE_KEY = new PreKey(1, EXISTS_NUMBER, Device.MASTER_ID, 1234, "test1", "test2", false);
|
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_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 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 );
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUpResources() {
|
protected void setUpResources() {
|
||||||
|
@ -46,7 +51,6 @@ public class KeyControllerTest extends ResourceTest {
|
||||||
Device sampleDevice = mock(Device.class );
|
Device sampleDevice = mock(Device.class );
|
||||||
Device sampleDevice2 = mock(Device.class);
|
Device sampleDevice2 = mock(Device.class);
|
||||||
Device sampleDevice3 = mock(Device.class);
|
Device sampleDevice3 = mock(Device.class);
|
||||||
Account existsAccount = mock(Account.class);
|
|
||||||
|
|
||||||
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);
|
||||||
|
@ -76,6 +80,8 @@ public class KeyControllerTest extends ResourceTest {
|
||||||
when(keys.get(EXISTS_NUMBER)).thenReturn(Optional.of(new UnstructuredPreKeyList(allKeys)));
|
when(keys.get(EXISTS_NUMBER)).thenReturn(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_ACCOUNT.getIdentityKey()).thenReturn(null);
|
||||||
|
|
||||||
addResource(new KeysController(rateLimiters, keys, accounts, null));
|
addResource(new KeysController(rateLimiters, keys, accounts, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,4 +171,42 @@ public class KeyControllerTest extends ResourceTest {
|
||||||
assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(401);
|
assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putKeysTest() throws Exception {
|
||||||
|
final PreKey newKey = new PreKey(0, null, 1L, 31337, "foobar", "foobarbaz", false);
|
||||||
|
final PreKey lastResortKey = new PreKey(0, null, 1L, 0xFFFFFF, "fooz", "foobarbaz", false);
|
||||||
|
|
||||||
|
List<PreKey> preKeys = new LinkedList<PreKey>() {{
|
||||||
|
add(newKey);
|
||||||
|
}};
|
||||||
|
|
||||||
|
PreKeyList preKeyList = new PreKeyList();
|
||||||
|
preKeyList.setKeys(preKeys);
|
||||||
|
preKeyList.setLastResortKey(lastResortKey);
|
||||||
|
|
||||||
|
ClientResponse response =
|
||||||
|
client().resource("/v1/keys")
|
||||||
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||||
|
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||||
|
.put(ClientResponse.class, preKeyList);
|
||||||
|
|
||||||
|
assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(204);
|
||||||
|
|
||||||
|
ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class );
|
||||||
|
ArgumentCaptor<PreKey> lastResortCaptor = ArgumentCaptor.forClass(PreKey.class);
|
||||||
|
verify(keys).store(eq(AuthHelper.VALID_NUMBER), eq(1L), listCaptor.capture(), lastResortCaptor.capture());
|
||||||
|
|
||||||
|
List<PreKey> capturedList = listCaptor.getValue();
|
||||||
|
assertThat(capturedList.size() == 1);
|
||||||
|
assertThat(capturedList.get(0).getIdentityKey().equals("foobarbaz"));
|
||||||
|
assertThat(capturedList.get(0).getKeyId() == 31337);
|
||||||
|
assertThat(capturedList.get(0).getPublicKey().equals("foobar"));
|
||||||
|
|
||||||
|
assertThat(lastResortCaptor.getValue().getPublicKey().equals("fooz"));
|
||||||
|
assertThat(lastResortCaptor.getValue().getIdentityKey().equals("foobarbaz"));
|
||||||
|
|
||||||
|
verify(AuthHelper.VALID_ACCOUNT).setIdentityKey(eq("foobarbaz"));
|
||||||
|
verify(accounts).update(AuthHelper.VALID_ACCOUNT);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -27,20 +27,20 @@ public class AuthHelper {
|
||||||
public static final String INVVALID_NUMBER = "+14151111111";
|
public static final String INVVALID_NUMBER = "+14151111111";
|
||||||
public static final String INVALID_PASSWORD = "bar";
|
public static final String INVALID_PASSWORD = "bar";
|
||||||
|
|
||||||
public static MultiBasicAuthProvider<FederatedPeer, Account> getAuthenticator() {
|
public static AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class );
|
||||||
AccountsManager accounts = mock(AccountsManager.class );
|
public static Account VALID_ACCOUNT = mock(Account.class );
|
||||||
Account account = mock(Account.class );
|
public static Device VALID_DEVICE = mock(Device.class );
|
||||||
Device device = mock(Device.class );
|
public static AuthenticationCredentials VALID_CREDENTIALS = mock(AuthenticationCredentials.class);
|
||||||
AuthenticationCredentials credentials = mock(AuthenticationCredentials.class);
|
|
||||||
|
|
||||||
when(credentials.verify("foo")).thenReturn(true);
|
public static MultiBasicAuthProvider<FederatedPeer, Account> getAuthenticator() {
|
||||||
when(device.getAuthenticationCredentials()).thenReturn(credentials);
|
when(VALID_CREDENTIALS.verify("foo")).thenReturn(true);
|
||||||
when(device.getId()).thenReturn(1L);
|
when(VALID_DEVICE.getAuthenticationCredentials()).thenReturn(VALID_CREDENTIALS);
|
||||||
when(account.getDevice(anyLong())).thenReturn(Optional.of(device));
|
when(VALID_DEVICE.getId()).thenReturn(1L);
|
||||||
when(account.getNumber()).thenReturn(VALID_NUMBER);
|
when(VALID_ACCOUNT.getDevice(anyLong())).thenReturn(Optional.of(VALID_DEVICE));
|
||||||
when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device));
|
when(VALID_ACCOUNT.getNumber()).thenReturn(VALID_NUMBER);
|
||||||
when(account.getRelay()).thenReturn(Optional.<String>absent());
|
when(VALID_ACCOUNT.getAuthenticatedDevice()).thenReturn(Optional.of(VALID_DEVICE));
|
||||||
when(accounts.get(VALID_NUMBER)).thenReturn(Optional.of(account));
|
when(VALID_ACCOUNT.getRelay()).thenReturn(Optional.<String>absent());
|
||||||
|
when(ACCOUNTS_MANAGER.get(VALID_NUMBER)).thenReturn(Optional.of(VALID_ACCOUNT));
|
||||||
|
|
||||||
List<FederatedPeer> peer = new LinkedList<FederatedPeer>() {{
|
List<FederatedPeer> peer = new LinkedList<FederatedPeer>() {{
|
||||||
add(new FederatedPeer("cyanogen", "https://foo", "foofoo", "bazzzzz"));
|
add(new FederatedPeer("cyanogen", "https://foo", "foofoo", "bazzzzz"));
|
||||||
|
@ -51,7 +51,7 @@ public class AuthHelper {
|
||||||
|
|
||||||
return new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(federationConfiguration),
|
return new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(federationConfiguration),
|
||||||
FederatedPeer.class,
|
FederatedPeer.class,
|
||||||
new AccountAuthenticator(accounts),
|
new AccountAuthenticator(ACCOUNTS_MANAGER),
|
||||||
Account.class, "WhisperServer");
|
Account.class, "WhisperServer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue