diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index 77c7b3cdf..bf2bdccde 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.controllers; @@ -63,17 +63,16 @@ public class DeviceController { private final Map maxDeviceConfiguration; public DeviceController(StoredVerificationCodeManager pendingDevices, - AccountsManager accounts, - MessagesManager messages, - Keys keys, - RateLimiters rateLimiters, - Map maxDeviceConfiguration) - { - this.pendingDevices = pendingDevices; - this.accounts = accounts; - this.messages = messages; - this.keys = keys; - this.rateLimiters = rateLimiters; + AccountsManager accounts, + MessagesManager messages, + Keys keys, + RateLimiters rateLimiters, + Map maxDeviceConfiguration) { + this.pendingDevices = pendingDevices; + this.accounts = accounts; + this.messages = messages; + this.keys = keys; + this.rateLimiters = rateLimiters; this.maxDeviceConfiguration = maxDeviceConfiguration; } @@ -151,12 +150,11 @@ public class DeviceController { @Path("/{verification_code}") @ChangesDeviceEnabledState public DeviceResponse verifyDeviceToken(@PathParam("verification_code") String verificationCode, - @HeaderParam("Authorization") BasicAuthorizationHeader authorizationHeader, - @HeaderParam("User-Agent") String userAgent, - @NotNull @Valid AccountAttributes accountAttributes, - @Context ContainerRequest containerRequest) - throws RateLimitExceededException, DeviceLimitExceededException - { + @HeaderParam("Authorization") BasicAuthorizationHeader authorizationHeader, + @HeaderParam("User-Agent") String userAgent, + @NotNull @Valid AccountAttributes accountAttributes, + @Context ContainerRequest containerRequest) + throws RateLimitExceededException, DeviceLimitExceededException { String number = authorizationHeader.getUsername(); String password = authorizationHeader.getPassword(); @@ -241,6 +239,7 @@ public class DeviceController { private boolean isCapabilityDowngrade(Account account, DeviceCapabilities capabilities, String userAgent) { boolean isDowngrade = false; + isDowngrade |= account.isPniSupported() && !capabilities.isPni(); isDowngrade |= account.isChangeNumberSupported() && !capabilities.isChangeNumber(); isDowngrade |= account.isAnnouncementGroupSupported() && !capabilities.isAnnouncementGroup(); isDowngrade |= account.isSenderKeySupported() && !capabilities.isSenderKey(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java index 93a06b178..fc4117fc7 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.storage; @@ -17,13 +17,13 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock; import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.util.Util; -import javax.annotation.Nullable; public class Account { @@ -234,6 +234,14 @@ public class Account { .allMatch(device -> device.getCapabilities() != null && device.getCapabilities().isChangeNumber()); } + public boolean isPniSupported() { + requireNotStale(); + + return devices.stream() + .filter(Device::isEnabled) + .allMatch(device -> device.getCapabilities() != null && device.getCapabilities().isPni()); + } + public boolean isEnabled() { requireNotStale(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java index d187fe428..9fc88bdd6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java @@ -1,18 +1,17 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.storage; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; import org.whispersystems.textsecuregcm.entities.SignedPreKey; import org.whispersystems.textsecuregcm.util.Util; -import javax.annotation.Nullable; -import java.util.concurrent.TimeUnit; - public class Device { public static final long MASTER_ID = 1; @@ -71,27 +70,26 @@ public class Device { public Device() {} public Device(long id, String name, String authToken, String salt, - String gcmId, String apnId, - String voipApnId, boolean fetchesMessages, - int registrationId, SignedPreKey signedPreKey, - long lastSeen, long created, String userAgent, - long uninstalledFeedback, DeviceCapabilities capabilities) - { - this.id = id; - this.name = name; - this.authToken = authToken; - this.salt = salt; - this.gcmId = gcmId; - this.apnId = apnId; - this.voipApnId = voipApnId; - this.fetchesMessages = fetchesMessages; - this.registrationId = registrationId; - this.signedPreKey = signedPreKey; - this.lastSeen = lastSeen; - this.created = created; - this.userAgent = userAgent; - this.uninstalledFeedback = uninstalledFeedback; - this.capabilities = capabilities; + String gcmId, String apnId, + String voipApnId, boolean fetchesMessages, + int registrationId, SignedPreKey signedPreKey, + long lastSeen, long created, String userAgent, + long uninstalledFeedback, DeviceCapabilities capabilities) { + this.id = id; + this.name = name; + this.authToken = authToken; + this.salt = salt; + this.gcmId = gcmId; + this.apnId = apnId; + this.voipApnId = voipApnId; + this.fetchesMessages = fetchesMessages; + this.registrationId = registrationId; + this.signedPreKey = signedPreKey; + this.lastSeen = lastSeen; + this.created = created; + this.userAgent = userAgent; + this.uninstalledFeedback = uninstalledFeedback; + this.capabilities = capabilities; } public String getApnId() { @@ -175,7 +173,8 @@ public class Device { return new AuthenticationCredentials(authToken, salt); } - public @Nullable DeviceCapabilities getCapabilities() { + @Nullable + public DeviceCapabilities getCapabilities() { return capabilities; } @@ -293,10 +292,15 @@ public class Device { @JsonProperty private boolean changeNumber; - public DeviceCapabilities() {} + @JsonProperty + private boolean pni; + + public DeviceCapabilities() { + } public DeviceCapabilities(boolean gv2, final boolean gv2_2, final boolean gv2_3, boolean storage, boolean transfer, - boolean gv1Migration, final boolean senderKey, final boolean announcementGroup, final boolean changeNumber) { + boolean gv1Migration, final boolean senderKey, final boolean announcementGroup, final boolean changeNumber, + final boolean pni) { this.gv2 = gv2; this.gv2_2 = gv2_2; this.gv2_3 = gv2_3; @@ -306,6 +310,7 @@ public class Device { this.senderKey = senderKey; this.announcementGroup = announcementGroup; this.changeNumber = changeNumber; + this.pni = pni; } public boolean isGv2() { @@ -343,5 +348,9 @@ public class Device { public boolean isChangeNumber() { return changeNumber; } + + public boolean isPni() { + return pni; + } } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java index c92a8280d..2bed1491a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -132,22 +132,22 @@ class MessageControllerTest { add(new Device(1, null, "foo", "bar", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true, false, false, true, true, false, - false, false, false))); + false, false, false, false))); }}; Set multiDeviceList = new HashSet<>() {{ add(new Device(1, null, "foo", "bar", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true, false, false, true, false, false, - false, false, false))); + false, false, false, false))); add(new Device(2, null, "foo", "bar", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(true, false, false, true, false, false, - false, false, false))); + false, false, false, false))); add(new Device(3, null, "foo", "bar", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), "Test", 0, new Device.DeviceCapabilities(false, false, false, false, false, false, - false, false, false))); + false, false, false, false))); }}; Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, SINGLE_DEVICE_UUID, SINGLE_DEVICE_PNI, singleDeviceList, "1234".getBytes()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java index 66a00b0ab..4a4944a7f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -182,7 +182,7 @@ class AccountsManagerConcurrentModificationIntegrationTest { random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt(), 0, new Device.DeviceCapabilities(random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), - random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), + random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean()))); }); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java index 072cd4e59..4726639c4 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -579,7 +579,7 @@ class AccountsManagerTest { @ValueSource(booleans = {true, false}) void testCreateWithStorageCapability(final boolean hasStorage) throws InterruptedException { final AccountAttributes attributes = new AccountAttributes(false, 0, null, null, true, - new DeviceCapabilities(false, false, false, hasStorage, false, false, false, false, false)); + new DeviceCapabilities(false, false, false, hasStorage, false, false, false, false, false, false)); final Account account = accountsManager.create("+18005550123", "password", null, attributes, new ArrayList<>()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java index 3c9b05c23..37b4132fc 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -830,11 +830,17 @@ class AccountsTest { } private Device generateDevice(long id) { - Random random = new Random(System.currentTimeMillis()); - SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(), "testSignature-" + random.nextInt()); - return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), "testSalt-" + random.nextInt(), - "testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt() , 0, new Device.DeviceCapabilities(random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), - random.nextBoolean(), random.nextBoolean(), random.nextBoolean())); + Random random = new Random(System.currentTimeMillis()); + SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(), + "testSignature-" + random.nextInt()); + return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), + "testSalt-" + random.nextInt(), + "testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), + random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), + "testUserAgent-" + random.nextInt(), 0, + new Device.DeviceCapabilities(random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), + random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), + random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean())); } private Account generateAccount(String number, UUID uuid, final UUID pni) { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeviceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeviceTest.java index d72c16063..50769140e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeviceTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeviceTest.java @@ -72,7 +72,7 @@ class DeviceTest { final boolean gv2_2Capability, final boolean gv2_3Capability, final boolean expectGv2Supported) { final Device.DeviceCapabilities capabilities = new Device.DeviceCapabilities(gv2Capability, gv2_2Capability, gv2_3Capability, false, false, false, - false, false, false); + false, false, false, false); final Device device = new Device(master ? 1 : 2, "test", "auth-token", "salt", null, apnId, null, false, 1, null, 0, 0, "user-agent", 0, capabilities); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java index cce33dfa6..a1e7caa92 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.tests.controllers; @@ -128,6 +128,7 @@ class DeviceControllerTest { when(account.isSenderKeySupported()).thenReturn(true); when(account.isAnnouncementGroupSupported()).thenReturn(true); when(account.isChangeNumberSupported()).thenReturn(true); + when(account.isPniSupported()).thenReturn(true); when(pendingDevicesManager.getCodeForNumber(AuthHelper.VALID_NUMBER)).thenReturn( Optional.of(new StoredVerificationCode("5678901", System.currentTimeMillis(), null, null))); @@ -304,7 +305,7 @@ class DeviceControllerTest { void deviceDowngradeCapabilitiesTest(final String userAgent, final boolean gv2, final boolean gv2_2, final boolean gv2_3, final int expectedStatus) { DeviceCapabilities deviceCapabilities = new DeviceCapabilities(gv2, gv2_2, gv2_3, true, false, true, true, true, - true); + true, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources.getJerseyTest() .target("/v1/devices/5678901") @@ -344,7 +345,8 @@ class DeviceControllerTest { @Test void deviceDowngradeGv1MigrationTest() { - DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, false, false, true, true, true); + DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, false, false, true, true, + true, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources.getJerseyTest() .target("/v1/devices/5678901") @@ -355,7 +357,7 @@ class DeviceControllerTest { assertThat(response.getStatus()).isEqualTo(409); - deviceCapabilities = new DeviceCapabilities(true, true, true, true, false, true, true, true, true); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, false, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources.getJerseyTest() .target("/v1/devices/5678901") @@ -369,7 +371,8 @@ class DeviceControllerTest { @Test void deviceDowngradeSenderKeyTest() { - DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, false, true, true); + DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, false, true, + true, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources @@ -381,7 +384,7 @@ class DeviceControllerTest { .put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE)); assertThat(response.getStatus()).isEqualTo(409); - deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources .getJerseyTest() @@ -395,7 +398,8 @@ class DeviceControllerTest { @Test void deviceDowngradeAnnouncementGroupTest() { - DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, false, true); + DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, false, + true, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources @@ -407,7 +411,7 @@ class DeviceControllerTest { .put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE)); assertThat(response.getStatus()).isEqualTo(409); - deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources .getJerseyTest() @@ -421,7 +425,36 @@ class DeviceControllerTest { @Test void deviceDowngradeChangeNumberTest() { - DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, false); + DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, false, true); + AccountAttributes accountAttributes = + new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); + Response response = resources + .getJerseyTest() + .target("/v1/devices/5678901") + .request() + .header("Authorization", + AuthHelper.getProvisioningAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .header("User-Agent", "Signal-Android/5.42.8675309 Android/30") + .put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE)); + assertThat(response.getStatus()).isEqualTo(409); + + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true); + accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); + response = resources + .getJerseyTest() + .target("/v1/devices/5678901") + .request() + .header("Authorization", + AuthHelper.getProvisioningAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .header("User-Agent", "Signal-Android/5.42.8675309 Android/30") + .put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE)); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Test + void deviceDowngradePniTest() { + DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, + false); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources @@ -433,7 +466,7 @@ class DeviceControllerTest { .put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE)); assertThat(response.getStatus()).isEqualTo(409); - deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources .getJerseyTest() diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java index a1ad5f6c9..a4a4edbb0 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -58,6 +58,10 @@ class AccountTest { private final Device changeNumberIncapableDevice = mock(Device.class); private final Device changeNumberIncapableExpiredDevice = mock(Device.class); + private final Device pniCapableDevice = mock(Device.class); + private final Device pniIncapableDevice = mock(Device.class); + private final Device pniIncapableExpiredDevice = mock(Device.class); + @BeforeEach void setup() { when(oldMasterDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366)); @@ -93,52 +97,64 @@ class AccountTest { when(gv2IncapableExpiredDevice.isEnabled()).thenReturn(false); when(gv1MigrationCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, false, false, false)); + new DeviceCapabilities(true, true, true, true, true, true, false, false, false, false)); when(gv1MigrationCapableDevice.isEnabled()).thenReturn(true); when(gv1MigrationIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, false, false, false, false)); + new DeviceCapabilities(true, true, true, true, true, false, false, false, false, false)); when(gv1MigrationIncapableDevice.isEnabled()).thenReturn(true); when(gv1MigrationIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, false, false, false, false)); + new DeviceCapabilities(true, true, true, true, true, false, false, false, false, false)); when(gv1MigrationIncapableExpiredDevice.isEnabled()).thenReturn(false); when(senderKeyCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, false, false)); + new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false)); when(senderKeyCapableDevice.isEnabled()).thenReturn(true); when(senderKeyIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, false, false, false)); + new DeviceCapabilities(true, true, true, true, true, true, false, false, false, false)); when(senderKeyIncapableDevice.isEnabled()).thenReturn(true); when(senderKeyIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, false, false, false)); + new DeviceCapabilities(true, true, true, true, true, true, false, false, false, false)); when(senderKeyIncapableExpiredDevice.isEnabled()).thenReturn(false); when(announcementGroupCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, true, false)); + new DeviceCapabilities(true, true, true, true, true, true, true, true, false, false)); when(announcementGroupCapableDevice.isEnabled()).thenReturn(true); when(announcementGroupIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, false, false)); + new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false)); when(announcementGroupIncapableDevice.isEnabled()).thenReturn(true); when(announcementGroupIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, false, false)); + new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false)); when(announcementGroupIncapableExpiredDevice.isEnabled()).thenReturn(false); when(changeNumberCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, false, true)); + new DeviceCapabilities(true, true, true, true, true, true, true, false, true, false)); when(changeNumberCapableDevice.isEnabled()).thenReturn(true); when(changeNumberIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, false, false)); + new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false)); when(changeNumberIncapableDevice.isEnabled()).thenReturn(true); when(changeNumberIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, false, false)); + new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false)); when(changeNumberIncapableExpiredDevice.isEnabled()).thenReturn(false); + + when(pniCapableDevice.getCapabilities()).thenReturn( + new DeviceCapabilities(true, true, true, true, true, true, true, false, false, true)); + when(pniCapableDevice.isEnabled()).thenReturn(true); + + when(pniIncapableDevice.getCapabilities()).thenReturn( + new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false)); + when(pniIncapableDevice.isEnabled()).thenReturn(true); + + when(pniIncapableExpiredDevice.getCapabilities()).thenReturn( + new DeviceCapabilities(true, true, true, true, true, true, true, false, false, false)); + when(pniIncapableExpiredDevice.isEnabled()).thenReturn(false); } @Test @@ -317,6 +333,19 @@ class AccountTest { "1234".getBytes(StandardCharsets.UTF_8)).isChangeNumberSupported()).isTrue(); } + @Test + void isPniSupported() { + assertThat(new Account("+18005551234", UUID.randomUUID(), + UUID.randomUUID(), Set.of(pniCapableDevice), + "1234".getBytes(StandardCharsets.UTF_8)).isPniSupported()).isTrue(); + assertThat(new Account("+18005551234", UUID.randomUUID(), + UUID.randomUUID(), Set.of(pniCapableDevice, pniIncapableDevice), + "1234".getBytes(StandardCharsets.UTF_8)).isPniSupported()).isFalse(); + assertThat(new Account("+18005551234", UUID.randomUUID(), + UUID.randomUUID(), Set.of(pniCapableDevice, pniIncapableExpiredDevice), + "1234".getBytes(StandardCharsets.UTF_8)).isPniSupported()).isTrue(); + } + @Test void stale() { final Account account = new Account("+14151234567", UUID.randomUUID(), UUID.randomUUID(), Collections.emptySet(), new byte[0]); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AccountsHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AccountsHelper.java index 6c0702f23..08702280a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AccountsHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AccountsHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013-2022 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -153,6 +153,10 @@ public class AccountsHelper { when(updatedAccount.isChangeNumberSupported()).thenAnswer(stubbing); break; } + case "isPniSupported": { + when(updatedAccount.isPniSupported()).thenAnswer(stubbing); + break; + } case "getEnabledDeviceCount": { when(updatedAccount.getEnabledDeviceCount()).thenAnswer(stubbing); break; @@ -188,7 +192,6 @@ public class AccountsHelper { account.markStale(); } - return updatedAccount; }