From cb5d3840d9be7109e652da8665d2f31b9d1e68d7 Mon Sep 17 00:00:00 2001 From: Katherine Yen Date: Tue, 20 Dec 2022 09:20:42 -0800 Subject: [PATCH] Add paymentActivation capability --- .../entities/UserCapabilities.java | 12 +++- .../textsecuregcm/storage/Account.java | 4 ++ .../textsecuregcm/storage/Device.java | 10 ++- .../storage/AccountsManagerTest.java | 2 +- .../controllers/DeviceControllerTest.java | 42 ++++++++---- .../tests/storage/AccountTest.java | 64 +++++++++++++------ .../tests/util/AccountsHelper.java | 1 + 7 files changed, 101 insertions(+), 34 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/UserCapabilities.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/UserCapabilities.java index a0c020650..d7ca26699 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/UserCapabilities.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/UserCapabilities.java @@ -17,7 +17,8 @@ public class UserCapabilities { account.isAnnouncementGroupSupported(), account.isChangeNumberSupported(), account.isStoriesSupported(), - account.isGiftBadgesSupported()); + account.isGiftBadgesSupported(), + false); // Hardcode to false until all clients support the flow } @JsonProperty("gv1-migration") @@ -38,6 +39,9 @@ public class UserCapabilities { @JsonProperty private boolean giftBadges; + @JsonProperty + private boolean paymentActivation; + public UserCapabilities() { } @@ -47,7 +51,8 @@ public class UserCapabilities { final boolean announcementGroup, final boolean changeNumber, final boolean stories, - final boolean giftBadges) { + final boolean giftBadges, + final boolean paymentActivation) { this.gv1Migration = gv1Migration; this.senderKey = senderKey; @@ -55,6 +60,7 @@ public class UserCapabilities { this.changeNumber = changeNumber; this.stories = stories; this.giftBadges = giftBadges; + this.paymentActivation = paymentActivation; } public boolean isGv1Migration() { @@ -80,4 +86,6 @@ public class UserCapabilities { public boolean isGiftBadges() { return giftBadges; } + + public boolean isPaymentActivation() { return paymentActivation; } } 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 4dc21e955..24cb27c29 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -221,6 +221,10 @@ public class Account { return allEnabledDevicesHaveCapability(DeviceCapabilities::isGiftBadges); } + public boolean isPaymentActivationSupported() { + return allEnabledDevicesHaveCapability(DeviceCapabilities::isPaymentActivation); + } + private boolean allEnabledDevicesHaveCapability(Predicate predicate) { 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 479be70d5..440493ef8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java @@ -277,12 +277,15 @@ public class Device { @JsonProperty private boolean giftBadges; + @JsonProperty + private boolean paymentActivation; + public DeviceCapabilities() { } public DeviceCapabilities(boolean storage, boolean transfer, final boolean senderKey, final boolean announcementGroup, final boolean changeNumber, - final boolean pni, final boolean stories, final boolean giftBadges) { + final boolean pni, final boolean stories, final boolean giftBadges, final boolean paymentActivation) { this.storage = storage; this.transfer = transfer; this.senderKey = senderKey; @@ -291,6 +294,7 @@ public class Device { this.pni = pni; this.stories = stories; this.giftBadges = giftBadges; + this.paymentActivation = paymentActivation; } public boolean isStorage() { @@ -324,5 +328,9 @@ public class Device { public boolean isGiftBadges() { return giftBadges; } + + public boolean isPaymentActivation() { + return paymentActivation; + } } } 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 5c4a10403..e078aa7a4 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java @@ -604,7 +604,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(hasStorage, false, false, false, false, false, false, false)); + new DeviceCapabilities(hasStorage, false, false, 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/tests/controllers/DeviceControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java index 74cccc140..c38b0df4b 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 @@ -38,6 +38,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; @@ -130,6 +131,7 @@ class DeviceControllerTest { when(account.isPniSupported()).thenReturn(true); when(account.isStoriesSupported()).thenReturn(true); when(account.isGiftBadgesSupported()).thenReturn(true); + when(account.isPaymentActivationSupported()).thenReturn(false); when(pendingDevicesManager.getCodeForNumber(AuthHelper.VALID_NUMBER)).thenReturn( Optional.of(new StoredVerificationCode("5678901", System.currentTimeMillis(), null, null, null))); @@ -304,7 +306,7 @@ class DeviceControllerTest { @Test void deviceDowngradeSenderKeyTest() { DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, false, true, - true, true, true, true); + true, true, true, true, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources @@ -316,7 +318,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); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources .getJerseyTest() @@ -331,7 +333,7 @@ class DeviceControllerTest { @Test void deviceDowngradeAnnouncementGroupTest() { DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, false, - true, true, true, true); + true, true, true, true, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources @@ -343,7 +345,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); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources .getJerseyTest() @@ -358,7 +360,7 @@ class DeviceControllerTest { @Test void deviceDowngradeChangeNumberTest() { DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, - false, true, true, true); + false, true, true, true, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources @@ -371,7 +373,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); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources .getJerseyTest() @@ -387,7 +389,7 @@ class DeviceControllerTest { @Test void deviceDowngradePniTest() { DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, - false, true, true); + false, true, true, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources @@ -399,7 +401,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); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources .getJerseyTest() @@ -415,7 +417,7 @@ class DeviceControllerTest { @Test void deviceDowngradeStoriesTest() { DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, - true, false, true); + true, false, true, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources @@ -428,7 +430,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); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources .getJerseyTest() @@ -443,7 +445,7 @@ class DeviceControllerTest { @Test void deviceDowngradeGiftBadgesTest() { - DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, false); + DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, false, true); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); Response response = resources .getJerseyTest() @@ -454,7 +456,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); + deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, true); accountAttributes = new AccountAttributes(false, 1234, null, null, true, deviceCapabilities); response = resources .getJerseyTest() @@ -467,6 +469,22 @@ class DeviceControllerTest { assertThat(response.getStatus()).isEqualTo(200); } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void deviceDowngradePaymentActivationTest(boolean paymentActivation) { + // Update when we start returning true value of capability & restricting downgrades + DeviceCapabilities deviceCapabilities = new DeviceCapabilities(true, true, true, true, true, true, true, true, paymentActivation); + 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(HttpHeaders.USER_AGENT, "Signal-Android/5.42.8675309 Android/30") + .put(Entity.entity(accountAttributes, MediaType.APPLICATION_JSON_TYPE)); + assertThat(response.getStatus()).isEqualTo(200); + } + @Test void deviceRemovalClearsMessagesAndKeys() { 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 3a36d8dc1..609de3604 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 @@ -64,6 +64,10 @@ class AccountTest { private final Device giftBadgesIncapableDevice = mock(Device.class); private final Device giftBadgesIncapableExpiredDevice = mock(Device.class); + private final Device paymentActivationCapableDevice = mock(Device.class); + private final Device paymentActivationIncapableDevice = mock(Device.class); + private final Device paymentActivationIncapableExpiredDevice = mock(Device.class); + @BeforeEach void setup() { when(oldMasterDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366)); @@ -87,77 +91,88 @@ class AccountTest { when(oldSecondaryDevice.getId()).thenReturn(2L); when(senderKeyCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, false, false)); + new DeviceCapabilities(true, true, true, false, false, false, false, false, false)); when(senderKeyCapableDevice.isEnabled()).thenReturn(true); when(senderKeyIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, false, false, false, false, false, false)); + new DeviceCapabilities(true, true, false, false, false, false, false, false, false)); when(senderKeyIncapableDevice.isEnabled()).thenReturn(true); when(senderKeyIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, false, false, false, false, false, false)); + new DeviceCapabilities(true, true, false, false, false, false, false, false, false)); when(senderKeyIncapableExpiredDevice.isEnabled()).thenReturn(false); when(announcementGroupCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, false, false, false, false)); + new DeviceCapabilities(true, true, true, true, false, false, false, false, false)); when(announcementGroupCapableDevice.isEnabled()).thenReturn(true); when(announcementGroupIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, false, false)); + new DeviceCapabilities(true, true, true, false, false, false, false, false, false)); when(announcementGroupIncapableDevice.isEnabled()).thenReturn(true); when(announcementGroupIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, false, false)); + new DeviceCapabilities(true, true, true, false, false, false, false, false, false)); when(announcementGroupIncapableExpiredDevice.isEnabled()).thenReturn(false); when(changeNumberCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, true, false, false, false)); + new DeviceCapabilities(true, true, true, false, true, false, false, false, false)); when(changeNumberCapableDevice.isEnabled()).thenReturn(true); when(changeNumberIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, false, false)); + new DeviceCapabilities(true, true, true, false, false, false, false, false, false)); when(changeNumberIncapableDevice.isEnabled()).thenReturn(true); when(changeNumberIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, false, false)); + new DeviceCapabilities(true, true, true, false, false, false, false, false, false)); when(changeNumberIncapableExpiredDevice.isEnabled()).thenReturn(false); when(pniCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, true, false, false)); + new DeviceCapabilities(true, true, true, false, false, true, false, false, false)); when(pniCapableDevice.isEnabled()).thenReturn(true); when(pniIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, false, false)); + new DeviceCapabilities(true, true, true, false, false, false, false, false, false)); when(pniIncapableDevice.isEnabled()).thenReturn(true); when(pniIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, false, false)); + new DeviceCapabilities(true, true, true, false, false, false, false, false, false)); when(pniIncapableExpiredDevice.isEnabled()).thenReturn(false); when(storiesCapableDevice.getId()).thenReturn(1L); when(storiesCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, true, false)); + new DeviceCapabilities(true, true, true, false, false, false, true, false, false)); when(storiesCapableDevice.isEnabled()).thenReturn(true); when(storiesCapableDevice.getId()).thenReturn(2L); when(storiesIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, false, false)); + new DeviceCapabilities(true, true, true, false, false, false, false, false, false)); when(storiesIncapableDevice.isEnabled()).thenReturn(true); when(storiesCapableDevice.getId()).thenReturn(3L); when(storiesIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, false, false, false, false, false)); + new DeviceCapabilities(true, true, true, false, false, false, false, false, false)); when(storiesIncapableExpiredDevice.isEnabled()).thenReturn(false); when(giftBadgesCapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, true)); + new DeviceCapabilities(true, true, true, true, true, true, true, true, false)); when(giftBadgesCapableDevice.isEnabled()).thenReturn(true); when(giftBadgesIncapableDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, false)); + new DeviceCapabilities(true, true, true, true, true, true, true, false, false)); when(giftBadgesIncapableDevice.isEnabled()).thenReturn(true); when(giftBadgesIncapableExpiredDevice.getCapabilities()).thenReturn( - new DeviceCapabilities(true, true, true, true, true, true, true, false)); + new DeviceCapabilities(true, true, true, true, true, true, true, false, false)); when(giftBadgesIncapableExpiredDevice.isEnabled()).thenReturn(false); + + when(paymentActivationCapableDevice.getCapabilities()).thenReturn( + new DeviceCapabilities(true, true, true, true, true, true, true, true, true)); + when(paymentActivationCapableDevice.isEnabled()).thenReturn(true); + when(paymentActivationIncapableDevice.getCapabilities()).thenReturn( + new DeviceCapabilities(true, true, true, true, true, true, true, false, false)); + when(paymentActivationIncapableDevice.isEnabled()).thenReturn(true); + when(paymentActivationIncapableExpiredDevice.getCapabilities()).thenReturn( + new DeviceCapabilities(true, true, true, true, true, true, true, false, false)); + when(paymentActivationIncapableExpiredDevice.isEnabled()).thenReturn(false); + } @Test @@ -322,6 +337,19 @@ class AccountTest { "1234".getBytes(StandardCharsets.UTF_8)).isGiftBadgesSupported()).isTrue(); } + @Test + void isPaymentActivationSupported() { + assertThat(AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), + List.of(paymentActivationCapableDevice), + "1234".getBytes(StandardCharsets.UTF_8)).isPaymentActivationSupported()).isTrue(); + assertThat(AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), + List.of(paymentActivationCapableDevice, paymentActivationIncapableDevice), + "1234".getBytes(StandardCharsets.UTF_8)).isPaymentActivationSupported()).isFalse(); + assertThat(AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), + List.of(paymentActivationCapableDevice, paymentActivationIncapableExpiredDevice), + "1234".getBytes(StandardCharsets.UTF_8)).isPaymentActivationSupported()).isTrue(); + } + @Test void stale() { final Account account = AccountsHelper.generateTestAccount("+14151234567", UUID.randomUUID(), UUID.randomUUID(), Collections.emptyList(), 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 48c3e237b..42083387f 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 @@ -126,6 +126,7 @@ public class AccountsHelper { case "isPniSupported" -> when(updatedAccount.isPniSupported()).thenAnswer(stubbing); case "isStoriesSupported" -> when(updatedAccount.isStoriesSupported()).thenAnswer(stubbing); case "isGiftBadgesSupported" -> when(updatedAccount.isGiftBadgesSupported()).thenAnswer(stubbing); + case "isPaymentActivationSupported" -> when(updatedAccount.isPaymentActivationSupported()).thenAnswer(stubbing); case "getEnabledDeviceCount" -> when(updatedAccount.getEnabledDeviceCount()).thenAnswer(stubbing); case "getRegistrationLock" -> when(updatedAccount.getRegistrationLock()).thenAnswer(stubbing); case "getIdentityKey" -> when(updatedAccount.getIdentityKey()).thenAnswer(stubbing);