From bfd2c32d4ec0e0c899167134731f521f25501a1b Mon Sep 17 00:00:00 2001 From: Ehren Kret Date: Wed, 12 May 2021 18:08:22 -0500 Subject: [PATCH] Add sender key capability --- .../controllers/ProfileController.java | 18 +++++++++++++++--- .../entities/UserCapabilities.java | 10 +++++++++- .../textsecuregcm/storage/Account.java | 6 ++++++ .../textsecuregcm/storage/Device.java | 11 ++++++++++- .../storage/AccountsDynamoDbTest.java | 3 ++- .../textsecuregcm/storage/DeviceTest.java | 3 ++- .../controllers/DeviceControllerTest.java | 8 +++++--- .../controllers/MessageControllerTest.java | 12 ++++++++---- .../controllers/ProfileControllerTest.java | 14 ++++++++++++++ .../tests/storage/AccountTest.java | 9 ++++++--- .../tests/storage/AccountsTest.java | 3 ++- 11 files changed, 79 insertions(+), 18 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index 2a4fd1f74..cd852e925 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -231,6 +231,10 @@ public class ProfileController { Optional credential = getProfileCredential(credentialRequest, profile, uuid); + final UserCapabilities userCapabilities = new UserCapabilities( + accountProfile.get().isGroupsV2Supported(), + accountProfile.get().isGv1MigrationSupported(), + accountProfile.get().isSenderKeySupported()); return Optional.of(new Profile(name, about, aboutEmoji, @@ -239,7 +243,7 @@ public class ProfileController { accountProfile.get().getIdentityKey(), UnidentifiedAccessChecksum.generateFor(accountProfile.get().getUnidentifiedAccessKey()), accountProfile.get().isUnrestrictedUnidentifiedAccess(), - new UserCapabilities(accountProfile.get().isGroupsV2Supported(), accountProfile.get().isGv1MigrationSupported()), + userCapabilities, username.orElse(null), null, credential.orElse(null))); @@ -271,6 +275,10 @@ public class ProfileController { throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build()); } + final UserCapabilities userCapabilities = new UserCapabilities( + accountProfile.get().isGroupsV2Supported(), + accountProfile.get().isGv1MigrationSupported(), + accountProfile.get().isSenderKeySupported()); return new Profile(accountProfile.get().getProfileName(), null, null, @@ -279,7 +287,7 @@ public class ProfileController { accountProfile.get().getIdentityKey(), UnidentifiedAccessChecksum.generateFor(accountProfile.get().getUnidentifiedAccessKey()), accountProfile.get().isUnrestrictedUnidentifiedAccess(), - new UserCapabilities(accountProfile.get().isGroupsV2Supported(), accountProfile.get().isGv1MigrationSupported()), + userCapabilities, username, accountProfile.get().getUuid(), null); @@ -346,6 +354,10 @@ public class ProfileController { username = usernamesManager.get(accountProfile.get().getUuid()); } + final UserCapabilities userCapabilities = new UserCapabilities( + accountProfile.get().isGroupsV2Supported(), + accountProfile.get().isGv1MigrationSupported(), + accountProfile.get().isSenderKeySupported()); return new Profile(accountProfile.get().getProfileName(), null, null, @@ -354,7 +366,7 @@ public class ProfileController { accountProfile.get().getIdentityKey(), UnidentifiedAccessChecksum.generateFor(accountProfile.get().getUnidentifiedAccessKey()), accountProfile.get().isUnrestrictedUnidentifiedAccess(), - new UserCapabilities(accountProfile.get().isGroupsV2Supported(), accountProfile.get().isGv1MigrationSupported()), + userCapabilities, username.orElse(null), null, null); 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 13acc7f9b..c5b71c60c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/UserCapabilities.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/UserCapabilities.java @@ -14,11 +14,15 @@ public class UserCapabilities { @JsonProperty("gv1-migration") private boolean gv1Migration; + @JsonProperty + private boolean senderKey; + public UserCapabilities() {} - public UserCapabilities(boolean gv2, boolean gv1Migration) { + public UserCapabilities(boolean gv2, boolean gv1Migration, final boolean senderKey) { this.gv2 = gv2; this.gv1Migration = gv1Migration; + this.senderKey = senderKey; } public boolean isGv2() { @@ -28,4 +32,8 @@ public class UserCapabilities { public boolean isGv1Migration() { return gv1Migration; } + + public boolean isSenderKey() { + return senderKey; + } } 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 c9aa46f71..3dbb3c3d5 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -145,6 +145,12 @@ public class Account implements Principal { .allMatch(device -> device.getCapabilities() != null && device.getCapabilities().isGv1Migration()); } + public boolean isSenderKeySupported() { + return devices.stream() + .filter(Device::isEnabled) + .allMatch(device -> device.getCapabilities() != null && device.getCapabilities().isSenderKey()); + } + public boolean isEnabled() { return getMasterDevice().map(Device::isEnabled).orElse(false); } 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 e7e65e9da..0ef120634 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java @@ -273,15 +273,20 @@ public class Device { @JsonProperty("gv1-migration") private boolean gv1Migration; + @JsonProperty + private boolean senderKey; + public DeviceCapabilities() {} - public DeviceCapabilities(boolean gv2, final boolean gv2_2, final boolean gv2_3, boolean storage, boolean transfer, boolean gv1Migration) { + public DeviceCapabilities(boolean gv2, final boolean gv2_2, final boolean gv2_3, boolean storage, boolean transfer, + boolean gv1Migration, final boolean senderKey) { this.gv2 = gv2; this.gv2_2 = gv2_2; this.gv2_3 = gv2_3; this.storage = storage; this.transfer = transfer; this.gv1Migration = gv1Migration; + this.senderKey = senderKey; } public boolean isGv2() { @@ -307,5 +312,9 @@ public class Device { public boolean isGv1Migration() { return gv1Migration; } + + public boolean isSenderKey() { + return senderKey; + } } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsDynamoDbTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsDynamoDbTest.java index 83b96c1a1..e01d17fea 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsDynamoDbTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsDynamoDbTest.java @@ -382,7 +382,8 @@ class AccountsDynamoDbTest { 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())); + "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(), + false)); } private Account generateAccount(String number, UUID uuid) { 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 dde3c513f..4ba27cb09 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeviceTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeviceTest.java @@ -69,7 +69,8 @@ public class DeviceTest { @Test @Parameters(method = "argumentsForTestIsGroupsV2Supported") public void testIsGroupsV2Supported(final boolean master, final String apnId, final boolean gv2Capability, 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); + final Device.DeviceCapabilities capabilities = new Device.DeviceCapabilities(gv2Capability, gv2_2Capability, gv2_3Capability, 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 08d468708..c30631ba0 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 @@ -221,7 +221,8 @@ public class DeviceControllerTest { @Test @Parameters(method = "argumentsForDeviceDowngradeCapabilitiesTest") public void deviceDowngradeCapabilitiesTest(final String userAgent, final boolean gv2, final boolean gv2_2, final boolean gv2_3, final int expectedStatus) throws Exception { - Device.DeviceCapabilities deviceCapabilities = new Device.DeviceCapabilities(gv2, gv2_2, gv2_3, true, false, true); + Device.DeviceCapabilities deviceCapabilities = new Device.DeviceCapabilities(gv2, gv2_2, gv2_3, true, false, true, + false); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, null, true, deviceCapabilities); Response response = resources.getJerseyTest() .target("/v1/devices/5678901") @@ -261,7 +262,8 @@ public class DeviceControllerTest { @Test public void deviceDowngradeGv1MigrationTest() { - Device.DeviceCapabilities deviceCapabilities = new Device.DeviceCapabilities(true, true, true, true, false, false); + Device.DeviceCapabilities deviceCapabilities = new Device.DeviceCapabilities(true, true, true, true, false, false, + false); AccountAttributes accountAttributes = new AccountAttributes(false, 1234, null, null, null, true, deviceCapabilities); Response response = resources.getJerseyTest() .target("/v1/devices/5678901") @@ -272,7 +274,7 @@ public class DeviceControllerTest { assertThat(response.getStatus()).isEqualTo(409); - deviceCapabilities = new Device.DeviceCapabilities(true, true, true, true, false, true); + deviceCapabilities = new Device.DeviceCapabilities(true, true, true, true, false, true, false); accountAttributes = new AccountAttributes(false, 1234, null, null, null, true, deviceCapabilities); response = resources.getJerseyTest() .target("/v1/devices/5678901") diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java index bda5f2926..f5726c81b 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java @@ -146,16 +146,20 @@ class MessageControllerTest { void setup() throws Exception { Set singleDeviceList = new HashSet() {{ 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))); + "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))); }}; 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))); + "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))); 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))); + "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))); 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))); + "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))); }}; Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, SINGLE_DEVICE_UUID, singleDeviceList, "1234".getBytes()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java index 1ce9a5f13..a9217bec9 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java @@ -124,6 +124,7 @@ public class ProfileControllerTest { when(profileAccount.isEnabled()).thenReturn(true); when(profileAccount.isGroupsV2Supported()).thenReturn(false); when(profileAccount.isGv1MigrationSupported()).thenReturn(false); + when(profileAccount.isSenderKeySupported()).thenReturn(false); when(profileAccount.getCurrentProfileVersion()).thenReturn(Optional.empty()); Account capabilitiesAccount = mock(Account.class); @@ -134,6 +135,7 @@ public class ProfileControllerTest { when(capabilitiesAccount.isEnabled()).thenReturn(true); when(capabilitiesAccount.isGroupsV2Supported()).thenReturn(true); when(capabilitiesAccount.isGv1MigrationSupported()).thenReturn(true); + when(capabilitiesAccount.isSenderKeySupported()).thenReturn(true); when(accountsManager.get(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount)); when(accountsManager.get(AuthHelper.VALID_UUID_TWO)).thenReturn(Optional.of(profileAccount)); @@ -271,6 +273,18 @@ public class ProfileControllerTest { assertThat(profile.getCapabilities().isGv2()).isTrue(); assertThat(profile.getCapabilities().isGv1Migration()).isTrue(); + assertThat(profile.getCapabilities().isSenderKey()).isTrue(); + + profile = resources + .getJerseyTest() + .target("/v1/profile/" + AuthHelper.VALID_NUMBER_TWO) + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER_TWO, AuthHelper.VALID_PASSWORD_TWO)) + .get(Profile.class); + + assertThat(profile.getCapabilities().isGv2()).isFalse(); + assertThat(profile.getCapabilities().isGv1Migration()).isFalse(); + assertThat(profile.getCapabilities().isSenderKey()).isFalse(); } @Test 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 778428cff..9956576fb 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 @@ -72,13 +72,16 @@ public class AccountTest { when(gv2IncapableExpiredDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)); when(gv2IncapableExpiredDevice.isEnabled()).thenReturn(false); - when(gv1MigrationCapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(true, true, true, true, true, true)); + when(gv1MigrationCapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(true, true, true, true, true, true, + false)); when(gv1MigrationCapableDevice.isEnabled()).thenReturn(true); - when(gv1MigrationIncapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(true, true, true, true, true, false)); + when(gv1MigrationIncapableDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(true, true, true, true, true, false, + false)); when(gv1MigrationIncapableDevice.isEnabled()).thenReturn(true); - when(gv1MigrationIncapableExpiredDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(true, true, true, true, true, false)); + when(gv1MigrationIncapableExpiredDevice.getCapabilities()).thenReturn(new Device.DeviceCapabilities(true, true, true, true, true, false, + false)); when(gv1MigrationIncapableExpiredDevice.isEnabled()).thenReturn(false); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsTest.java index 8e4fb94fb..5ef0182a4 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsTest.java @@ -308,7 +308,8 @@ public class AccountsTest { 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())); + "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(), + false)); } private Account generateAccount(String number, UUID uuid) {