mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 09:20:29 +01:00
Support device transfers (SERVER-41, SERVER-42) (#32)
This change introduces a `transfer` device capability and account creation argument in support of the iOS device transfer effort.
This commit is contained in:
@@ -39,6 +39,7 @@ import org.whispersystems.textsecuregcm.storage.AbusiveHostRule;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PaymentAddress;
|
||||
import org.whispersystems.textsecuregcm.storage.PaymentAddressList;
|
||||
@@ -53,14 +54,18 @@ import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
@@ -83,6 +88,7 @@ public class AccountControllerTest {
|
||||
private static final String SENDER_PREAUTH = "+14157777777";
|
||||
private static final String SENDER_REG_LOCK = "+14158888888";
|
||||
private static final String SENDER_HAS_STORAGE = "+14159999999";
|
||||
private static final String SENDER_TRANSFER = "+14151111112";
|
||||
|
||||
private static final UUID SENDER_REG_LOCK_UUID = UUID.randomUUID();
|
||||
|
||||
@@ -114,6 +120,7 @@ public class AccountControllerTest {
|
||||
private Account senderPinAccount = mock(Account.class);
|
||||
private Account senderRegLockAccount = mock(Account.class);
|
||||
private Account senderHasStorage = mock(Account.class);
|
||||
private Account senderTransfer = mock(Account.class);
|
||||
private RecaptchaClient recaptchaClient = mock(RecaptchaClient.class);
|
||||
private GCMSender gcmSender = mock(GCMSender.class);
|
||||
private APNSender apnSender = mock(APNSender.class);
|
||||
@@ -180,6 +187,7 @@ public class AccountControllerTest {
|
||||
when(pendingAccountsManager.getCodeForNumber(SENDER_OVER_PIN)).thenReturn(Optional.of(new StoredVerificationCode("444444", System.currentTimeMillis(), null)));
|
||||
when(pendingAccountsManager.getCodeForNumber(SENDER_PREAUTH)).thenReturn(Optional.of(new StoredVerificationCode("555555", System.currentTimeMillis(), "validchallenge")));
|
||||
when(pendingAccountsManager.getCodeForNumber(SENDER_HAS_STORAGE)).thenReturn(Optional.of(new StoredVerificationCode("666666", System.currentTimeMillis(), null)));
|
||||
when(pendingAccountsManager.getCodeForNumber(SENDER_TRANSFER)).thenReturn(Optional.of(new StoredVerificationCode("1234", System.currentTimeMillis(), null)));
|
||||
|
||||
when(accountsManager.get(eq(SENDER_PIN))).thenReturn(Optional.of(senderPinAccount));
|
||||
when(accountsManager.get(eq(SENDER_REG_LOCK))).thenReturn(Optional.of(senderRegLockAccount));
|
||||
@@ -188,6 +196,7 @@ public class AccountControllerTest {
|
||||
when(accountsManager.get(eq(SENDER_OLD))).thenReturn(Optional.empty());
|
||||
when(accountsManager.get(eq(SENDER_PREAUTH))).thenReturn(Optional.empty());
|
||||
when(accountsManager.get(eq(SENDER_HAS_STORAGE))).thenReturn(Optional.of(senderHasStorage));
|
||||
when(accountsManager.get(eq(SENDER_TRANSFER))).thenReturn(Optional.of(senderTransfer));
|
||||
|
||||
when(usernamesManager.put(eq(AuthHelper.VALID_UUID), eq("n00bkiller"))).thenReturn(true);
|
||||
when(usernamesManager.put(eq(AuthHelper.VALID_UUID), eq("takenusername"))).thenReturn(false);
|
||||
@@ -747,6 +756,52 @@ public class AccountControllerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyTransferSupported() {
|
||||
when(senderTransfer.isTransferSupported()).thenReturn(true);
|
||||
|
||||
final Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "1234"))
|
||||
.queryParam("transfer", true)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER_TRANSFER, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 2222, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyTransferNotSupported() {
|
||||
when(senderTransfer.isTransferSupported()).thenReturn(false);
|
||||
|
||||
final Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "1234"))
|
||||
.queryParam("transfer", true)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER_TRANSFER, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 2222, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyTransferSupportedNotRequested() {
|
||||
when(senderTransfer.isTransferSupported()).thenReturn(true);
|
||||
|
||||
final Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "1234"))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER_TRANSFER, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 2222, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetPin() throws Exception {
|
||||
|
||||
@@ -85,13 +85,13 @@ public class MessageControllerTest {
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
Set<Device> singleDeviceList = new HashSet<Device>() {{
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true, true, true)));
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true, true, true, true)));
|
||||
}};
|
||||
|
||||
Set<Device> multiDeviceList = new HashSet<Device>() {{
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true, true, true)));
|
||||
add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true, true, true)));
|
||||
add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(false, false, false)));
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true, true, true, false)));
|
||||
add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true, true, true, false)));
|
||||
add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(false, false, false, false)));
|
||||
}};
|
||||
|
||||
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, SINGLE_DEVICE_UUID, singleDeviceList, "1234".getBytes());
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -49,15 +50,15 @@ public class AccountTest {
|
||||
when(oldSecondaryDevice.isEnabled()).thenReturn(false);
|
||||
when(oldSecondaryDevice.getId()).thenReturn(2L);
|
||||
|
||||
when(uuidCapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(true, true, true));
|
||||
when(uuidCapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(true, true, true, true));
|
||||
when(uuidCapableDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
|
||||
when(uuidCapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(uuidIncapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(false, false, false));
|
||||
when(uuidIncapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(false, false, false, false));
|
||||
when(uuidIncapableDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
|
||||
when(uuidIncapableDevice.isEnabled()).thenReturn(true);
|
||||
|
||||
when(uuidIncapableExpiredDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(false, false, false));
|
||||
when(uuidIncapableExpiredDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(false, false, false, false));
|
||||
when(uuidIncapableExpiredDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31));
|
||||
when(uuidIncapableExpiredDevice.isEnabled()).thenReturn(false);
|
||||
}
|
||||
@@ -117,4 +118,52 @@ public class AccountTest {
|
||||
assertTrue(uuidCapableWithExpiredIncapable.isUuidAddressingSupported());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsTransferSupported() {
|
||||
final Device transferCapableMasterDevice = mock(Device.class);
|
||||
final Device nonTransferCapableMasterDevice = mock(Device.class);
|
||||
final Device transferCapableLinkedDevice = mock(Device.class);
|
||||
|
||||
final Device.DeviceCapabilities transferCapabilities = mock(Device.DeviceCapabilities.class);
|
||||
final Device.DeviceCapabilities nonTransferCapabilities = mock(Device.DeviceCapabilities.class);
|
||||
|
||||
when(transferCapableMasterDevice.getId()).thenReturn(1L);
|
||||
when(transferCapableMasterDevice.isMaster()).thenReturn(true);
|
||||
when(transferCapableMasterDevice.getCapabilities()).thenReturn(transferCapabilities);
|
||||
|
||||
when(nonTransferCapableMasterDevice.getId()).thenReturn(1L);
|
||||
when(nonTransferCapableMasterDevice.isMaster()).thenReturn(true);
|
||||
when(nonTransferCapableMasterDevice.getCapabilities()).thenReturn(nonTransferCapabilities);
|
||||
|
||||
when(transferCapableLinkedDevice.getId()).thenReturn(2L);
|
||||
when(transferCapableLinkedDevice.isMaster()).thenReturn(false);
|
||||
when(transferCapableLinkedDevice.getCapabilities()).thenReturn(transferCapabilities);
|
||||
|
||||
when(transferCapabilities.isTransfer()).thenReturn(true);
|
||||
when(nonTransferCapabilities.isTransfer()).thenReturn(false);
|
||||
|
||||
{
|
||||
final Account transferableMasterAccount =
|
||||
new Account("+14152222222", UUID.randomUUID(), Collections.singleton(transferCapableMasterDevice), "1234".getBytes());
|
||||
|
||||
assertTrue(transferableMasterAccount.isTransferSupported());
|
||||
}
|
||||
|
||||
{
|
||||
final Account nonTransferableMasterAccount =
|
||||
new Account("+14152222222", UUID.randomUUID(), Collections.singleton(nonTransferCapableMasterDevice), "1234".getBytes());
|
||||
|
||||
assertFalse(nonTransferableMasterAccount.isTransferSupported());
|
||||
}
|
||||
|
||||
{
|
||||
final Account transferableLinkedAccount = new Account("+14152222222", UUID.randomUUID(), new HashSet<>() {{
|
||||
add(nonTransferCapableMasterDevice);
|
||||
add(transferCapableLinkedDevice);
|
||||
}}, "1234".getBytes());
|
||||
|
||||
assertFalse(transferableLinkedAccount.isTransferSupported());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ public class AccountsTest {
|
||||
private Device generateDevice(long id) {
|
||||
Random random = new Random(System.currentTimeMillis());
|
||||
SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(), "testSignature-" + random.nextInt());
|
||||
return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), "testSalt-" + random.nextInt(), null, "testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt() , 0, new Device.DeviceCapabilities(random.nextBoolean(), random.nextBoolean(), random.nextBoolean()));
|
||||
return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), "testSalt-" + random.nextInt(), null, "testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt() , 0, new Device.DeviceCapabilities(random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean()));
|
||||
}
|
||||
|
||||
private Account generateAccount(String number, UUID uuid) {
|
||||
|
||||
Reference in New Issue
Block a user