mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 14:28:05 +01:00
new /v2/accounts endpoint to distribute PNI key material without changing phone number
This commit is contained in:
committed by
GitHub
parent
4fb89360ce
commit
47ad5779ad
@@ -323,13 +323,38 @@ class AccountControllerTest {
|
||||
final String pniIdentityKey = invocation.getArgument(2, String.class);
|
||||
|
||||
final UUID uuid = account.getUuid();
|
||||
final UUID pni = number.equals(account.getNumber()) ? account.getPhoneNumberIdentifier() : UUID.randomUUID();
|
||||
final List<Device> devices = account.getDevices();
|
||||
|
||||
final Account updatedAccount = mock(Account.class);
|
||||
when(updatedAccount.getUuid()).thenReturn(uuid);
|
||||
when(updatedAccount.getNumber()).thenReturn(number);
|
||||
when(updatedAccount.getPhoneNumberIdentityKey()).thenReturn(pniIdentityKey);
|
||||
when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(UUID.randomUUID());
|
||||
when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(pni);
|
||||
when(updatedAccount.getDevices()).thenReturn(devices);
|
||||
|
||||
for (long i = 1; i <= 3; i++) {
|
||||
final Optional<Device> d = account.getDevice(i);
|
||||
when(updatedAccount.getDevice(i)).thenReturn(d);
|
||||
}
|
||||
|
||||
return updatedAccount;
|
||||
});
|
||||
|
||||
when(changeNumberManager.updatePNIKeys(any(), any(), any(), any(), any())).thenAnswer((Answer<Account>) invocation -> {
|
||||
final Account account = invocation.getArgument(0, Account.class);
|
||||
final String pniIdentityKey = invocation.getArgument(1, String.class);
|
||||
|
||||
final String number = account.getNumber();
|
||||
final UUID uuid = account.getUuid();
|
||||
final UUID pni = account.getPhoneNumberIdentifier();
|
||||
final List<Device> devices = account.getDevices();
|
||||
|
||||
final Account updatedAccount = mock(Account.class);
|
||||
when(updatedAccount.getNumber()).thenReturn(number);
|
||||
when(updatedAccount.getUuid()).thenReturn(uuid);
|
||||
when(updatedAccount.getPhoneNumberIdentityKey()).thenReturn(pniIdentityKey);
|
||||
when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(pni);
|
||||
when(updatedAccount.getDevices()).thenReturn(devices);
|
||||
|
||||
for (long i = 1; i <= 3; i++) {
|
||||
@@ -1646,6 +1671,61 @@ class AccountControllerTest {
|
||||
assertThat(accountIdentityResponse.pni()).isNotEqualTo(AuthHelper.VALID_PNI);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangePhoneNumberSameNumberChangePrekeys() throws Exception {
|
||||
final String code = "987654";
|
||||
final String pniIdentityKey = "changed-pni-identity-key";
|
||||
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
Device device2 = mock(Device.class);
|
||||
when(device2.getId()).thenReturn(2L);
|
||||
when(device2.isEnabled()).thenReturn(true);
|
||||
when(device2.getRegistrationId()).thenReturn(2);
|
||||
|
||||
Device device3 = mock(Device.class);
|
||||
when(device3.getId()).thenReturn(3L);
|
||||
when(device3.isEnabled()).thenReturn(true);
|
||||
when(device3.getRegistrationId()).thenReturn(3);
|
||||
|
||||
when(AuthHelper.VALID_ACCOUNT.getDevices()).thenReturn(List.of(AuthHelper.VALID_DEVICE, device2, device3));
|
||||
when(AuthHelper.VALID_ACCOUNT.getDevice(2L)).thenReturn(Optional.of(device2));
|
||||
when(AuthHelper.VALID_ACCOUNT.getDevice(3L)).thenReturn(Optional.of(device3));
|
||||
|
||||
when(pendingAccountsManager.getCodeForNumber(AuthHelper.VALID_NUMBER)).thenReturn(
|
||||
Optional.of(new StoredVerificationCode(null, System.currentTimeMillis(), "push", sessionId)));
|
||||
|
||||
when(registrationServiceClient.checkVerificationCode(any(), any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(true));
|
||||
|
||||
var deviceMessages = List.of(
|
||||
new IncomingMessage(1, 2, 2, "content2"),
|
||||
new IncomingMessage(1, 3, 3, "content3"));
|
||||
var deviceKeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey(), 3L, new SignedPreKey());
|
||||
|
||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
||||
|
||||
final AccountIdentityResponse accountIdentityResponse =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/accounts/number")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new ChangePhoneNumberRequest(
|
||||
AuthHelper.VALID_NUMBER, code, null,
|
||||
pniIdentityKey, deviceMessages,
|
||||
deviceKeys,
|
||||
registrationIds),
|
||||
MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class);
|
||||
|
||||
verify(changeNumberManager).changeNumber(
|
||||
eq(AuthHelper.VALID_ACCOUNT), eq(AuthHelper.VALID_NUMBER), any(), any(), any(), any());
|
||||
verifyNoInteractions(rateLimiter);
|
||||
verifyNoInteractions(pendingAccountsManager);
|
||||
|
||||
assertThat(accountIdentityResponse.uuid()).isEqualTo(AuthHelper.VALID_UUID);
|
||||
assertThat(accountIdentityResponse.number()).isEqualTo(AuthHelper.VALID_NUMBER);
|
||||
assertThat(accountIdentityResponse.pni()).isEqualTo(AuthHelper.VALID_PNI);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetRegistrationLock() {
|
||||
Response response =
|
||||
|
||||
@@ -71,6 +71,7 @@ import org.whispersystems.textsecuregcm.entities.AccountDataReportResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.ChangeNumberRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.PhoneNumberDiscoverabilityRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
@@ -147,7 +148,11 @@ class AccountControllerV2Test {
|
||||
when(updatedAccount.getUuid()).thenReturn(uuid);
|
||||
when(updatedAccount.getNumber()).thenReturn(number);
|
||||
when(updatedAccount.getPhoneNumberIdentityKey()).thenReturn(pniIdentityKey);
|
||||
when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(UUID.randomUUID());
|
||||
if (number.equals(account.getNumber())) {
|
||||
when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(AuthHelper.VALID_PNI);
|
||||
} else {
|
||||
when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(UUID.randomUUID());
|
||||
}
|
||||
when(updatedAccount.getDevices()).thenReturn(devices);
|
||||
|
||||
for (long i = 1; i <= 3; i++) {
|
||||
@@ -187,6 +192,29 @@ class AccountControllerV2Test {
|
||||
assertNotEquals(AuthHelper.VALID_PNI, accountIdentityResponse.pni());
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeNumberSameNumber() throws Exception {
|
||||
final AccountIdentityResponse accountIdentityResponse =
|
||||
resources.getJerseyTest()
|
||||
.target("/v2/accounts/number")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION,
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(
|
||||
new ChangeNumberRequest(encodeSessionId("session"), null, AuthHelper.VALID_NUMBER, null,
|
||||
"pni-identity-key",
|
||||
Collections.emptyList(),
|
||||
Collections.emptyMap(), Collections.emptyMap()),
|
||||
MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class);
|
||||
|
||||
verify(changeNumberManager).changeNumber(eq(AuthHelper.VALID_ACCOUNT), eq(AuthHelper.VALID_NUMBER), any(), any(), any(),
|
||||
any());
|
||||
|
||||
assertEquals(AuthHelper.VALID_UUID, accountIdentityResponse.uuid());
|
||||
assertEquals(AuthHelper.VALID_NUMBER, accountIdentityResponse.number());
|
||||
assertEquals(AuthHelper.VALID_PNI, accountIdentityResponse.pni());
|
||||
}
|
||||
|
||||
@Test
|
||||
void unprocessableRequestJson() {
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
@@ -426,6 +454,144 @@ class AccountControllerV2Test {
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class PhoneNumberIdentityKeyDistribution {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
when(changeNumberManager.updatePNIKeys(any(), any(), any(), any(), any())).thenAnswer(
|
||||
(Answer<Account>) invocation -> {
|
||||
final Account account = invocation.getArgument(0, Account.class);
|
||||
final String pniIdentityKey = invocation.getArgument(1, String.class);
|
||||
|
||||
final UUID uuid = account.getUuid();
|
||||
final UUID pni = account.getPhoneNumberIdentifier();
|
||||
final String number = account.getNumber();
|
||||
final List<Device> devices = account.getDevices();
|
||||
|
||||
final Account updatedAccount = mock(Account.class);
|
||||
when(updatedAccount.getUuid()).thenReturn(uuid);
|
||||
when(updatedAccount.getNumber()).thenReturn(number);
|
||||
when(updatedAccount.getPhoneNumberIdentityKey()).thenReturn(pniIdentityKey);
|
||||
when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(pni);
|
||||
when(updatedAccount.getDevices()).thenReturn(devices);
|
||||
|
||||
for (long i = 1; i <= 3; i++) {
|
||||
final Optional<Device> d = account.getDevice(i);
|
||||
when(updatedAccount.getDevice(i)).thenReturn(d);
|
||||
}
|
||||
|
||||
return updatedAccount;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void pniKeyDistributionSuccess() throws Exception {
|
||||
when(AuthHelper.VALID_ACCOUNT.isPniSupported()).thenReturn(true);
|
||||
|
||||
final AccountIdentityResponse accountIdentityResponse =
|
||||
resources.getJerseyTest()
|
||||
.target("/v2/accounts/phone_number_identity_key_distribution")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION,
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.json(requestJson()), AccountIdentityResponse.class);
|
||||
|
||||
verify(changeNumberManager).updatePNIKeys(eq(AuthHelper.VALID_ACCOUNT), eq("pni-identity-key"), any(), any(), any());
|
||||
|
||||
assertEquals(AuthHelper.VALID_UUID, accountIdentityResponse.uuid());
|
||||
assertEquals(AuthHelper.VALID_NUMBER, accountIdentityResponse.number());
|
||||
assertEquals(AuthHelper.VALID_PNI, accountIdentityResponse.pni());
|
||||
}
|
||||
|
||||
@Test
|
||||
void unprocessableRequestJson() {
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v2/accounts/phone_number_identity_key_distribution")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION,
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD));
|
||||
try (Response response = request.put(Entity.json(unprocessableJson()))) {
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingBasicAuthorization() {
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v2/accounts/phone_number_identity_key_distribution")
|
||||
.request();
|
||||
try (Response response = request.put(Entity.json(requestJson()))) {
|
||||
assertEquals(401, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidBasicAuthorization() {
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v2/accounts/phone_number_identity_key_distribution")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic but-invalid");
|
||||
try (Response response = request.put(Entity.json(requestJson()))) {
|
||||
assertEquals(401, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidRequestBody() {
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v2/accounts/phone_number_identity_key_distribution")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION,
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD));
|
||||
try (Response response = request.put(Entity.json(invalidRequestJson()))) {
|
||||
assertEquals(422, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid request JSON for a {@link org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest}
|
||||
*/
|
||||
private static String requestJson() {
|
||||
return """
|
||||
{
|
||||
"pniIdentityKey": "pni-identity-key",
|
||||
"deviceMessages": [],
|
||||
"devicePniSignedPrekeys": {},
|
||||
"pniRegistrationIds": {}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* Request JSON in the shape of {@link org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest}, but that
|
||||
* fails validation
|
||||
*/
|
||||
private static String invalidRequestJson() {
|
||||
return """
|
||||
{
|
||||
"pniIdentityKey": null,
|
||||
"deviceMessages": [],
|
||||
"devicePniSignedPrekeys": {},
|
||||
"pniRegistrationIds": {}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* Request JSON that cannot be marshalled into
|
||||
* {@link org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest}
|
||||
*/
|
||||
private static String unprocessableJson() {
|
||||
return """
|
||||
{
|
||||
"pniIdentityKey": []
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class PhoneNumberDiscoverability {
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
@@ -39,6 +41,7 @@ import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -59,6 +62,7 @@ import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.DevicesHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
|
||||
|
||||
class AccountsManagerTest {
|
||||
@@ -694,6 +698,22 @@ class AccountsManagerTest {
|
||||
verify(keys, never()).delete(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangePhoneNumberSameNumberWithPNIData() throws InterruptedException, MismatchedDevicesException {
|
||||
final String number = "+14152222222";
|
||||
|
||||
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> accountsManager.changeNumber(
|
||||
account, number, "new-identity-key", Map.of(1L, new SignedPreKey()), Map.of(1L, 101)),
|
||||
"AccountsManager should not allow use of changeNumber with new PNI keys but without changing number");
|
||||
|
||||
verify(accounts, never()).update(any());
|
||||
verifyNoInteractions(deletedAccountsManager);
|
||||
verifyNoInteractions(directoryQueue);
|
||||
verifyNoInteractions(keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangePhoneNumberExistingAccount() throws InterruptedException, MismatchedDevicesException {
|
||||
doAnswer(invocation -> invocation.getArgument(2, BiFunction.class).apply(Optional.empty(), Optional.empty()))
|
||||
@@ -733,6 +753,45 @@ class AccountsManagerTest {
|
||||
assertThrows(AssertionError.class, () -> accountsManager.update(account, a -> a.setNumber(targetNumber, UUID.randomUUID())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPNIUpdate() throws InterruptedException, MismatchedDevicesException {
|
||||
final String number = "+14152222222";
|
||||
|
||||
List<Device> devices = List.of(DevicesHelper.createDevice(1L, 0L, 101), DevicesHelper.createDevice(2L, 0L, 102));
|
||||
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), devices, new byte[16]);
|
||||
Map<Long, SignedPreKey> newSignedKeys = Map.of(
|
||||
1L, new SignedPreKey(1L, "pub1", "sig1"),
|
||||
2L, new SignedPreKey(2L, "pub2", "sig2"));
|
||||
Map<Long, Integer> newRegistrationIds = Map.of(1L, 201, 2L, 202);
|
||||
|
||||
UUID oldUuid = account.getUuid();
|
||||
UUID oldPni = account.getPhoneNumberIdentifier();
|
||||
Map<Long, SignedPreKey> oldSignedPreKeys = account.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey));
|
||||
|
||||
final Account updatedAccount = accountsManager.updatePNIKeys(account, "new-pni-identity-key", newSignedKeys, newRegistrationIds);
|
||||
|
||||
// non-PNI stuff should not change
|
||||
assertEquals(oldUuid, updatedAccount.getUuid());
|
||||
assertEquals(number, updatedAccount.getNumber());
|
||||
assertEquals(oldPni, updatedAccount.getPhoneNumberIdentifier());
|
||||
assertEquals(null, updatedAccount.getIdentityKey());
|
||||
assertEquals(oldSignedPreKeys, updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey)));
|
||||
assertEquals(Map.of(1L, 101, 2L, 102),
|
||||
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getRegistrationId)));
|
||||
|
||||
// PNI stuff should
|
||||
assertEquals("new-pni-identity-key", updatedAccount.getPhoneNumberIdentityKey());
|
||||
assertEquals(newSignedKeys,
|
||||
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getPhoneNumberIdentitySignedPreKey)));
|
||||
assertEquals(newRegistrationIds,
|
||||
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, d -> d.getPhoneNumberIdentityRegistrationId().getAsInt())));
|
||||
|
||||
verify(accounts).update(any());
|
||||
verifyNoInteractions(deletedAccountsManager);
|
||||
verifyNoInteractions(directoryQueue);
|
||||
verifyNoInteractions(keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReserveUsernameHash() throws UsernameHashNotAvailableException {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -68,6 +69,25 @@ public class ChangeNumberManagerTest {
|
||||
|
||||
return updatedAccount;
|
||||
});
|
||||
|
||||
when(accountsManager.updatePNIKeys(any(), any(), any(), any())).thenAnswer((Answer<Account>)invocation -> {
|
||||
final Account account = invocation.getArgument(0, Account.class);
|
||||
|
||||
final UUID uuid = account.getUuid();
|
||||
final UUID pni = account.getPhoneNumberIdentifier();
|
||||
final List<Device> devices = account.getDevices();
|
||||
|
||||
final Account updatedAccount = mock(Account.class);
|
||||
when(updatedAccount.getUuid()).thenReturn(uuid);
|
||||
when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(pni);
|
||||
when(updatedAccount.getDevices()).thenReturn(devices);
|
||||
for (long i = 1; i <= 3; i++) {
|
||||
final Optional<Device> d = account.getDevice(i);
|
||||
when(updatedAccount.getDevice(i)).thenReturn(d);
|
||||
}
|
||||
|
||||
return updatedAccount;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,6 +154,86 @@ public class ChangeNumberManagerTest {
|
||||
assertEquals(updatedPhoneNumberIdentifiersByAccount.get(account), UUID.fromString(envelope.getUpdatedPni()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeNumberSameNumberSetPrimaryDevicePrekeyAndSendMessages() throws Exception {
|
||||
final String originalE164 = "+18005551234";
|
||||
final UUID aci = UUID.randomUUID();
|
||||
final UUID pni = UUID.randomUUID();
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getNumber()).thenReturn(originalE164);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
when(account.getPhoneNumberIdentifier()).thenReturn(pni);
|
||||
|
||||
final Device d2 = mock(Device.class);
|
||||
when(d2.isEnabled()).thenReturn(true);
|
||||
when(d2.getId()).thenReturn(2L);
|
||||
|
||||
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
||||
when(account.getDevices()).thenReturn(List.of(d2));
|
||||
|
||||
final String pniIdentityKey = "pni-identity-key";
|
||||
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
||||
|
||||
final IncomingMessage msg = mock(IncomingMessage.class);
|
||||
when(msg.destinationDeviceId()).thenReturn(2L);
|
||||
when(msg.content()).thenReturn(Base64.getEncoder().encodeToString(new byte[]{1}));
|
||||
|
||||
changeNumberManager.changeNumber(account, originalE164, pniIdentityKey, prekeys, List.of(msg), registrationIds);
|
||||
|
||||
verify(accountsManager).updatePNIKeys(account, pniIdentityKey, prekeys, registrationIds);
|
||||
|
||||
final ArgumentCaptor<MessageProtos.Envelope> envelopeCaptor = ArgumentCaptor.forClass(MessageProtos.Envelope.class);
|
||||
verify(messageSender).sendMessage(any(), eq(d2), envelopeCaptor.capture(), eq(false));
|
||||
|
||||
final MessageProtos.Envelope envelope = envelopeCaptor.getValue();
|
||||
|
||||
assertEquals(aci, UUID.fromString(envelope.getDestinationUuid()));
|
||||
assertEquals(aci, UUID.fromString(envelope.getSourceUuid()));
|
||||
assertEquals(Device.MASTER_ID, envelope.getSourceDevice());
|
||||
assertFalse(updatedPhoneNumberIdentifiersByAccount.containsKey(account));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updatePNIKeysSetPrimaryDevicePrekeyAndSendMessages() throws Exception {
|
||||
final UUID aci = UUID.randomUUID();
|
||||
final UUID pni = UUID.randomUUID();
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
when(account.getPhoneNumberIdentifier()).thenReturn(pni);
|
||||
|
||||
final Device d2 = mock(Device.class);
|
||||
when(d2.isEnabled()).thenReturn(true);
|
||||
when(d2.getId()).thenReturn(2L);
|
||||
|
||||
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
||||
when(account.getDevices()).thenReturn(List.of(d2));
|
||||
|
||||
final String pniIdentityKey = "pni-identity-key";
|
||||
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
||||
|
||||
final IncomingMessage msg = mock(IncomingMessage.class);
|
||||
when(msg.destinationDeviceId()).thenReturn(2L);
|
||||
when(msg.content()).thenReturn(Base64.getEncoder().encodeToString(new byte[]{1}));
|
||||
|
||||
changeNumberManager.updatePNIKeys(account, pniIdentityKey, prekeys, List.of(msg), registrationIds);
|
||||
|
||||
verify(accountsManager).updatePNIKeys(account, pniIdentityKey, prekeys, registrationIds);
|
||||
|
||||
final ArgumentCaptor<MessageProtos.Envelope> envelopeCaptor = ArgumentCaptor.forClass(MessageProtos.Envelope.class);
|
||||
verify(messageSender).sendMessage(any(), eq(d2), envelopeCaptor.capture(), eq(false));
|
||||
|
||||
final MessageProtos.Envelope envelope = envelopeCaptor.getValue();
|
||||
|
||||
assertEquals(aci, UUID.fromString(envelope.getDestinationUuid()));
|
||||
assertEquals(aci, UUID.fromString(envelope.getSourceUuid()));
|
||||
assertEquals(Device.MASTER_ID, envelope.getSourceDevice());
|
||||
assertFalse(updatedPhoneNumberIdentifiersByAccount.containsKey(account));
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeNumberMismatchedRegistrationId() {
|
||||
final Account account = mock(Account.class);
|
||||
@@ -164,6 +264,36 @@ public class ChangeNumberManagerTest {
|
||||
() -> changeNumberManager.changeNumber(account, "+18005559876", "pni-identity-key", preKeys, messages, registrationIds));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updatePNIKeysMismatchedRegistrationId() {
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getNumber()).thenReturn("+18005551234");
|
||||
|
||||
final List<Device> devices = new ArrayList<>();
|
||||
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
final Device device = mock(Device.class);
|
||||
when(device.getId()).thenReturn((long) i);
|
||||
when(device.isEnabled()).thenReturn(true);
|
||||
when(device.getRegistrationId()).thenReturn(i);
|
||||
|
||||
devices.add(device);
|
||||
when(account.getDevice(i)).thenReturn(Optional.of(device));
|
||||
}
|
||||
|
||||
when(account.getDevices()).thenReturn(devices);
|
||||
|
||||
final List<IncomingMessage> messages = List.of(
|
||||
new IncomingMessage(1, 2, 1, "foo"),
|
||||
new IncomingMessage(1, 3, 1, "foo"));
|
||||
|
||||
final Map<Long, SignedPreKey> preKeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey(), 3L, new SignedPreKey());
|
||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
||||
|
||||
assertThrows(StaleDevicesException.class,
|
||||
() -> changeNumberManager.updatePNIKeys(account, "pni-identity-key", preKeys, messages, registrationIds));
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeNumberMissingData() {
|
||||
final Account account = mock(Account.class);
|
||||
|
||||
@@ -19,10 +19,15 @@ public class DevicesHelper {
|
||||
}
|
||||
|
||||
public static Device createDevice(final long deviceId, final long lastSeen) {
|
||||
return createDevice(deviceId, lastSeen, 0);
|
||||
}
|
||||
|
||||
public static Device createDevice(final long deviceId, final long lastSeen, final int registrationId) {
|
||||
final Device device = new Device();
|
||||
device.setId(deviceId);
|
||||
device.setLastSeen(lastSeen);
|
||||
device.setUserAgent("OWT");
|
||||
device.setRegistrationId(registrationId);
|
||||
|
||||
setEnabled(device, true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user