From 58e3122dab6ecbaee131d92c390f6a38016cc2ee Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Tue, 18 Aug 2020 12:03:13 -0400 Subject: [PATCH] Add a discoverableByPhoneNumber account attribute. (SERVER-129) --- .../controllers/AccountController.java | 2 + .../entities/AccountAttributes.java | 28 +++++++++----- .../textsecuregcm/storage/Account.java | 11 ++++++ .../controllers/AccountControllerTest.java | 37 +++++++++++++++++-- .../controllers/DeviceControllerTest.java | 2 +- .../tests/storage/AccountTest.java | 12 ++++++ 6 files changed, 78 insertions(+), 14 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 3bac041ca..2cd4b9065 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -482,6 +482,7 @@ public class AccountController { account.setUnidentifiedAccessKey(attributes.getUnidentifiedAccessKey()); account.setUnrestrictedUnidentifiedAccess(attributes.isUnrestrictedUnidentifiedAccess()); account.setPayments(attributes.getPayments()); + account.setDiscoverableByPhoneNumber(attributes.isDiscoverableByPhoneNumber()); accounts.update(account); } @@ -629,6 +630,7 @@ public class AccountController { account.setUnidentifiedAccessKey(accountAttributes.getUnidentifiedAccessKey()); account.setUnrestrictedUnidentifiedAccess(accountAttributes.isUnrestrictedUnidentifiedAccess()); account.setPayments(accountAttributes.getPayments()); + account.setDiscoverableByPhoneNumber(accountAttributes.isDiscoverableByPhoneNumber()); if (accounts.create(account)) { newUserMeter.mark(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java index 9b47a00cc..3581a85d1 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java @@ -23,7 +23,9 @@ import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities; import org.whispersystems.textsecuregcm.storage.PaymentAddress; +import javax.annotation.Nullable; import java.util.List; +import java.util.Optional; public class AccountAttributes { @@ -58,22 +60,26 @@ public class AccountAttributes { @JsonProperty private DeviceCapabilities capabilities; + @JsonProperty + private boolean discoverableByPhoneNumber = true; + public AccountAttributes() {} @VisibleForTesting public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId, String pin) { - this(signalingKey, fetchesMessages, registrationId, null, pin, null, null); + this(signalingKey, fetchesMessages, registrationId, null, pin, null, null, true); } @VisibleForTesting - public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId, String name, String pin, String registrationLock, List payments) { - this.signalingKey = signalingKey; - this.fetchesMessages = fetchesMessages; - this.registrationId = registrationId; - this.name = name; - this.pin = pin; - this.registrationLock = registrationLock; - this.payments = payments; + public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId, String name, String pin, String registrationLock, List payments, boolean discoverableByPhoneNumber) { + this.signalingKey = signalingKey; + this.fetchesMessages = fetchesMessages; + this.registrationId = registrationId; + this.name = name; + this.pin = pin; + this.registrationLock = registrationLock; + this.payments = payments; + this.discoverableByPhoneNumber = discoverableByPhoneNumber; } public String getSignalingKey() { @@ -115,4 +121,8 @@ public class AccountAttributes { public List getPayments() { return payments; } + + public boolean isDiscoverableByPhoneNumber() { + return discoverableByPhoneNumber; + } } 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 729efa95a..66fc426ce 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -70,6 +70,9 @@ public class Account implements Principal { @JsonProperty("uua") private boolean unrestrictedUnidentifiedAccess; + @JsonProperty("inCds") + private boolean discoverableByPhoneNumber = true; + @JsonIgnore private Device authenticatedDevice; @@ -272,6 +275,14 @@ public class Account implements Principal { else throw new AssertionError(); } + public boolean isDiscoverableByPhoneNumber() { + return this.discoverableByPhoneNumber; + } + + public void setDiscoverableByPhoneNumber(final boolean discoverableByPhoneNumber) { + this.discoverableByPhoneNumber = discoverableByPhoneNumber; + } + // Principal implementation @Override diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java index 77afeda08..cbd99f81e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java @@ -507,8 +507,37 @@ public class AccountControllerTest { assertThat(result.getUuid()).isNotNull(); assertThat(result.isStorageCapable()).isFalse(); - verify(accountsManager, times(1)).create(isA(Account.class)); + final ArgumentCaptor accountArgumentCaptor = ArgumentCaptor.forClass(Account.class); + + verify(accountsManager, times(1)).create(accountArgumentCaptor.capture()); verify(directoryQueue, times(1)).deleteRegisteredUser(notNull(), eq(SENDER)); + + final Account createdAccount = accountArgumentCaptor.getValue(); + + assertThat(createdAccount.isDiscoverableByPhoneNumber()).isTrue(); + } + + @Test + public void testVerifyCodeUndiscoverable() throws Exception { + AccountCreationResult result = + resources.getJerseyTest() + .target(String.format("/v1/accounts/code/%s", "1234")) + .request() + .header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar")) + .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 2222, null, null, null, null, false), + MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class); + + assertThat(result.getUuid()).isNotNull(); + assertThat(result.isStorageCapable()).isFalse(); + + final ArgumentCaptor accountArgumentCaptor = ArgumentCaptor.forClass(Account.class); + + verify(accountsManager, times(1)).create(accountArgumentCaptor.capture()); + verify(directoryQueue, times(1)).deleteRegisteredUser(notNull(), eq(SENDER)); + + final Account createdAccount = accountArgumentCaptor.getValue(); + + assertThat(createdAccount.isDiscoverableByPhoneNumber()).isFalse(); } @Test @@ -580,7 +609,7 @@ public class AccountControllerTest { .target(String.format("/v1/accounts/code/%s", "666666")) .request() .header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar")) - .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null, null, Hex.toStringCondensed(registration_lock_key), null), + .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null, null, Hex.toStringCondensed(registration_lock_key), null, true), MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class); assertThat(result.getUuid()).isNotNull(); @@ -596,7 +625,7 @@ public class AccountControllerTest { .target(String.format("/v1/accounts/code/%s", "666666")) .request() .header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar")) - .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null, null, Hex.toStringCondensed(registration_lock_key), null), + .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null, null, Hex.toStringCondensed(registration_lock_key), null, true), MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class); assertThat(result.getUuid()).isNotNull(); @@ -630,7 +659,7 @@ public class AccountControllerTest { .target(String.format("/v1/accounts/code/%s", "666666")) .request() .header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar")) - .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null, null, null, null), + .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null, null, null, null, true), MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class); assertThat(result.getUuid()).isNotNull(); 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 c9b1fe563..d1bd4beba 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 @@ -213,7 +213,7 @@ public class DeviceControllerTest { .target("/v1/devices/5678901") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, "password1")) - .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, "this is a really long name that is longer than 80 characters it's so long that it's even longer than 204 characters. that's a lot of characters. we're talking lots and lots and lots of characters. 12345678", null, null, null), + .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, "this is a really long name that is longer than 80 characters it's so long that it's even longer than 204 characters. that's a lot of characters. we're talking lots and lots and lots of characters. 12345678", null, null, null, true), MediaType.APPLICATION_JSON_TYPE)); assertEquals(response.getStatus(), 422); 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 2041ec7ba..b251ad7dc 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 @@ -166,4 +166,16 @@ public class AccountTest { } } + @Test + public void testDiscoverableByPhoneNumber() { + final Account account = new Account("+14152222222", UUID.randomUUID(), Collections.singleton(recentMasterDevice), "1234".getBytes()); + + assertTrue("Freshly-loaded legacy accounts should be discoverable by phone number.", account.isDiscoverableByPhoneNumber()); + + account.setDiscoverableByPhoneNumber(false); + assertFalse(account.isDiscoverableByPhoneNumber()); + + account.setDiscoverableByPhoneNumber(true); + assertTrue(account.isDiscoverableByPhoneNumber()); + } }