From 4f42c10d60a58f245daa85ad9516562539c504ac Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Wed, 19 Jul 2023 10:55:16 -0400 Subject: [PATCH] Disallow sync messages to PNIs --- .../controllers/MessageController.java | 6 +++++- .../controllers/MessageControllerTest.java | 20 +++++++++++++++++++ .../tests/util/AccountsHelper.java | 1 + .../textsecuregcm/tests/util/AuthHelper.java | 18 +++++++++++++++++ .../fixtures/current_message_sync.json | 8 ++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 service/src/test/resources/fixtures/current_message_sync.json diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index adb208c9e..906c015fe 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -87,6 +87,7 @@ import org.whispersystems.textsecuregcm.entities.SendMultiRecipientMessageRespon import org.whispersystems.textsecuregcm.entities.SpamReport; import org.whispersystems.textsecuregcm.entities.StaleDevices; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; +import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.ServiceIdentifier; import org.whispersystems.textsecuregcm.limits.CardinalityEstimator; import org.whispersystems.textsecuregcm.limits.RateLimiters; @@ -138,7 +139,6 @@ public class MessageController { private static final String CONTENT_SIZE_DISTRIBUTION_NAME = name(MessageController.class, "messageContentSize"); private static final String OUTGOING_MESSAGE_LIST_SIZE_BYTES_DISTRIBUTION_NAME = name(MessageController.class, "outgoingMessageListSizeBytes"); private static final String RATE_LIMITED_MESSAGE_COUNTER_NAME = name(MessageController.class, "rateLimitedMessage"); - private static final String RATE_LIMITED_STORIES_COUNTER_NAME = name(MessageController.class, "rateLimitedStory"); private static final String REJECT_INVALID_ENVELOPE_TYPE = name(MessageController.class, "rejectInvalidEnvelopeType"); @@ -248,6 +248,10 @@ public class MessageController { try { boolean isSyncMessage = source.isPresent() && source.get().getAccount().isIdentifiedBy(destinationIdentifier); + if (isSyncMessage && destinationIdentifier.identityType() == IdentityType.PNI) { + throw new WebApplicationException(Status.FORBIDDEN); + } + Optional destination; if (!isSyncMessage) { 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 b6c4ae0ee..3a3fd963c 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java @@ -297,6 +297,26 @@ class MessageControllerTest { assertTrue(captor.getValue().getUrgent()); } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testSingleDeviceSync(final boolean sendToPni) throws Exception { + final ServiceIdentifier serviceIdentifier = sendToPni + ? new PniServiceIdentifier(AuthHelper.VALID_PNI_3) + : new AciServiceIdentifier(AuthHelper.VALID_UUID_3); + + try (final Response response = + resources.getJerseyTest() + .target(String.format("/v1/messages/%s", serviceIdentifier.toServiceIdentifierString())) + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_3, AuthHelper.VALID_PASSWORD_3_PRIMARY)) + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_sync.json"), + IncomingMessageList.class), + MediaType.APPLICATION_JSON_TYPE))) { + + assertThat(response.getStatus(), is(equalTo(sendToPni ? 403 : 200))); + } + } + @Test void testSingleDeviceCurrentNotUrgent() throws Exception { Response response = 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 3d942ea95..10454d612 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 @@ -119,6 +119,7 @@ public class AccountsHelper { switch (stubbing.getInvocation().getMethod().getName()) { case "getUuid" -> when(updatedAccount.getUuid()).thenAnswer(stubbing); case "getPhoneNumberIdentifier" -> when(updatedAccount.getPhoneNumberIdentifier()).thenAnswer(stubbing); + case "isIdentifiedBy" -> when(updatedAccount.isIdentifiedBy(stubbing.getInvocation().getArgument(0))).thenAnswer(stubbing); case "getNumber" -> when(updatedAccount.getNumber()).thenAnswer(stubbing); case "getUsername" -> when(updatedAccount.getUsernameHash()).thenAnswer(stubbing); case "getUsernameHash" -> when(updatedAccount.getUsernameHash()).thenAnswer(stubbing); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java index a47013751..33920e098 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Base64; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Optional; import java.util.Random; import java.util.UUID; @@ -34,7 +35,9 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.SaltedTokenHash; +import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.IdentityType; +import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; @@ -147,6 +150,12 @@ public class AuthHelper { when(VALID_ACCOUNT_3.getPrimaryDevice()).thenReturn(Optional.of(VALID_DEVICE_3_PRIMARY)); when(VALID_ACCOUNT_3.getDevice((byte) 2)).thenReturn(Optional.of(VALID_DEVICE_3_LINKED)); + when(VALID_ACCOUNT.getDevices()).thenReturn(List.of(VALID_DEVICE)); + when(VALID_ACCOUNT_TWO.getDevices()).thenReturn(List.of(VALID_DEVICE_TWO)); + when(DISABLED_ACCOUNT.getDevices()).thenReturn(List.of(DISABLED_DEVICE)); + when(UNDISCOVERABLE_ACCOUNT.getDevices()).thenReturn(List.of(UNDISCOVERABLE_DEVICE)); + when(VALID_ACCOUNT_3.getDevices()).thenReturn(List.of(VALID_DEVICE_3_PRIMARY, VALID_DEVICE_3_LINKED)); + when(VALID_ACCOUNT_TWO.hasEnabledLinkedDevice()).thenReturn(true); when(VALID_ACCOUNT.getNumber()).thenReturn(VALID_NUMBER); @@ -175,6 +184,15 @@ public class AuthHelper { when(UNDISCOVERABLE_ACCOUNT.isDiscoverableByPhoneNumber()).thenReturn(false); when(VALID_ACCOUNT_3.isDiscoverableByPhoneNumber()).thenReturn(true); + when(VALID_ACCOUNT.isIdentifiedBy(new AciServiceIdentifier(VALID_UUID))).thenReturn(true); + when(VALID_ACCOUNT.isIdentifiedBy(new PniServiceIdentifier(VALID_PNI))).thenReturn(true); + when(VALID_ACCOUNT_TWO.isIdentifiedBy(new AciServiceIdentifier(VALID_UUID_TWO))).thenReturn(true); + when(VALID_ACCOUNT_TWO.isIdentifiedBy(new PniServiceIdentifier(VALID_PNI_TWO))).thenReturn(true); + when(DISABLED_ACCOUNT.isIdentifiedBy(new AciServiceIdentifier(DISABLED_UUID))).thenReturn(true); + when(UNDISCOVERABLE_ACCOUNT.isIdentifiedBy(new AciServiceIdentifier(UNDISCOVERABLE_UUID))).thenReturn(true); + when(VALID_ACCOUNT_3.isIdentifiedBy(new AciServiceIdentifier(VALID_UUID_3))).thenReturn(true); + when(VALID_ACCOUNT_3.isIdentifiedBy(new PniServiceIdentifier(VALID_PNI_3))).thenReturn(true); + when(VALID_ACCOUNT.getIdentityKey(IdentityType.ACI)).thenReturn(VALID_IDENTITY); reset(ACCOUNTS_MANAGER); diff --git a/service/src/test/resources/fixtures/current_message_sync.json b/service/src/test/resources/fixtures/current_message_sync.json new file mode 100644 index 000000000..6207f5943 --- /dev/null +++ b/service/src/test/resources/fixtures/current_message_sync.json @@ -0,0 +1,8 @@ +{ + "messages" : [{ + "type" : 1, + "destinationDeviceId" : 2, + "content" : "Zm9vYmFyego", + "timestamp" : 1234 + }] +}