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 dc65cf8d5..9700652c0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -213,7 +213,19 @@ public class MessageController { if (!isStory) { OptionalAccess.verify(source.map(AuthenticatedAccount::getAccount), accessKey, destination); } - assert (destination.isPresent()); + + boolean needsSync = !isSyncMessage && source.isPresent() && source.get().getAccount().getEnabledDeviceCount() > 1; + + // We return 200 when stories are sent to a non-existent account. Since story sends bypass OptionalAccess.verify + // we leak information about whether a destination UUID exists if we return any other code (e.g. 404) from + // these requests. + if (isStory && destination.isEmpty()) { + return Response.ok(new SendMessageResponse(needsSync)).build(); + } + + // if destination is empty we would either throw an exception in OptionalAccess.verify when isStory is false + // or else return a 200 response when isStory is true. + assert destination.isPresent(); if (source.isPresent() && !isSyncMessage) { checkMessageRateLimit(source.get(), destination.get(), userAgent); @@ -254,7 +266,6 @@ public class MessageController { } } - boolean needsSync = !isSyncMessage && source.isPresent() && source.get().getAccount().getEnabledDeviceCount() > 1; return Response.ok(new SendMessageResponse(needsSync)).build(); } catch (NoSuchUserException e) { throw new WebApplicationException(Response.status(404).build()); 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 5cc87eaaf..d76a62d6b 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java @@ -846,6 +846,23 @@ class MessageControllerTest { ); } + @Test + void testSendStoryToUnknownAccount() throws Exception { + String accessBytes = Base64.getEncoder().encodeToString(UNIDENTIFIED_ACCESS_BYTES); + String json = jsonFixture("fixtures/current_message_single_device.json"); + UUID unknownUUID = UUID.randomUUID(); + IncomingMessageList list = SystemMapper.getMapper().readValue(json, IncomingMessageList.class); + Response response = + resources.getJerseyTest() + .target(String.format("/v1/messages/%s", unknownUUID)) + .queryParam("story", "true") + .request() + .header(OptionalAccess.UNIDENTIFIED, accessBytes) + .put(Entity.entity(list, MediaType.APPLICATION_JSON_TYPE)); + + assertThat("200 masks unknown recipient", response.getStatus(), is(equalTo(200))); + } + private void checkBadMultiRecipientResponse(Response response, int expectedCode) throws Exception { assertThat("Unexpected response", response.getStatus(), is(equalTo(expectedCode))); verify(messageSender, never()).sendMessage(any(), any(), any(), anyBoolean());