mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 18:38:05 +01:00
Validate intra-account messages before applying number changes
This commit is contained in:
@@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -75,7 +76,8 @@ class OutgoingMessageEntityTest {
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
reportSpamToken);
|
||||
reportSpamToken,
|
||||
Clock.systemUTC());
|
||||
|
||||
MessageProtos.Envelope envelope = baseEnvelope.toBuilder().setServerGuid(UUID.randomUUID().toString()).build();
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -25,8 +27,10 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
@@ -38,31 +42,30 @@ import org.whispersystems.textsecuregcm.controllers.MismatchedDevices;
|
||||
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
|
||||
import org.whispersystems.textsecuregcm.controllers.MultiRecipientMismatchedDevicesException;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.tests.util.MultiRecipientMessageHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.TestRecipient;
|
||||
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
|
||||
|
||||
class MessageSenderTest {
|
||||
|
||||
private MessagesManager messagesManager;
|
||||
private PushNotificationManager pushNotificationManager;
|
||||
private MessageSender messageSender;
|
||||
private ExperimentEnrollmentManager experimentEnrollmentManager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
messagesManager = mock(MessagesManager.class);
|
||||
pushNotificationManager = mock(PushNotificationManager.class);
|
||||
experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
|
||||
|
||||
messageSender = new MessageSender(messagesManager, pushNotificationManager, experimentEnrollmentManager);
|
||||
messageSender = new MessageSender(messagesManager, pushNotificationManager);
|
||||
}
|
||||
|
||||
|
||||
@@ -258,13 +261,183 @@ class MessageSenderTest {
|
||||
mismatchedDevicesException.getMismatchedDevicesByServiceIdentifier());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void validateIndividualMessageBundle(final Account destination,
|
||||
final ServiceIdentifier destinationIdentifier,
|
||||
final Map<Byte, MessageProtos.Envelope> messagesByDeviceId,
|
||||
final Map<Byte, Integer> registrationIdsByDeviceId,
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") final Optional<Byte> syncMessageSenderDeviceId,
|
||||
@Nullable final Class<? extends Exception> expectedExceptionClass) {
|
||||
|
||||
final Executable validateIndividualMessageBundle = () -> MessageSender.validateIndividualMessageBundle(destination,
|
||||
destinationIdentifier,
|
||||
messagesByDeviceId,
|
||||
registrationIdsByDeviceId,
|
||||
syncMessageSenderDeviceId,
|
||||
"Signal/Test");
|
||||
|
||||
if (expectedExceptionClass != null) {
|
||||
assertThrows(expectedExceptionClass, validateIndividualMessageBundle);
|
||||
} else {
|
||||
assertDoesNotThrow(validateIndividualMessageBundle);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Arguments> validateIndividualMessageBundle() {
|
||||
final ServiceIdentifier destinationIdentifier = new AciServiceIdentifier(UUID.randomUUID());
|
||||
|
||||
final byte primaryDeviceId = Device.PRIMARY_ID;
|
||||
final byte linkedDeviceId = primaryDeviceId + 1;
|
||||
|
||||
final int primaryDeviceRegistrationId = 17;
|
||||
final int linkedDeviceRegistrationId = primaryDeviceRegistrationId + 1;
|
||||
|
||||
final Device primaryDevice = mock(Device.class);
|
||||
when(primaryDevice.getId()).thenReturn(primaryDeviceId);
|
||||
when(primaryDevice.getRegistrationId(IdentityType.ACI)).thenReturn(primaryDeviceRegistrationId);
|
||||
|
||||
final Device linkedDevice = mock(Device.class);
|
||||
when(linkedDevice.getId()).thenReturn(linkedDeviceId);
|
||||
when(linkedDevice.getRegistrationId(IdentityType.ACI)).thenReturn(linkedDeviceRegistrationId);
|
||||
|
||||
final Account destination = mock(Account.class);
|
||||
when(destination.isIdentifiedBy(any())).thenReturn(false);
|
||||
when(destination.isIdentifiedBy(destinationIdentifier)).thenReturn(true);
|
||||
when(destination.getDevices()).thenReturn(List.of(primaryDevice, linkedDevice));
|
||||
when(destination.getDevice(anyByte())).thenReturn(Optional.empty());
|
||||
when(destination.getDevice(primaryDeviceId)).thenReturn(Optional.of(primaryDevice));
|
||||
when(destination.getDevice(linkedDeviceId)).thenReturn(Optional.of(linkedDevice));
|
||||
|
||||
return List.of(
|
||||
Arguments.argumentSet("Valid",
|
||||
destination,
|
||||
destinationIdentifier,
|
||||
Map.of(
|
||||
primaryDeviceId, generateEnvelope(null, 16),
|
||||
linkedDeviceId, generateEnvelope(null, 16)),
|
||||
Map.of(
|
||||
primaryDeviceId, primaryDeviceRegistrationId,
|
||||
linkedDeviceId, linkedDeviceRegistrationId),
|
||||
Optional.empty(),
|
||||
null),
|
||||
|
||||
Arguments.argumentSet("Mismatched service ID",
|
||||
destination,
|
||||
new AciServiceIdentifier(UUID.randomUUID()),
|
||||
Map.of(
|
||||
primaryDeviceId, generateEnvelope(null, 16),
|
||||
linkedDeviceId, generateEnvelope(null, 16)),
|
||||
Map.of(
|
||||
primaryDeviceId, primaryDeviceRegistrationId,
|
||||
linkedDeviceId, linkedDeviceRegistrationId),
|
||||
Optional.empty(),
|
||||
IllegalArgumentException.class),
|
||||
|
||||
Arguments.argumentSet("Sync message without source on all messages",
|
||||
destination,
|
||||
destinationIdentifier,
|
||||
Map.of(linkedDeviceId, generateEnvelope(null, 16)),
|
||||
Map.of(linkedDeviceId, linkedDeviceRegistrationId),
|
||||
Optional.of(primaryDevice),
|
||||
IllegalArgumentException.class),
|
||||
|
||||
Arguments.argumentSet("Sync message to other account",
|
||||
destination,
|
||||
destinationIdentifier,
|
||||
Map.of(linkedDeviceId, generateEnvelope(new AciServiceIdentifier(UUID.randomUUID()), 16)),
|
||||
Map.of(linkedDeviceId, linkedDeviceRegistrationId),
|
||||
Optional.of(primaryDevice),
|
||||
IllegalArgumentException.class),
|
||||
|
||||
Arguments.argumentSet("Sync message to other account",
|
||||
destination,
|
||||
destinationIdentifier,
|
||||
Map.of(linkedDeviceId, generateEnvelope(new AciServiceIdentifier(UUID.randomUUID()), 16)),
|
||||
Map.of(linkedDeviceId, linkedDeviceRegistrationId),
|
||||
Optional.of(primaryDevice),
|
||||
IllegalArgumentException.class),
|
||||
|
||||
Arguments.argumentSet("Non-sync message addressed to sender",
|
||||
destination,
|
||||
destinationIdentifier,
|
||||
Map.of(
|
||||
primaryDeviceId, generateEnvelope(destinationIdentifier, 16),
|
||||
linkedDeviceId, generateEnvelope(destinationIdentifier, 16)),
|
||||
Map.of(
|
||||
primaryDeviceId, primaryDeviceRegistrationId,
|
||||
linkedDeviceId, linkedDeviceRegistrationId),
|
||||
Optional.empty(),
|
||||
IllegalArgumentException.class),
|
||||
|
||||
Arguments.argumentSet("Non-sync message addressed to sender",
|
||||
destination,
|
||||
destinationIdentifier,
|
||||
Map.of(
|
||||
primaryDeviceId, generateEnvelope(destinationIdentifier, 16),
|
||||
linkedDeviceId, generateEnvelope(destinationIdentifier, 16)),
|
||||
Map.of(
|
||||
primaryDeviceId, primaryDeviceRegistrationId,
|
||||
linkedDeviceId, linkedDeviceRegistrationId),
|
||||
Optional.empty(),
|
||||
IllegalArgumentException.class),
|
||||
|
||||
Arguments.argumentSet("Mismatched devices in message set",
|
||||
destination,
|
||||
destinationIdentifier,
|
||||
Map.of(
|
||||
primaryDeviceId, generateEnvelope(null, 16),
|
||||
linkedDeviceId + 1, generateEnvelope(null, 16)),
|
||||
Map.of(
|
||||
primaryDeviceId, primaryDeviceRegistrationId,
|
||||
linkedDeviceId + 1, linkedDeviceRegistrationId),
|
||||
Optional.empty(),
|
||||
MismatchedDevicesException.class),
|
||||
|
||||
Arguments.argumentSet("Mismatched registration IDs",
|
||||
destination,
|
||||
destinationIdentifier,
|
||||
Map.of(
|
||||
primaryDeviceId, generateEnvelope(null, 16),
|
||||
linkedDeviceId, generateEnvelope(null, 16)),
|
||||
Map.of(
|
||||
primaryDeviceId, primaryDeviceRegistrationId,
|
||||
linkedDeviceId, linkedDeviceRegistrationId + 1),
|
||||
Optional.empty(),
|
||||
MismatchedDevicesException.class),
|
||||
|
||||
Arguments.argumentSet("Oversized message",
|
||||
destination,
|
||||
destinationIdentifier,
|
||||
Map.of(
|
||||
primaryDeviceId, generateEnvelope(null, MessageSender.MAX_MESSAGE_SIZE + 1),
|
||||
linkedDeviceId, generateEnvelope(null, MessageSender.MAX_MESSAGE_SIZE + 1)),
|
||||
Map.of(
|
||||
primaryDeviceId, primaryDeviceRegistrationId,
|
||||
linkedDeviceId, linkedDeviceRegistrationId),
|
||||
Optional.empty(),
|
||||
MessageTooLargeException.class)
|
||||
);
|
||||
}
|
||||
|
||||
private static MessageProtos.Envelope generateEnvelope(@Nullable ServiceIdentifier sourceIdentifier, final int contentLength) {
|
||||
final MessageProtos.Envelope.Builder envelopeBuilder = MessageProtos.Envelope.newBuilder()
|
||||
.setContent(ByteString.copyFrom(TestRandomUtil.nextBytes(contentLength)));
|
||||
|
||||
if (sourceIdentifier != null) {
|
||||
envelopeBuilder.setSourceServiceId(sourceIdentifier.toServiceIdentifierString());
|
||||
}
|
||||
|
||||
return envelopeBuilder.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateContentLength() {
|
||||
assertThrows(MessageTooLargeException.class, () ->
|
||||
MessageSender.validateContentLength(MessageSender.MAX_MESSAGE_SIZE + 1, false, false, false, null));
|
||||
MessageSender.validateContentLength(MessageSender.MAX_MESSAGE_SIZE + 1, false, false, false, Tag.of(UserAgentTagUtil.PLATFORM_TAG, "test")));
|
||||
|
||||
assertDoesNotThrow(() ->
|
||||
MessageSender.validateContentLength(MessageSender.MAX_MESSAGE_SIZE, false, false, false, null));
|
||||
MessageSender.validateContentLength(MessageSender.MAX_MESSAGE_SIZE, false, false, false, Tag.of(UserAgentTagUtil.PLATFORM_TAG, "test")));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyByte;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
@@ -14,6 +13,7 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.protobuf.ByteString;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
@@ -21,11 +21,9 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
@@ -36,6 +34,7 @@ import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.push.MessageSender;
|
||||
import org.whispersystems.textsecuregcm.tests.util.KeysHelper;
|
||||
import org.whispersystems.textsecuregcm.util.TestClock;
|
||||
@@ -61,154 +60,136 @@ public class ChangeNumberManagerTest {
|
||||
final Account account = invocation.getArgument(0, Account.class);
|
||||
final String number = invocation.getArgument(1, String.class);
|
||||
|
||||
final UUID uuid = account.getUuid();
|
||||
final UUID uuid = account.getIdentifier(IdentityType.ACI);
|
||||
final List<Device> devices = account.getDevices();
|
||||
|
||||
final UUID updatedPni = UUID.randomUUID();
|
||||
updatedPhoneNumberIdentifiersByAccount.put(account, updatedPni);
|
||||
|
||||
final Account updatedAccount = mock(Account.class);
|
||||
when(updatedAccount.getUuid()).thenReturn(uuid);
|
||||
when(updatedAccount.getIdentifier(IdentityType.ACI)).thenReturn(uuid);
|
||||
when(updatedAccount.getIdentifier(IdentityType.PNI)).thenReturn(updatedPni);
|
||||
when(updatedAccount.isIdentifiedBy(any())).thenReturn(false);
|
||||
when(updatedAccount.isIdentifiedBy(new AciServiceIdentifier(uuid))).thenReturn(true);
|
||||
when(updatedAccount.isIdentifiedBy(new PniServiceIdentifier(updatedPni))).thenReturn(true);
|
||||
when(updatedAccount.getNumber()).thenReturn(number);
|
||||
when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(updatedPni);
|
||||
when(updatedAccount.getDevices()).thenReturn(devices);
|
||||
for (byte i = 1; i <= 3; i++) {
|
||||
final Optional<Device> d = account.getDevice(i);
|
||||
when(updatedAccount.getDevice(i)).thenReturn(d);
|
||||
}
|
||||
when(updatedAccount.getDevice(anyByte())).thenReturn(Optional.empty());
|
||||
|
||||
account.getDevices().forEach(device ->
|
||||
when(updatedAccount.getDevice(device.getId())).thenReturn(Optional.of(device)));
|
||||
|
||||
return updatedAccount;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeNumberSetPrimaryDevicePrekey() throws Exception {
|
||||
Account account = mock(Account.class);
|
||||
when(account.getNumber()).thenReturn("+18005551234");
|
||||
void changeNumberSingleDevice() throws Exception {
|
||||
final String targetNumber = PhoneNumberUtil.getInstance().format(
|
||||
PhoneNumberUtil.getInstance().getExampleNumber("US"), PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
|
||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||
final IdentityKey pniIdentityKey = new IdentityKey(Curve.generateKeyPair().getPublicKey());
|
||||
final Map<Byte, ECSignedPreKey> prekeys = Map.of(Device.PRIMARY_ID,
|
||||
KeysHelper.signedECPreKey(1, pniIdentityKeyPair));
|
||||
|
||||
changeNumberManager.changeNumber(account, "+18025551234", pniIdentityKey, prekeys, null, Collections.emptyList(), Collections.emptyMap(), null);
|
||||
verify(accountsManager).changeNumber(account, "+18025551234", pniIdentityKey, prekeys, null, Collections.emptyMap());
|
||||
final Map<Byte, ECSignedPreKey> ecSignedPreKeys =
|
||||
Map.of(Device.PRIMARY_ID, KeysHelper.signedECPreKey(1, pniIdentityKeyPair));
|
||||
|
||||
final Map<Byte, KEMSignedPreKey> kemLastResortPreKeys =
|
||||
Map.of(Device.PRIMARY_ID, KeysHelper.signedKEMPreKey(2, pniIdentityKeyPair));
|
||||
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.isIdentifiedBy(any())).thenReturn(false);
|
||||
when(account.isIdentifiedBy(new AciServiceIdentifier(accountIdentifier))).thenReturn(true);
|
||||
|
||||
changeNumberManager.changeNumber(account, targetNumber, pniIdentityKey, ecSignedPreKeys, kemLastResortPreKeys, Collections.emptyList(), Collections.emptyMap(), null);
|
||||
verify(accountsManager).changeNumber(account, targetNumber, pniIdentityKey, ecSignedPreKeys, kemLastResortPreKeys, Collections.emptyMap());
|
||||
verify(messageSender, never()).sendMessages(eq(account), any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeNumberSetPrimaryDevicePrekeyAndSendMessages() throws Exception {
|
||||
final String originalE164 = "+18005551234";
|
||||
final String changedE164 = "+18025551234";
|
||||
final UUID aci = UUID.randomUUID();
|
||||
final UUID pni = UUID.randomUUID();
|
||||
void changeNumberLinkedDevices() throws Exception {
|
||||
final String targetNumber = PhoneNumberUtil.getInstance().format(
|
||||
PhoneNumberUtil.getInstance().getExampleNumber("US"), PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getNumber()).thenReturn(originalE164);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
when(account.getPhoneNumberIdentifier()).thenReturn(pni);
|
||||
final UUID aci = UUID.randomUUID();
|
||||
|
||||
final byte primaryDeviceId = Device.PRIMARY_ID;
|
||||
final byte linkedDeviceId = primaryDeviceId + 1;
|
||||
|
||||
final int primaryDeviceRegistrationId = 17;
|
||||
final int linkedDeviceRegistrationId = primaryDeviceRegistrationId + 1;
|
||||
|
||||
final Device primaryDevice = mock(Device.class);
|
||||
when(primaryDevice.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(primaryDevice.getRegistrationId(IdentityType.ACI)).thenReturn(7);
|
||||
when(primaryDevice.getId()).thenReturn(primaryDeviceId);
|
||||
when(primaryDevice.getRegistrationId(IdentityType.ACI)).thenReturn(primaryDeviceRegistrationId);
|
||||
|
||||
final Device linkedDevice = mock(Device.class);
|
||||
final byte linkedDeviceId = Device.PRIMARY_ID + 1;
|
||||
final int linkedDeviceRegistrationId = 17;
|
||||
when(linkedDevice.getId()).thenReturn(linkedDeviceId);
|
||||
when(linkedDevice.getRegistrationId(IdentityType.ACI)).thenReturn(linkedDeviceRegistrationId);
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getIdentifier(IdentityType.ACI)).thenReturn(aci);
|
||||
when(account.isIdentifiedBy(any())).thenReturn(false);
|
||||
when(account.isIdentifiedBy(new AciServiceIdentifier(aci))).thenReturn(true);
|
||||
when(account.getDevice(anyByte())).thenReturn(Optional.empty());
|
||||
when(account.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(primaryDevice));
|
||||
when(account.getDevice(primaryDeviceId)).thenReturn(Optional.of(primaryDevice));
|
||||
when(account.getDevice(linkedDeviceId)).thenReturn(Optional.of(linkedDevice));
|
||||
when(account.getDevices()).thenReturn(List.of(primaryDevice, linkedDevice));
|
||||
|
||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||
final IdentityKey pniIdentityKey = new IdentityKey(pniIdentityKeyPair.getPublicKey());
|
||||
final Map<Byte, ECSignedPreKey> prekeys = Map.of(Device.PRIMARY_ID,
|
||||
KeysHelper.signedECPreKey(1, pniIdentityKeyPair),
|
||||
final Map<Byte, ECSignedPreKey> ecSignedPreKeys = Map.of(
|
||||
primaryDeviceId, KeysHelper.signedECPreKey(1, pniIdentityKeyPair),
|
||||
linkedDeviceId, KeysHelper.signedECPreKey(2, pniIdentityKeyPair));
|
||||
final Map<Byte, Integer> registrationIds = Map.of(Device.PRIMARY_ID, 17, linkedDeviceId, 19);
|
||||
|
||||
final IncomingMessage msg = mock(IncomingMessage.class);
|
||||
when(msg.type()).thenReturn(1);
|
||||
when(msg.destinationDeviceId()).thenReturn(linkedDeviceId);
|
||||
when(msg.destinationRegistrationId()).thenReturn(linkedDeviceRegistrationId);
|
||||
when(msg.content()).thenReturn(new byte[]{1});
|
||||
final Map<Byte, KEMSignedPreKey> kemLastResortPreKeys = Map.of(
|
||||
primaryDeviceId, KeysHelper.signedKEMPreKey(3, pniIdentityKeyPair),
|
||||
linkedDeviceId, KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair));
|
||||
|
||||
changeNumberManager.changeNumber(account, changedE164, pniIdentityKey, prekeys, null, List.of(msg), registrationIds, null);
|
||||
final Map<Byte, Integer> registrationIds = Map.of(
|
||||
primaryDeviceId, primaryDeviceRegistrationId,
|
||||
linkedDeviceId, linkedDeviceRegistrationId);
|
||||
|
||||
verify(accountsManager).changeNumber(account, changedE164, pniIdentityKey, prekeys, null, registrationIds);
|
||||
final IncomingMessage incomingMessage =
|
||||
new IncomingMessage(1, linkedDeviceId, linkedDeviceRegistrationId, new byte[] { 1 });
|
||||
|
||||
changeNumberManager.changeNumber(account,
|
||||
targetNumber,
|
||||
pniIdentityKey,
|
||||
ecSignedPreKeys,
|
||||
kemLastResortPreKeys,
|
||||
List.of(incomingMessage),
|
||||
registrationIds,
|
||||
null);
|
||||
|
||||
verify(accountsManager).changeNumber(account,
|
||||
targetNumber,
|
||||
pniIdentityKey,
|
||||
ecSignedPreKeys,
|
||||
kemLastResortPreKeys,
|
||||
registrationIds);
|
||||
|
||||
final MessageProtos.Envelope expectedEnvelope = MessageProtos.Envelope.newBuilder()
|
||||
.setType(MessageProtos.Envelope.Type.forNumber(msg.type()))
|
||||
.setType(MessageProtos.Envelope.Type.forNumber(incomingMessage.type()))
|
||||
.setClientTimestamp(CLOCK.millis())
|
||||
.setServerTimestamp(CLOCK.millis())
|
||||
.setDestinationServiceId(new AciServiceIdentifier(aci).toServiceIdentifierString())
|
||||
.setContent(ByteString.copyFrom(msg.content()))
|
||||
.setContent(ByteString.copyFrom(incomingMessage.content()))
|
||||
.setSourceServiceId(new AciServiceIdentifier(aci).toServiceIdentifierString())
|
||||
.setSourceDevice(Device.PRIMARY_ID)
|
||||
.setSourceDevice(primaryDeviceId)
|
||||
.setUpdatedPni(updatedPhoneNumberIdentifiersByAccount.get(account).toString())
|
||||
.setUrgent(true)
|
||||
.setEphemeral(false)
|
||||
.setStory(false)
|
||||
.build();
|
||||
|
||||
verify(messageSender).sendMessages(argThat(a -> a.getUuid().equals(aci)),
|
||||
verify(messageSender).sendMessages(argThat(a -> a.getIdentifier(IdentityType.ACI).equals(aci)),
|
||||
eq(new AciServiceIdentifier(aci)),
|
||||
eq(Map.of(linkedDeviceId, expectedEnvelope)),
|
||||
eq(Map.of(linkedDeviceId, linkedDeviceRegistrationId)),
|
||||
eq(Optional.of(Device.PRIMARY_ID)),
|
||||
eq(Optional.of(primaryDeviceId)),
|
||||
any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeNumberSetPrimaryDevicePrekeyPqAndSendMessages() throws Exception {
|
||||
final String originalE164 = "+18005551234";
|
||||
final String changedE164 = "+18025551234";
|
||||
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);
|
||||
final byte deviceId2 = 2;
|
||||
when(d2.getId()).thenReturn(deviceId2);
|
||||
|
||||
when(account.getDevice(deviceId2)).thenReturn(Optional.of(d2));
|
||||
when(account.getDevices()).thenReturn(List.of(d2));
|
||||
|
||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||
final IdentityKey pniIdentityKey = new IdentityKey(pniIdentityKeyPair.getPublicKey());
|
||||
final Map<Byte, ECSignedPreKey> prekeys = Map.of(Device.PRIMARY_ID,
|
||||
KeysHelper.signedECPreKey(1, pniIdentityKeyPair),
|
||||
deviceId2, KeysHelper.signedECPreKey(2, pniIdentityKeyPair));
|
||||
final Map<Byte, KEMSignedPreKey> pqPrekeys = Map.of((byte) 3, KeysHelper.signedKEMPreKey(3, pniIdentityKeyPair),
|
||||
(byte) 4, KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair));
|
||||
final Map<Byte, Integer> registrationIds = Map.of(Device.PRIMARY_ID, 17, deviceId2, 19);
|
||||
|
||||
final IncomingMessage msg = mock(IncomingMessage.class);
|
||||
when(msg.destinationDeviceId()).thenReturn(deviceId2);
|
||||
when(msg.content()).thenReturn(new byte[]{1});
|
||||
|
||||
changeNumberManager.changeNumber(account, changedE164, pniIdentityKey, prekeys, pqPrekeys, List.of(msg), registrationIds, null);
|
||||
|
||||
verify(accountsManager).changeNumber(account, changedE164, pniIdentityKey, prekeys, pqPrekeys, registrationIds);
|
||||
|
||||
@SuppressWarnings("unchecked") final ArgumentCaptor<Map<Byte, MessageProtos.Envelope>> envelopeCaptor =
|
||||
ArgumentCaptor.forClass(Map.class);
|
||||
|
||||
verify(messageSender).sendMessages(any(), any(), envelopeCaptor.capture(), any(), any(), any());
|
||||
|
||||
assertEquals(1, envelopeCaptor.getValue().size());
|
||||
assertEquals(Set.of(deviceId2), envelopeCaptor.getValue().keySet());
|
||||
|
||||
final MessageProtos.Envelope envelope = envelopeCaptor.getValue().get(deviceId2);
|
||||
|
||||
assertEquals(aci, UUID.fromString(envelope.getDestinationServiceId()));
|
||||
assertEquals(aci, UUID.fromString(envelope.getSourceServiceId()));
|
||||
assertEquals(Device.PRIMARY_ID, envelope.getSourceDevice());
|
||||
assertEquals(updatedPhoneNumberIdentifiersByAccount.get(account), UUID.fromString(envelope.getUpdatedPni()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user