mirror of
https://github.com/signalapp/Signal-Server
synced 2026-08-22 01:17:26 +01:00
Support linking devices to accounts without phone numbers
This commit is contained in:
committed by
Jon Chambers
parent
2c18e4aa1f
commit
48fd25f01e
+34
-23
@@ -48,6 +48,7 @@ import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nullable;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
|
||||
import org.whispersystems.textsecuregcm.auth.BasicAuthorizationHeader;
|
||||
import org.whispersystems.textsecuregcm.auth.ChangesLinkedDevices;
|
||||
@@ -224,7 +225,7 @@ public class DeviceController {
|
||||
""")
|
||||
@ApiResponse(responseCode = "200", description = "The new device was linked to the calling account", useReturnTypeSchema = true)
|
||||
@ApiResponse(responseCode = "403", description = "The given account was not found or the given verification code was incorrect")
|
||||
@ApiResponse(responseCode = "409", description = "The new device is missing a capability supported by all other devices on the account")
|
||||
@ApiResponse(responseCode = "409", description = "The new device is missing a capability required for the account")
|
||||
@ApiResponse(responseCode = "411", description = "The given account already has its maximum number of linked devices")
|
||||
@ApiResponse(responseCode = "422", description = "The request did not pass validation")
|
||||
@ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
|
||||
@@ -234,7 +235,6 @@ public class DeviceController {
|
||||
@HeaderParam(HttpHeaders.USER_AGENT) @Nullable String userAgent,
|
||||
@NotNull @Valid LinkDeviceRequest linkDeviceRequest)
|
||||
throws RateLimitExceededException, DeviceLimitExceededException {
|
||||
|
||||
final Account account = accounts.checkDeviceLinkingToken(linkDeviceRequest.verificationCode())
|
||||
.flatMap(accounts::getByAccountIdentifier)
|
||||
.orElseThrow(ForbiddenException::new);
|
||||
@@ -242,23 +242,33 @@ public class DeviceController {
|
||||
final DeviceActivationRequest deviceActivationRequest = linkDeviceRequest.deviceActivationRequest();
|
||||
final DeviceAttributes deviceAttributes = linkDeviceRequest.deviceAttributes();
|
||||
|
||||
if (deviceAttributes.phoneNumberIdentityRegistrationId() == null ||
|
||||
deviceActivationRequest.pniSignedPreKey().isEmpty() ||
|
||||
deviceActivationRequest.pniPqLastResortPreKey().isEmpty()) {
|
||||
throw new WebApplicationException("PNI-associated info must all be provided", 422);
|
||||
}
|
||||
|
||||
rateLimiters.getVerifyDeviceLimiter().validate(account.getAccountIdentifier());
|
||||
|
||||
// Check the optional-phone-number capability before checking PNI keys, so we can give a better error code (since an
|
||||
// older device will improperly supply PNI keys for a PNI-less account)
|
||||
if (account.getPhoneNumberIdentifierOptional().isEmpty() &&
|
||||
!linkDeviceRequest.deviceAttributes().capabilities().contains(DeviceCapability.OPTIONAL_PHONE_NUMBER)) {
|
||||
throw new WebApplicationException("Missing required device capability", 409);
|
||||
}
|
||||
|
||||
final boolean allKeysValid =
|
||||
PreKeySignatureValidator.validatePreKeySignatures(account.getIdentityKey(IdentityType.ACI),
|
||||
List.of(deviceActivationRequest.aciSignedPreKey(), deviceActivationRequest.aciPqLastResortPreKey()),
|
||||
userAgent,
|
||||
"link-device")
|
||||
&& PreKeySignatureValidator.validatePreKeySignatures(account.getIdentityKey(IdentityType.PNI),
|
||||
List.of(deviceActivationRequest.pniSignedPreKey().get(), deviceActivationRequest.pniPqLastResortPreKey().get()),
|
||||
userAgent,
|
||||
"link-device");
|
||||
PreKeySignatureValidator
|
||||
.validatePreKeySignatures(account.getAccountIdentityKey(),
|
||||
List.of(deviceActivationRequest.aciSignedPreKey(), deviceActivationRequest.aciPqLastResortPreKey()),
|
||||
userAgent,
|
||||
"link-device")
|
||||
&& account.getPhoneNumberIdentityKey()
|
||||
.map(pniIdentityKey ->
|
||||
deviceActivationRequest.pniSignedPreKey().isPresent()
|
||||
&& deviceActivationRequest.pniPqLastResortPreKey().isPresent()
|
||||
&& PreKeySignatureValidator.validatePreKeySignatures(
|
||||
pniIdentityKey,
|
||||
List.of(deviceActivationRequest.pniSignedPreKey().get(), deviceActivationRequest.pniPqLastResortPreKey().get()),
|
||||
userAgent,
|
||||
"link-device"))
|
||||
.orElse(
|
||||
deviceActivationRequest.pniSignedPreKey().isEmpty()
|
||||
&& deviceActivationRequest.pniPqLastResortPreKey().isEmpty());
|
||||
|
||||
if (!allKeysValid) {
|
||||
throw new WebApplicationException(Response.status(422).build());
|
||||
@@ -287,7 +297,7 @@ public class DeviceController {
|
||||
}
|
||||
|
||||
try {
|
||||
final Pair<Account, Device> accountAndDevice = accounts.addDevice(account.getIdentifier(IdentityType.ACI),
|
||||
final Pair<Account, Device> accountAndDevice = accounts.addDevice(account.getAccountIdentifier(),
|
||||
new DeviceSpec(deviceAttributes.name(),
|
||||
authorizationHeader.getPassword(),
|
||||
signalAgent,
|
||||
@@ -296,18 +306,19 @@ public class DeviceController {
|
||||
deviceAttributes.registrationId(),
|
||||
deviceActivationRequest.aciSignedPreKey(),
|
||||
deviceActivationRequest.aciPqLastResortPreKey()),
|
||||
Optional.of(new DeviceIdentityInfo(
|
||||
deviceAttributes.phoneNumberIdentityRegistrationId(),
|
||||
deviceActivationRequest.pniSignedPreKey().get(),
|
||||
deviceActivationRequest.pniPqLastResortPreKey().get())),
|
||||
account.getPhoneNumberIdentityKey().map(_ ->
|
||||
new DeviceIdentityInfo(
|
||||
deviceAttributes.phoneNumberIdentityRegistrationId(),
|
||||
deviceActivationRequest.pniSignedPreKey().get(),
|
||||
deviceActivationRequest.pniPqLastResortPreKey().get())),
|
||||
deviceAttributes.fetchesMessages(),
|
||||
deviceActivationRequest.apnToken(),
|
||||
deviceActivationRequest.gcmToken()),
|
||||
linkDeviceRequest.verificationCode());
|
||||
|
||||
return new LinkDeviceResponse(
|
||||
accountAndDevice.first().getIdentifier(IdentityType.ACI),
|
||||
accountAndDevice.first().getIdentifier(IdentityType.PNI),
|
||||
accountAndDevice.first().getAccountIdentifier(),
|
||||
accountAndDevice.first().getPhoneNumberIdentifierOptional(),
|
||||
accountAndDevice.second().getId());
|
||||
} catch (final LinkDeviceTokenAlreadyUsedException e) {
|
||||
throw new ForbiddenException();
|
||||
|
||||
-2
@@ -1,10 +1,8 @@
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public record DeviceActivationRequest(
|
||||
|
||||
+1
-2
@@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.whispersystems.textsecuregcm.storage.DeviceCapability;
|
||||
@@ -29,13 +30,11 @@ public record DeviceAttributes(
|
||||
Integer phoneNumberIdentityRegistrationId,
|
||||
|
||||
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||
|
||||
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||
@Size(max = 225)
|
||||
byte[] name,
|
||||
|
||||
@JsonSerialize(using = DeviceCapabilityAdapter.Serializer.class)
|
||||
|
||||
@JsonDeserialize(using = DeviceCapabilityAdapter.Deserializer.class)
|
||||
@Nullable
|
||||
Set<DeviceCapability> capabilities) {
|
||||
|
||||
+2
-1
@@ -5,7 +5,8 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public record LinkDeviceResponse(UUID uuid, UUID pni, byte deviceId) {
|
||||
public record LinkDeviceResponse(UUID uuid, Optional<UUID> pni, byte deviceId) {
|
||||
}
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ public abstract class PreKeySignatureValidator {
|
||||
final Collection<SignedPreKey<?>> signedPreKeys,
|
||||
@Nullable final String userAgent,
|
||||
final String context) {
|
||||
|
||||
final boolean success = signedPreKeys.stream().allMatch(signedPreKey -> signedPreKey.signatureValid(identityKey));
|
||||
|
||||
if (!success) {
|
||||
|
||||
@@ -20,6 +20,7 @@ public class DeviceCapabilityUtil {
|
||||
case DEVICE_CAPABILITY_SPARSE_POST_QUANTUM_RATCHET -> DeviceCapability.SPARSE_POST_QUANTUM_RATCHET;
|
||||
case DEVICE_CAPABILITY_PROFILES_V2 -> DeviceCapability.PROFILES_V2;
|
||||
case DEVICE_CAPABILITY_USERNAME_CHANGE_SYNC_MESSAGE -> DeviceCapability.USERNAME_CHANGE_SYNC_MESSAGE;
|
||||
case DEVICE_CAPABILITY_OPTIONAL_PHONE_NUMBER -> DeviceCapability.OPTIONAL_PHONE_NUMBER;
|
||||
case DEVICE_CAPABILITY_UNSPECIFIED, UNRECOGNIZED ->
|
||||
throw GrpcExceptions.invalidArguments("unrecognized device capability");
|
||||
};
|
||||
@@ -33,6 +34,7 @@ public class DeviceCapabilityUtil {
|
||||
case SPARSE_POST_QUANTUM_RATCHET -> org.signal.chat.common.DeviceCapability.DEVICE_CAPABILITY_SPARSE_POST_QUANTUM_RATCHET;
|
||||
case PROFILES_V2 -> org.signal.chat.common.DeviceCapability.DEVICE_CAPABILITY_PROFILES_V2;
|
||||
case USERNAME_CHANGE_SYNC_MESSAGE -> org.signal.chat.common.DeviceCapability.DEVICE_CAPABILITY_USERNAME_CHANGE_SYNC_MESSAGE;
|
||||
case OPTIONAL_PHONE_NUMBER -> org.signal.chat.common.DeviceCapability.DEVICE_CAPABILITY_OPTIONAL_PHONE_NUMBER;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,11 +516,10 @@ public class AccountsManager extends RedisPubSubAdapter<String, String> implemen
|
||||
public Pair<Account, Device> addDevice(final UUID accountIdentifier, final DeviceSpec deviceSpec, final String linkDeviceToken)
|
||||
throws LinkDeviceTokenAlreadyUsedException {
|
||||
|
||||
final UUID phoneNumberIdentifier = accounts.getByAccountIdentifier(accountIdentifier)
|
||||
.map(account -> account.getIdentifier(IdentityType.PNI))
|
||||
final Account account = accounts.getByAccountIdentifier(accountIdentifier)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Account not found: " + accountIdentifier));
|
||||
|
||||
return accountLockManager.withLock(Set.of(phoneNumberIdentifier),
|
||||
return accountLockManager.withSingleAccountLock(account,
|
||||
() -> addDevice(accountIdentifier, deviceSpec, linkDeviceToken, MAX_UPDATE_ATTEMPTS),
|
||||
accountLockExecutor);
|
||||
}
|
||||
@@ -532,16 +531,19 @@ public class AccountsManager extends RedisPubSubAdapter<String, String> implemen
|
||||
|
||||
final byte nextDeviceId = account.getNextDeviceId();
|
||||
|
||||
CompletableFuture.allOf(
|
||||
CompletableFuture
|
||||
.allOf(
|
||||
keysManager.deleteSingleUsePreKeys(account.getAccountIdentifier(), nextDeviceId),
|
||||
keysManager.deleteSingleUsePreKeys(account.getPhoneNumberIdentifier(), nextDeviceId),
|
||||
account.getPhoneNumberIdentifierOptional()
|
||||
.map(pni -> keysManager.deleteSingleUsePreKeys(pni, nextDeviceId))
|
||||
.orElse(CompletableFuture.completedFuture(null)),
|
||||
messagesManager.clear(account.getAccountIdentifier(), nextDeviceId))
|
||||
.join();
|
||||
|
||||
account.addDevice(deviceSpec.toDevice(nextDeviceId, clock, account.getIdentityKey(IdentityType.ACI)));
|
||||
account.addDevice(deviceSpec.toDevice(nextDeviceId, clock, account.getAccountIdentityKey()));
|
||||
|
||||
final List<TransactWriteItem> additionalWriteItems = new ArrayList<>(keysManager.buildWriteItemsForNewDevice(
|
||||
account.getIdentifier(IdentityType.ACI),
|
||||
account.getAccountIdentifier(),
|
||||
account.getPhoneNumberIdentifierOptional(),
|
||||
nextDeviceId,
|
||||
deviceSpec.aciInfo().signedPreKey(),
|
||||
|
||||
+2
-1
@@ -18,7 +18,8 @@ public enum DeviceCapability {
|
||||
ATTACHMENT_BACKFILL("attachmentBackfill", AccountCapabilityMode.PRIMARY_DEVICE, AccountCapabilityVisibility.SELF, false, false),
|
||||
SPARSE_POST_QUANTUM_RATCHET("spqr", AccountCapabilityMode.ALL_DEVICES, AccountCapabilityVisibility.PUBLIC, true, true),
|
||||
PROFILES_V2("profiles_v2", AccountCapabilityMode.ALL_DEVICES, AccountCapabilityVisibility.SELF, false, false),
|
||||
USERNAME_CHANGE_SYNC_MESSAGE("usernameChangeSyncMessage", AccountCapabilityMode.ALL_DEVICES, AccountCapabilityVisibility.SELF, true, false);
|
||||
USERNAME_CHANGE_SYNC_MESSAGE("usernameChangeSyncMessage", AccountCapabilityMode.ALL_DEVICES, AccountCapabilityVisibility.SELF, true, false),
|
||||
OPTIONAL_PHONE_NUMBER("optionalPhoneNumber", AccountCapabilityMode.ALL_DEVICES, AccountCapabilityVisibility.SERVER, false, false);
|
||||
|
||||
public static final List<DeviceCapability> PUBLIC_VISIBLE_CAPABILITIES = Arrays.stream(DeviceCapability.values())
|
||||
.filter(c -> c.accountCapabilityVisibility == AccountCapabilityVisibility.PUBLIC)
|
||||
|
||||
@@ -22,7 +22,7 @@ public record DeviceSpec(
|
||||
boolean fetchesMessages,
|
||||
Optional<ApnRegistrationId> apnRegistrationId,
|
||||
Optional<GcmRegistrationId> gcmRegistrationId) {
|
||||
|
||||
|
||||
public Device toDevice(final byte deviceId, final Clock clock, final IdentityKey aciIdentityKey) {
|
||||
final long created = clock.millis();
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ enum DeviceCapability {
|
||||
DEVICE_CAPABILITY_SPARSE_POST_QUANTUM_RATCHET = 7;
|
||||
DEVICE_CAPABILITY_PROFILES_V2 = 8;
|
||||
DEVICE_CAPABILITY_USERNAME_CHANGE_SYNC_MESSAGE = 9;
|
||||
DEVICE_CAPABILITY_OPTIONAL_PHONE_NUMBER = 10;
|
||||
}
|
||||
|
||||
message ZkCredential {
|
||||
|
||||
+121
-39
@@ -138,9 +138,11 @@ class DeviceControllerTest {
|
||||
|
||||
when(account.getNextDeviceId()).thenReturn(NEXT_DEVICE_ID);
|
||||
when(account.getNumber()).thenReturn(AuthHelper.VALID_NUMBER);
|
||||
when(account.getNumberOptional()).thenReturn(Optional.of((AuthHelper.VALID_NUMBER)));
|
||||
when(account.getAccountIdentifier()).thenReturn(AuthHelper.VALID_UUID);
|
||||
when(account.getIdentifier(IdentityType.ACI)).thenReturn(AuthHelper.VALID_UUID);
|
||||
when(account.getPhoneNumberIdentifier()).thenReturn(AuthHelper.VALID_PNI);
|
||||
when(account.getPhoneNumberIdentifierOptional()).thenReturn(Optional.of(AuthHelper.VALID_PNI));
|
||||
when(account.getIdentifier(IdentityType.PNI)).thenReturn(AuthHelper.VALID_PNI);
|
||||
when(account.getPrimaryDevice()).thenReturn(primaryDevice);
|
||||
when(account.getDevice(anyByte())).thenReturn(Optional.empty());
|
||||
@@ -214,11 +216,12 @@ class DeviceControllerTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
void linkDeviceAtomic(final boolean fetchesMessages,
|
||||
final Optional<ApnRegistrationId> apnRegistrationId,
|
||||
final Optional<GcmRegistrationId> gcmRegistrationId,
|
||||
final Optional<String> expectedApnsToken,
|
||||
final Optional<String> expectedGcmToken) throws LinkDeviceTokenAlreadyUsedException {
|
||||
void linkDeviceAtomic(final boolean withPhoneNumber,
|
||||
final boolean fetchesMessages,
|
||||
final Optional<ApnRegistrationId> apnRegistrationId,
|
||||
final Optional<GcmRegistrationId> gcmRegistrationId,
|
||||
final Optional<String> expectedApnsToken,
|
||||
final Optional<String> expectedGcmToken) throws LinkDeviceTokenAlreadyUsedException {
|
||||
|
||||
final Device existingDevice = mock(Device.class);
|
||||
when(existingDevice.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
@@ -238,8 +241,10 @@ class DeviceControllerTest {
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
final IdentityKey aciIdentityKey = new IdentityKey(aciIdentityKeyPair.getPublicKey());
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(aciIdentityKey);
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(aciIdentityKey);
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(
|
||||
Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey()))
|
||||
.filter(_ -> withPhoneNumber));
|
||||
|
||||
when(accountsManager.checkDeviceLinkingToken(anyString())).thenReturn(Optional.of(AuthHelper.VALID_UUID));
|
||||
|
||||
@@ -252,12 +257,15 @@ class DeviceControllerTest {
|
||||
|
||||
when(asyncCommands.set(any(), any(), any())).thenReturn(MockRedisFuture.completedFuture(null));
|
||||
|
||||
final DeviceAttributes deviceAttributes = new DeviceAttributes(fetchesMessages, 1234, 5678, null,
|
||||
DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES);
|
||||
final EnumSet<DeviceCapability> capabilities = EnumSet.copyOf(DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES);
|
||||
capabilities.add(DeviceCapability.OPTIONAL_PHONE_NUMBER);
|
||||
|
||||
final DeviceAttributes deviceAttributes = new DeviceAttributes(fetchesMessages, 1234, withPhoneNumber ? 5678 : null, null,
|
||||
capabilities);
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest("link-device-token",
|
||||
deviceAttributes,
|
||||
new DeviceActivationRequest(aciSignedPreKey, Optional.of(pniSignedPreKey), aciPqLastResortPreKey, Optional.of(pniPqLastResortPreKey), apnRegistrationId, gcmRegistrationId));
|
||||
new DeviceActivationRequest(aciSignedPreKey, Optional.of(pniSignedPreKey).filter(_ -> withPhoneNumber), aciPqLastResortPreKey, Optional.of(pniPqLastResortPreKey).filter(_ -> withPhoneNumber), apnRegistrationId, gcmRegistrationId));
|
||||
|
||||
final LinkDeviceResponse response = resources.getJerseyTest()
|
||||
.target("/v1/devices/link")
|
||||
@@ -286,10 +294,14 @@ class DeviceControllerTest {
|
||||
final String gcmToken = "gcm-token";
|
||||
|
||||
return Stream.of(
|
||||
Arguments.of(true, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()),
|
||||
Arguments.of(false, Optional.of(new ApnRegistrationId(apnsToken)), Optional.empty(), Optional.of(apnsToken), Optional.empty()),
|
||||
Arguments.of(false, Optional.of(new ApnRegistrationId(apnsToken)), Optional.empty(), Optional.of(apnsToken), Optional.empty()),
|
||||
Arguments.of(false, Optional.empty(), Optional.of(new GcmRegistrationId(gcmToken)), Optional.empty(), Optional.of(gcmToken))
|
||||
Arguments.of(true, true, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()),
|
||||
Arguments.of(true, false, Optional.of(new ApnRegistrationId(apnsToken)), Optional.empty(), Optional.of(apnsToken), Optional.empty()),
|
||||
Arguments.of(true, false, Optional.of(new ApnRegistrationId(apnsToken)), Optional.empty(), Optional.of(apnsToken), Optional.empty()),
|
||||
Arguments.of(true, false, Optional.empty(), Optional.of(new GcmRegistrationId(gcmToken)), Optional.empty(), Optional.of(gcmToken)),
|
||||
Arguments.of(false, true, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()),
|
||||
Arguments.of(false, false, Optional.of(new ApnRegistrationId(apnsToken)), Optional.empty(), Optional.of(apnsToken), Optional.empty()),
|
||||
Arguments.of(false, false, Optional.of(new ApnRegistrationId(apnsToken)), Optional.empty(), Optional.of(apnsToken), Optional.empty()),
|
||||
Arguments.of(false, false, Optional.empty(), Optional.of(new GcmRegistrationId(gcmToken)), Optional.empty(), Optional.of(gcmToken))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -320,8 +332,8 @@ class DeviceControllerTest {
|
||||
aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
when(account.hasCapability(capability)).thenReturn(accountHasCapability);
|
||||
|
||||
when(asyncCommands.set(any(), any(), any())).thenReturn(MockRedisFuture.completedFuture(null));
|
||||
@@ -376,8 +388,8 @@ class DeviceControllerTest {
|
||||
aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
|
||||
when(asyncCommands.set(any(), any(), any())).thenReturn(MockRedisFuture.completedFuture(null));
|
||||
|
||||
@@ -422,8 +434,8 @@ class DeviceControllerTest {
|
||||
aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest("link-device-token",
|
||||
new DeviceAttributes(false, 1234, 5678, null, null),
|
||||
@@ -458,8 +470,8 @@ class DeviceControllerTest {
|
||||
aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
|
||||
when(accountsManager.checkDeviceLinkingToken(anyString())).thenReturn(Optional.of(AuthHelper.VALID_UUID));
|
||||
|
||||
@@ -507,8 +519,8 @@ class DeviceControllerTest {
|
||||
aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
|
||||
when(commands.get(anyString())).thenReturn("");
|
||||
|
||||
@@ -558,8 +570,8 @@ class DeviceControllerTest {
|
||||
aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest(deviceCode.token(),
|
||||
new DeviceAttributes(fetchesMessages, 1234, 5678, null, null),
|
||||
@@ -599,9 +611,8 @@ class DeviceControllerTest {
|
||||
final Device existingDevice = mock(Device.class);
|
||||
when(existingDevice.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(account.getDevices()).thenReturn(List.of(existingDevice));
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(aciIdentityKey);
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(pniIdentityKey);
|
||||
when(account.getAccountIdentityKey()).thenReturn(aciIdentityKey);
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(pniIdentityKey));
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest("link-device-token",
|
||||
new DeviceAttributes(true, 1234, 5678, null, null),
|
||||
@@ -637,6 +648,37 @@ class DeviceControllerTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkDeviceSpuriousPniMaterial() {
|
||||
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account));
|
||||
when(accountsManager.checkDeviceLinkingToken(any())).thenReturn(Optional.of(AuthHelper.VALID_UUID));
|
||||
|
||||
final ECKeyPair aciIdentityKeyPair = ECKeyPair.generate();
|
||||
final ECKeyPair pniIdentityKeyPair = ECKeyPair.generate();
|
||||
final IdentityKey aciIdentityKey = new IdentityKey(aciIdentityKeyPair.getPublicKey());
|
||||
final IdentityKey pniIdentityKey = new IdentityKey(aciIdentityKeyPair.getPublicKey());
|
||||
final ECSignedPreKey aciSignedPreKey = KeysHelper.signedECPreKey(1, aciIdentityKeyPair);
|
||||
final ECSignedPreKey pniSignedPreKey = KeysHelper.signedECPreKey(2, pniIdentityKeyPair);
|
||||
final KEMSignedPreKey aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
final KEMSignedPreKey pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(account.getAccountIdentityKey()).thenReturn(aciIdentityKey);
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.empty());
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest("link-device-token",
|
||||
new DeviceAttributes(true, 1234, 5678, null, null),
|
||||
new DeviceActivationRequest(aciSignedPreKey, Optional.of(pniSignedPreKey), aciPqLastResortPreKey, Optional.of(pniPqLastResortPreKey), Optional.empty(), Optional.empty()));
|
||||
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/link")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getProvisioningAuthHeader(AuthHelper.VALID_NUMBER, "password1"))
|
||||
.put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE))) {
|
||||
|
||||
assertEquals(422, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkDeviceAtomicMissingCapabilities() {
|
||||
final ECSignedPreKey aciSignedPreKey;
|
||||
@@ -658,8 +700,8 @@ class DeviceControllerTest {
|
||||
when(existingDevice.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(account.getDevices()).thenReturn(List.of(existingDevice));
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
|
||||
when(accountsManager.checkDeviceLinkingToken(anyString())).thenReturn(Optional.of(AuthHelper.VALID_UUID));
|
||||
|
||||
@@ -677,6 +719,46 @@ class DeviceControllerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkDeviceAtomicMissingOptionalPhoneNumberCapability() {
|
||||
final ECSignedPreKey aciSignedPreKey;
|
||||
final ECSignedPreKey pniSignedPreKey;
|
||||
final KEMSignedPreKey aciPqLastResortPreKey;
|
||||
final KEMSignedPreKey pniPqLastResortPreKey;
|
||||
|
||||
final ECKeyPair aciIdentityKeyPair = ECKeyPair.generate();
|
||||
final ECKeyPair pniIdentityKeyPair = ECKeyPair.generate();
|
||||
|
||||
aciSignedPreKey = KeysHelper.signedECPreKey(1, aciIdentityKeyPair);
|
||||
pniSignedPreKey = KeysHelper.signedECPreKey(2, pniIdentityKeyPair);
|
||||
aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account));
|
||||
|
||||
final Device existingDevice = mock(Device.class);
|
||||
when(existingDevice.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(account.getDevices()).thenReturn(List.of(existingDevice));
|
||||
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentifierOptional()).thenReturn(Optional.empty());
|
||||
|
||||
when(accountsManager.checkDeviceLinkingToken(anyString())).thenReturn(Optional.of(AuthHelper.VALID_UUID));
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest("link-device-token",
|
||||
new DeviceAttributes(true, 1234, 5678, null, DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES),
|
||||
new DeviceActivationRequest(aciSignedPreKey, Optional.of(pniSignedPreKey), aciPqLastResortPreKey, Optional.of(pniPqLastResortPreKey), Optional.empty(), Optional.empty()));
|
||||
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/link")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getProvisioningAuthHeader(AuthHelper.VALID_NUMBER, "password1"))
|
||||
.put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE))) {
|
||||
|
||||
assertEquals(409, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void linkDeviceAtomicInvalidSignature(final IdentityKey aciIdentityKey,
|
||||
@@ -691,8 +773,8 @@ class DeviceControllerTest {
|
||||
final Device existingDevice = mock(Device.class);
|
||||
when(existingDevice.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(account.getDevices()).thenReturn(List.of(existingDevice));
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(aciIdentityKey);
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(pniIdentityKey);
|
||||
when(account.getAccountIdentityKey()).thenReturn(aciIdentityKey);
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(pniIdentityKey));
|
||||
|
||||
when(accountsManager.checkDeviceLinkingToken(anyString())).thenReturn(Optional.of(AuthHelper.VALID_UUID));
|
||||
|
||||
@@ -752,8 +834,8 @@ class DeviceControllerTest {
|
||||
aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest("link-device-token",
|
||||
new DeviceAttributes(false, 1234, 5678, TestRandomUtil.nextBytes(512), null),
|
||||
@@ -786,8 +868,8 @@ class DeviceControllerTest {
|
||||
final KEMSignedPreKey pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
final IdentityKey aciIdentityKey = new IdentityKey(aciIdentityKeyPair.getPublicKey());
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(aciIdentityKey);
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(aciIdentityKey);
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
|
||||
when(accountsManager.addDevice(any(), any(), any())).thenAnswer(invocation -> {
|
||||
final Account a = accountsManager.getByAccountIdentifier(invocation.getArgument(0)).orElseThrow();
|
||||
@@ -848,8 +930,8 @@ class DeviceControllerTest {
|
||||
aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||
pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
when(account.getAccountIdentityKey()).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getPhoneNumberIdentityKey()).thenReturn(Optional.of(new IdentityKey(pniIdentityKeyPair.getPublicKey())));
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest("link-device-token",
|
||||
null,
|
||||
|
||||
+41
-44
@@ -654,46 +654,6 @@ class RegistrationControllerTest {
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void atomicAccountCreationSuccess(final RegistrationRequest registrationRequest,
|
||||
final IdentityKey expectedAciIdentityKey,
|
||||
final IdentityKey expectedPniIdentityKey,
|
||||
final DeviceSpec expectedDeviceSpec) {
|
||||
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
final UUID phoneNumberIdentifier = UUID.randomUUID();
|
||||
final Device device = mock(Device.class);
|
||||
|
||||
final Account account = MockUtils.buildMock(Account.class, a -> {
|
||||
when(a.getAccountIdentifier()).thenReturn(accountIdentifier);
|
||||
when(a.getPhoneNumberIdentifierOptional()).thenReturn(Optional.of(phoneNumberIdentifier));
|
||||
when(a.getPrimaryDevice()).thenReturn(device);
|
||||
});
|
||||
|
||||
when(accountsManager.create(any(), any(), any(), any(), any(), any()))
|
||||
.thenReturn(account);
|
||||
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getProvisioningAuthHeader(NUMBER, PASSWORD));
|
||||
|
||||
try (Response response = request.post(Entity.json(registrationRequest))) {
|
||||
assertEquals(200, response.getStatus());
|
||||
final AccountIdentityResponse identityResponse = response.readEntity(AccountIdentityResponse.class);
|
||||
assertEquals(accountIdentifier, identityResponse.uuid());
|
||||
}
|
||||
|
||||
verify(accountsManager).create(
|
||||
eq(NUMBER),
|
||||
argThat(attributes -> accountAttributesEqual(attributes, registrationRequest.accountAttributes())),
|
||||
eq(expectedAciIdentityKey),
|
||||
eq(expectedPniIdentityKey),
|
||||
eq(expectedDeviceSpec),
|
||||
any());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void reregistrationFlag(final boolean accountExists) {
|
||||
@@ -1177,6 +1137,45 @@ class RegistrationControllerTest {
|
||||
&& Arrays.equals(a.recoveryPassword().orElse(null), b.recoveryPassword().orElse(null));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void atomicAccountCreationSuccess(final RegistrationRequest registrationRequest,
|
||||
final IdentityKey expectedAciIdentityKey,
|
||||
final IdentityKey expectedPniIdentityKey,
|
||||
final DeviceSpec expectedDeviceSpec) throws InterruptedException {
|
||||
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
final UUID phoneNumberIdentifier = UUID.randomUUID();
|
||||
final Device device = mock(Device.class);
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getAccountIdentifier()).thenReturn(accountIdentifier);
|
||||
when(account.getPhoneNumberIdentifierOptional()).thenReturn(Optional.of(phoneNumberIdentifier));
|
||||
when(account.getPrimaryDevice()).thenReturn(device);
|
||||
|
||||
when(accountsManager.create(any(), any(), any(), any(), any(), any()))
|
||||
.thenReturn(account);
|
||||
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target("/v1/registration")
|
||||
.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getProvisioningAuthHeader(NUMBER, PASSWORD));
|
||||
|
||||
try (Response response = request.post(Entity.json(registrationRequest))) {
|
||||
assertEquals(200, response.getStatus());
|
||||
final AccountIdentityResponse identityResponse = response.readEntity(AccountIdentityResponse.class);
|
||||
assertEquals(accountIdentifier, identityResponse.uuid());
|
||||
}
|
||||
|
||||
verify(accountsManager).create(
|
||||
eq(NUMBER),
|
||||
argThat(attributes -> accountAttributesEqual(attributes, registrationRequest.accountAttributes())),
|
||||
eq(expectedAciIdentityKey),
|
||||
eq(expectedPniIdentityKey),
|
||||
eq(expectedDeviceSpec),
|
||||
any());
|
||||
}
|
||||
|
||||
private static List<Arguments> atomicAccountCreationSuccess() {
|
||||
final IdentityKey aciIdentityKey;
|
||||
final IdentityKey pniIdentityKey;
|
||||
@@ -1203,13 +1202,11 @@ class RegistrationControllerTest {
|
||||
final Set<DeviceCapability> deviceCapabilities = DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES;
|
||||
|
||||
final AccountAttributes fetchesMessagesAccountAttributes =
|
||||
new AccountAttributes(true, registrationId, pniRegistrationId, "test".getBytes(StandardCharsets.UTF_8), null, true, deviceCapabilities,
|
||||
null)
|
||||
new AccountAttributes(true, registrationId, pniRegistrationId, deviceName, null, true, deviceCapabilities, null)
|
||||
.setUnidentifiedAccessKey(TestRandomUtil.nextBytes(16));
|
||||
|
||||
final AccountAttributes pushAccountAttributes =
|
||||
new AccountAttributes(false, registrationId, pniRegistrationId, "test".getBytes(StandardCharsets.UTF_8), null, true, deviceCapabilities,
|
||||
null)
|
||||
new AccountAttributes(false, registrationId, pniRegistrationId, deviceName, null, true, deviceCapabilities, null)
|
||||
.setUnidentifiedAccessKey(TestRandomUtil.nextBytes(16));
|
||||
|
||||
final String apnsToken = "apns-token";
|
||||
|
||||
+19
-13
@@ -994,15 +994,18 @@ class AccountsManagerTest {
|
||||
assertEquals(hasStorage, account.hasCapability(DeviceCapability.STORAGE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddDevice() throws LinkDeviceTokenAlreadyUsedException {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testAddDevice(boolean accountHasPhoneNumber) throws LinkDeviceTokenAlreadyUsedException {
|
||||
final String phoneNumber =
|
||||
PhoneNumberUtil.getInstance().format(PhoneNumberUtil.getInstance().getExampleNumber("US"),
|
||||
PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
|
||||
final Account account = AccountsHelper.generateTestAccount(phoneNumber, List.of(generateTestDevice(CLOCK.millis())));
|
||||
final UUID aci = account.getIdentifier(IdentityType.ACI);
|
||||
final UUID pni = account.getIdentifier(IdentityType.PNI);
|
||||
final Account account = accountHasPhoneNumber
|
||||
? AccountsHelper.generateTestAccount(phoneNumber, List.of(generateTestDevice(CLOCK.millis())))
|
||||
: AccountsHelper.generateTestAccountNoPhoneNumber(List.of(generateTestDevice(CLOCK.millis())));
|
||||
final UUID aci = account.getAccountIdentifier();
|
||||
final Optional<UUID> maybePni = account.getPhoneNumberIdentifierOptional();
|
||||
account.setIdentityKey(new IdentityKey(ECKeyPair.generate().getPublicKey()));
|
||||
|
||||
final byte nextDeviceId = account.getNextDeviceId();
|
||||
@@ -1027,39 +1030,42 @@ class AccountsManagerTest {
|
||||
|
||||
CLOCK.pin(CLOCK.instant().plusSeconds(60));
|
||||
|
||||
final Pair<Account, Device> updatedAccountAndDevice = accountsManager.addDevice(aci, new DeviceSpec(
|
||||
final Pair<Account, Device> updatedAccountAndDevice = accountsManager.addDevice(
|
||||
aci,
|
||||
new DeviceSpec(
|
||||
deviceNameCiphertext,
|
||||
password,
|
||||
signalAgent,
|
||||
deviceCapabilities,
|
||||
new DeviceIdentityInfo(aciRegistrationId, aciSignedPreKey, aciPqLastResortPreKey),
|
||||
Optional.of(new DeviceIdentityInfo(pniRegistrationId, pniSignedPreKey, pniPqLastResortPreKey)),
|
||||
Optional.of(new DeviceIdentityInfo(pniRegistrationId, pniSignedPreKey, pniPqLastResortPreKey)).filter(_ -> accountHasPhoneNumber),
|
||||
true,
|
||||
Optional.empty(),
|
||||
Optional.empty()),
|
||||
accountsManager.generateLinkDeviceToken(aci));
|
||||
|
||||
verify(keysManager).deleteSingleUsePreKeys(aci, nextDeviceId);
|
||||
verify(keysManager).deleteSingleUsePreKeys(pni, nextDeviceId);
|
||||
maybePni.ifPresent(pni -> verify(keysManager).deleteSingleUsePreKeys(pni, nextDeviceId));
|
||||
verify(messagesManager).clear(aci, nextDeviceId);
|
||||
|
||||
verify(keysManager).buildWriteItemsForNewDevice(
|
||||
aci,
|
||||
Optional.of(pni),
|
||||
maybePni,
|
||||
nextDeviceId,
|
||||
aciSignedPreKey,
|
||||
Optional.of(pniSignedPreKey),
|
||||
maybePni.map(_ -> pniSignedPreKey),
|
||||
aciPqLastResortPreKey,
|
||||
Optional.of(pniPqLastResortPreKey));
|
||||
maybePni.map(_ ->pniPqLastResortPreKey));
|
||||
|
||||
verifyNoMoreInteractions(keysManager);
|
||||
final Device device = updatedAccountAndDevice.second();
|
||||
|
||||
assertEquals(deviceNameCiphertext, device.getName());
|
||||
assertTrue(device.getAuthTokenHash().verify(password));
|
||||
assertEquals(signalAgent, device.getUserAgent());
|
||||
assertEquals(Collections.emptySet(), device.getCapabilities());
|
||||
assertEquals(aciRegistrationId, device.getRegistrationId(IdentityType.ACI));
|
||||
assertEquals(pniRegistrationId, device.getRegistrationId(IdentityType.PNI));
|
||||
assertEquals(aciRegistrationId, device.getAccountRegistrationId());
|
||||
assertEquals(accountHasPhoneNumber ? Optional.of(pniRegistrationId) : Optional.empty(), device.getPhoneNumberIdentityRegistrationId());
|
||||
assertTrue(device.getFetchesMessages());
|
||||
assertNull(device.getApnId());
|
||||
assertNull(device.getGcmId());
|
||||
|
||||
@@ -66,6 +66,14 @@ public class AccountsHelper {
|
||||
return account;
|
||||
}
|
||||
|
||||
public static Account generateTestAccountNoPhoneNumber(List<Device> devices) {
|
||||
final Account account = new Account();
|
||||
account.setAccountIdentifier(UUID.randomUUID());
|
||||
devices.forEach(account::addDevice);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
public static void setupMockUpdate(final AccountsManager mockAccountsManager) {
|
||||
setupMockUpdate(mockAccountsManager, true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user