Introduce an emergency "read only" mode for messages

This commit is contained in:
Jon Chambers
2026-05-11 11:43:41 -04:00
committed by GitHub
parent 4485e26562
commit f045e3ee0f
17 changed files with 602 additions and 82 deletions
@@ -743,7 +743,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
final AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
final MessageSender messageSender = new MessageSender(messagesManager, pushNotificationManager);
final MessageSender messageSender = new MessageSender(messagesManager, pushNotificationManager, dynamicConfigurationManager);
final ReceiptSender receiptSender = new ReceiptSender(accountsManager, messageSender, receiptSenderExecutor);
final CloudflareTurnCredentialsManager cloudflareTurnCredentialsManager = new CloudflareTurnCredentialsManager(
config.getTurnConfiguration().cloudflare().apiToken().value(),
@@ -12,7 +12,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.signal.chat.calling.GetTurnCredentialsResponseOrBuilder;
import org.whispersystems.textsecuregcm.limits.RateLimiterConfig;
public class DynamicConfiguration {
@@ -45,6 +44,10 @@ public class DynamicConfiguration {
@Valid
DynamicMessagePersisterConfiguration messagePersister = new DynamicMessagePersisterConfiguration();
@JsonProperty
@Valid
DynamicMessageDeliveryConfiguration messageDelivery = new DynamicMessageDeliveryConfiguration();
@JsonProperty
@Valid
DynamicRegistrationConfiguration registrationConfiguration = new DynamicRegistrationConfiguration(false);
@@ -111,6 +114,10 @@ public class DynamicConfiguration {
return messagePersister;
}
public DynamicMessageDeliveryConfiguration getMessageDeliveryConfiguration() {
return messageDelivery;
}
public DynamicRegistrationConfiguration getRegistrationConfiguration() {
return registrationConfiguration;
}
@@ -0,0 +1,18 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DynamicMessageDeliveryConfiguration {
@JsonProperty
private boolean readOnly = false;
public boolean isReadOnly() {
return readOnly;
}
}
@@ -22,6 +22,7 @@ import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.ServiceUnavailableException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.Context;
@@ -121,6 +122,8 @@ public class AccountControllerV2 {
throw new BadRequestException(e);
} catch (final MessageTooLargeException e) {
throw new WebApplicationException(Response.Status.REQUEST_ENTITY_TOO_LARGE);
} catch (final MessageDeliveryNotAllowedException e) {
throw new ServiceUnavailableException();
}
}
@@ -33,6 +33,7 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.ServiceUnavailableException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.Context;
@@ -471,6 +472,8 @@ public class MessageController {
}
} catch (final MessageTooLargeException e) {
throw new WebApplicationException(Status.REQUEST_ENTITY_TOO_LARGE);
} catch (final MessageDeliveryNotAllowedException e) {
throw new ServiceUnavailableException();
}
}
@@ -724,6 +727,8 @@ public class MessageController {
.type(MediaType.APPLICATION_JSON)
.entity(accountStaleDevices)
.build());
} catch (final MessageDeliveryNotAllowedException e) {
throw new ServiceUnavailableException();
}
}
@@ -0,0 +1,11 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.controllers;
import org.whispersystems.textsecuregcm.util.NoStackTraceException;
public class MessageDeliveryNotAllowedException extends NoStackTraceException {
}
@@ -158,6 +158,10 @@ public class GrpcExceptions {
return StatusProto.toStatusRuntimeException(builder.build());
}
public static StatusRuntimeException unavailable() {
return unavailable(null);
}
/// There was an internal error processing the RPC. The client should retry the request with exponential backoff.
///
/// @return A [StatusRuntimeException] encoding the error
@@ -11,6 +11,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.google.protobuf.Empty;
import io.grpc.Status;
import org.signal.chat.errors.FailedUnidentifiedAuthorization;
import org.signal.chat.errors.NotFound;
import org.signal.chat.messages.IndividualRecipientMessageBundle;
@@ -28,6 +29,7 @@ import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.InvalidVersionException;
import org.signal.libsignal.protocol.SealedSenderMultiRecipientMessage;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
import org.whispersystems.textsecuregcm.controllers.MessageDeliveryNotAllowedException;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.MultiRecipientMismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
@@ -229,6 +231,8 @@ public class MessagesAnonymousGrpcService extends SimpleMessagesAnonymousGrpc.Me
.build();
} catch (final MessageTooLargeException e) {
throw GrpcExceptions.invalidArguments("message too large");
} catch (final MessageDeliveryNotAllowedException e) {
throw GrpcExceptions.unavailable();
}
}
@@ -325,6 +329,8 @@ public class MessagesAnonymousGrpcService extends SimpleMessagesAnonymousGrpc.Me
return SendMultiRecipientMessageResponse.newBuilder()
.setMismatchedDevices(mismatchedDevicesBuilder)
.build();
} catch (final MessageDeliveryNotAllowedException e) {
throw GrpcExceptions.unavailable();
}
}
@@ -5,21 +5,24 @@
package org.whispersystems.textsecuregcm.grpc;
import static org.whispersystems.textsecuregcm.grpc.MessagesGrpcHelper.buildMismatchedDevices;
import com.google.protobuf.ByteString;
import com.google.protobuf.Empty;
import java.time.Clock;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.google.protobuf.Empty;
import org.signal.chat.errors.NotFound;
import org.signal.chat.messages.SendMessageType;
import org.signal.chat.messages.IndividualRecipientMessageBundle;
import org.signal.chat.messages.SendAuthenticatedSenderMessageRequest;
import org.signal.chat.messages.SendMessageAuthenticatedSenderResponse;
import org.signal.chat.messages.SendMessageType;
import org.signal.chat.messages.SendSyncMessageRequest;
import org.signal.chat.messages.SimpleMessagesGrpc;
import org.whispersystems.textsecuregcm.auth.grpc.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.auth.grpc.AuthenticationUtil;
import org.whispersystems.textsecuregcm.controllers.MessageDeliveryNotAllowedException;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
@@ -36,8 +39,6 @@ import org.whispersystems.textsecuregcm.spam.SpamChecker;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import static org.whispersystems.textsecuregcm.grpc.MessagesGrpcHelper.buildMismatchedDevices;
public class MessagesGrpcService extends SimpleMessagesGrpc.MessagesImplBase {
private final AccountsManager accountsManager;
@@ -192,6 +193,8 @@ public class MessagesGrpcService extends SimpleMessagesGrpc.MessagesImplBase {
.build();
} catch (final MessageTooLargeException e) {
throw GrpcExceptions.invalidArguments("message too large");
} catch (final MessageDeliveryNotAllowedException e) {
throw GrpcExceptions.unavailable();
}
}
@@ -26,6 +26,8 @@ import javax.annotation.Nullable;
import kotlin.Pair;
import org.apache.commons.lang3.StringUtils;
import org.signal.libsignal.protocol.SealedSenderMultiRecipientMessage;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.MessageDeliveryNotAllowedException;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevices;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.MultiRecipientMismatchedDevicesException;
@@ -36,6 +38,7 @@ import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.spam.MessageDeliveryListener;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.util.Util;
@@ -54,6 +57,7 @@ public class MessageSender {
private final MessagesManager messagesManager;
private final PushNotificationManager pushNotificationManager;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
private final List<MessageDeliveryListener> messageDeliveryListeners = new ArrayList<>();
@@ -80,9 +84,12 @@ public class MessageSender {
@VisibleForTesting
static final byte NO_EXCLUDED_DEVICE_ID = -1;
public MessageSender(final MessagesManager messagesManager, final PushNotificationManager pushNotificationManager) {
public MessageSender(final MessagesManager messagesManager,
final PushNotificationManager pushNotificationManager,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
this.messagesManager = messagesManager;
this.pushNotificationManager = pushNotificationManager;
this.dynamicConfigurationManager = dynamicConfigurationManager;
}
public void addMessageDeliveryListener(final MessageDeliveryListener messageDeliveryListener) {
@@ -112,7 +119,12 @@ public class MessageSender {
final Map<Byte, Envelope> messagesByDeviceId,
final Map<Byte, Integer> registrationIdsByDeviceId,
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") final Optional<Byte> syncMessageSenderDeviceId,
@Nullable final String userAgent) throws MismatchedDevicesException, MessageTooLargeException {
@Nullable final String userAgent)
throws MismatchedDevicesException, MessageTooLargeException, MessageDeliveryNotAllowedException {
if (dynamicConfigurationManager.getConfiguration().getMessageDeliveryConfiguration().isReadOnly()) {
throw new MessageDeliveryNotAllowedException();
}
final Tag platformTag = UserAgentTagUtil.getPlatformTag(userAgent);
@@ -189,7 +201,12 @@ public class MessageSender {
final boolean isStory,
final boolean isEphemeral,
final boolean isUrgent,
@Nullable final String userAgent) throws MultiRecipientMismatchedDevicesException, MessageTooLargeException {
@Nullable final String userAgent)
throws MultiRecipientMismatchedDevicesException, MessageTooLargeException, MessageDeliveryNotAllowedException {
if (dynamicConfigurationManager.getConfiguration().getMessageDeliveryConfiguration().isReadOnly()) {
throw new MessageDeliveryNotAllowedException();
}
final Tag platformTag = UserAgentTagUtil.getPlatformTag(userAgent);
@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.PhoneVerificationTokenManager;
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
import org.whispersystems.textsecuregcm.controllers.AccountControllerV2;
import org.whispersystems.textsecuregcm.controllers.MessageDeliveryNotAllowedException;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
@@ -81,7 +82,7 @@ public class ChangeNumberManager {
final List<IncomingMessage> deviceMessages,
final Map<Byte, Integer> pniRegistrationIds,
final ContainerRequestContext containerRequestContext)
throws InterruptedException, MismatchedDevicesException, MessageTooLargeException, RateLimitExceededException {
throws InterruptedException, MismatchedDevicesException, MessageTooLargeException, RateLimitExceededException, MessageDeliveryNotAllowedException {
final String senderUserAgent = containerRequestContext.getHeaderString(HttpHeaders.USER_AGENT);
@@ -292,7 +292,7 @@ class AccountControllerV2Test {
@ParameterizedTest
@MethodSource
void phoneVerificationException(final Exception exception, final int expectedStatus)
throws InterruptedException, MessageTooLargeException, MismatchedDevicesException, RateLimitExceededException {
throws InterruptedException, MessageTooLargeException, MismatchedDevicesException, RateLimitExceededException, MessageDeliveryNotAllowedException {
doThrow(exception)
.when(changeNumberManager).changeNumber(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any());
@@ -338,6 +338,28 @@ class AccountControllerV2Test {
}
}
@Test
void messageDeliveryNotAllowed() throws Exception {
doThrow(MessageDeliveryNotAllowedException.class)
.when(changeNumberManager).changeNumber(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any());
try (final Response response = resources.getJerseyTest()
.target("/v2/accounts/number")
.request()
.header(HttpHeaders.AUTHORIZATION,
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.entity(
new ChangeNumberRequest(encodeSessionId("session"), null, NEW_NUMBER, "123", IDENTITY_KEY,
Collections.emptyList(),
Map.of(Device.PRIMARY_ID, KeysHelper.signedECPreKey(1, IDENTITY_KEY_PAIR)),
Map.of(Device.PRIMARY_ID, KeysHelper.signedKEMPreKey(2, IDENTITY_KEY_PAIR)),
Map.of(Device.PRIMARY_ID, 17)),
MediaType.APPLICATION_JSON_TYPE))) {
assertEquals(503, response.getStatus());
}
}
/**
* Valid request JSON with the given Recovery Password
*/
@@ -196,7 +196,8 @@ class MessageControllerTest {
.build();
@BeforeEach
void setup() throws MultiRecipientMismatchedDevicesException, MessageTooLargeException {
void setup()
throws MultiRecipientMismatchedDevicesException, MessageTooLargeException, MessageDeliveryNotAllowedException {
reset(pushNotificationScheduler);
when(messageSender.sendMultiRecipientMessage(any(), any(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), any()))
@@ -545,6 +546,24 @@ class MessageControllerTest {
}
}
@Test
void testSingleDeviceMessageDeliveryNotAllowed() throws Exception {
doThrow(MessageDeliveryNotAllowedException.class)
.when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
try (final Response response =
resources.getJerseyTest()
.target(String.format("/v1/messages/%s", SINGLE_DEVICE_UUID))
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"),
IncomingMessageList.class),
MediaType.APPLICATION_JSON_TYPE))) {
assertThat(response.getStatus(), is(equalTo(503)));
}
}
@Test
void testSendBadAuth() throws Exception {
try (final Response response =
@@ -1042,7 +1061,8 @@ class MessageControllerTest {
}
@Test
void testValidateContentLength() throws MismatchedDevicesException, MessageTooLargeException, IOException {
void testValidateContentLength()
throws MismatchedDevicesException, MessageTooLargeException, IOException, MessageDeliveryNotAllowedException {
doThrow(new MessageTooLargeException()).when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
try (final Response response =
@@ -1101,7 +1121,7 @@ class MessageControllerTest {
final Set<Account> expectedResolvedAccounts,
final Set<ServiceIdentifier> expectedUuids404,
@Nullable final MultiRecipientMismatchedDevicesException mismatchedDevicesException)
throws MultiRecipientMismatchedDevicesException, MessageTooLargeException {
throws MultiRecipientMismatchedDevicesException, MessageTooLargeException, MessageDeliveryNotAllowedException {
clock.pin(START_OF_DAY);
@@ -1674,6 +1694,72 @@ class MessageControllerTest {
}
}
@Test
void sendMultiRecipientMessageMessageDeliveryNotAllowed() throws Exception {
clock.pin(START_OF_DAY);
final UUID singleDeviceAccountAci = UUID.randomUUID();
final UUID singleDeviceAccountPni = UUID.randomUUID();
final byte[] singleDeviceAccountUak = TestRandomUtil.nextBytes(UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH);
final int singleDevicePrimaryRegistrationId = 1;
final Device singleDeviceAccountPrimary = mock(Device.class);
when(singleDeviceAccountPrimary.getId()).thenReturn(Device.PRIMARY_ID);
when(singleDeviceAccountPrimary.getRegistrationId(IdentityType.ACI)).thenReturn(singleDevicePrimaryRegistrationId);
final Account singleDeviceAccount = mock(Account.class);
when(singleDeviceAccount.getIdentifier(IdentityType.ACI)).thenReturn(singleDeviceAccountAci);
when(singleDeviceAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of(singleDeviceAccountUak));
when(singleDeviceAccount.getDevices()).thenReturn(List.of(singleDeviceAccountPrimary));
when(singleDeviceAccount.getDevice(anyByte())).thenReturn(Optional.empty());
when(singleDeviceAccount.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(singleDeviceAccountPrimary));
final Map<ServiceIdentifier, Account> accountsByServiceIdentifier = Map.of(
new AciServiceIdentifier(singleDeviceAccountAci), singleDeviceAccount,
new PniServiceIdentifier(singleDeviceAccountPni), singleDeviceAccount);
final byte[] aciMessage = MultiRecipientMessageHelper.generateMultiRecipientMessage(List.of(
new TestRecipient(new AciServiceIdentifier(singleDeviceAccountAci), Device.PRIMARY_ID, singleDevicePrimaryRegistrationId, new byte[48])));
when(accountsManager.getByServiceIdentifierAsync(any()))
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
accountsByServiceIdentifier.forEach(((serviceIdentifier, account) ->
when(accountsManager.getByServiceIdentifierAsync(serviceIdentifier))
.thenReturn(CompletableFuture.completedFuture(Optional.of(account)))));
final boolean ephemeral = true;
final boolean urgent = false;
final boolean story = false;
final Invocation.Builder invocationBuilder = resources
.getJerseyTest()
.target("/v1/messages/multi_recipient")
.queryParam("ts", clock.millis())
.queryParam("online", ephemeral)
.queryParam("story", story)
.queryParam("urgent", urgent)
.request()
.header(HeaderUtils.GROUP_SEND_TOKEN, AuthHelper.validGroupSendTokenHeader(serverSecretParams,
List.of(new AciServiceIdentifier(singleDeviceAccountAci)),
START_OF_DAY.plus(Duration.ofDays(1))));
when(rateLimiter.validateAsync(any(UUID.class)))
.thenReturn(CompletableFuture.completedFuture(null));
doThrow(MessageDeliveryNotAllowedException.class)
.when(messageSender).sendMultiRecipientMessage(any(), any(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), any());
try (final Response response = invocationBuilder
.put(Entity.entity(aciMessage, MultiRecipientMessageProvider.MEDIA_TYPE))) {
assertThat(response.getStatus(), is(equalTo(503)));
}
}
@SuppressWarnings("SameParameterValue")
private static Envelope generateEnvelope(UUID guid, int type, long timestamp, UUID sourceUuid,
byte sourceDevice, UUID destinationUuid, UUID updatedPni, byte[] content, long serverTimestamp) {
@@ -54,6 +54,7 @@ import org.signal.chat.messages.SendMultiRecipientStoryRequest;
import org.signal.chat.messages.SendSealedSenderMessageRequest;
import org.signal.chat.messages.SendStoryMessageRequest;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
import org.whispersystems.textsecuregcm.controllers.MessageDeliveryNotAllowedException;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.MultiRecipientMismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
@@ -156,7 +157,7 @@ class MessagesAnonymousGrpcServiceTest extends
@CartesianTest.Values(booleans = {true, false}) final boolean ephemeral,
@CartesianTest.Values(booleans = {true, false}) final boolean urgent,
@CartesianTest.Values(booleans = {true, false}) final boolean includeReportSpamToken)
throws MessageTooLargeException, MismatchedDevicesException {
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -244,7 +245,8 @@ class MessagesAnonymousGrpcServiceTest extends
.setType(SendMessageType.DOUBLE_RATCHET)
.setPayload(ByteString.copyFrom(payload))
.build());
final byte[] reportSpamToken = TestRandomUtil.nextBytes(64);
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT,
() -> unauthenticatedServiceStub().sendSingleRecipientMessage(
generateRequest(serviceIdentifier, false, true, messages, UNIDENTIFIED_ACCESS_KEY, null)));
@@ -254,8 +256,7 @@ class MessagesAnonymousGrpcServiceTest extends
@CartesianTest
void sendUnrestrictedAccessMessage(
@CartesianTest.Values(booleans = {true, false}) final boolean useUak,
@CartesianTest.Values(booleans = {true, false}) final boolean isUua)
throws MessageTooLargeException, MismatchedDevicesException {
@CartesianTest.Values(booleans = {true, false}) final boolean isUua) {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -289,7 +290,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void mismatchedDevices() throws MessageTooLargeException, MismatchedDevicesException {
void mismatchedDevices()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -328,7 +330,8 @@ class MessagesAnonymousGrpcServiceTest extends
@ParameterizedTest
@ValueSource(booleans = {true, false})
void badCredentials(final boolean useUak) throws MessageTooLargeException, MismatchedDevicesException {
void badCredentials(final boolean useUak)
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -367,7 +370,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void destinationNotFound() throws MessageTooLargeException, MismatchedDevicesException {
void destinationNotFound()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
final Map<Byte, IndividualRecipientMessageBundle.Message> messages =
@@ -388,7 +392,7 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void pniIdentifierWithUak() throws MessageTooLargeException, MismatchedDevicesException {
void pniIdentifierWithUak() {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
final Device destinationDevice = DevicesHelper.createDevice(deviceId, CLOCK.millis(), registrationId);
@@ -410,13 +414,15 @@ class MessagesAnonymousGrpcServiceTest extends
final SendSealedSenderMessageRequest request =
generateRequest(pniIdentifier, false, true, messages, UNIDENTIFIED_ACCESS_KEY, null);
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(
Status.INVALID_ARGUMENT,
() -> unauthenticatedServiceStub().sendSingleRecipientMessage(request));
}
@Test
void rateLimited() throws RateLimitExceededException, MessageTooLargeException, MismatchedDevicesException {
void rateLimited()
throws RateLimitExceededException, MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -450,7 +456,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void oversizedMessage() throws MessageTooLargeException, MismatchedDevicesException {
void oversizedMessage()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -470,14 +477,15 @@ class MessagesAnonymousGrpcServiceTest extends
doThrow(new MessageTooLargeException())
.when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT,
() -> unauthenticatedServiceStub().sendSingleRecipientMessage(
generateRequest(serviceIdentifier, false, true, messages, UNIDENTIFIED_ACCESS_KEY, null)));
}
@Test
void spamWithStatus() throws MessageTooLargeException, MismatchedDevicesException {
void spamWithStatus()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -502,7 +510,7 @@ class MessagesAnonymousGrpcServiceTest extends
Optional.of(GrpcChallengeResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.RESOURCE_EXHAUSTED,
() -> unauthenticatedServiceStub().sendSingleRecipientMessage(
generateRequest(serviceIdentifier, false, true, messages, UNIDENTIFIED_ACCESS_KEY, null)));
@@ -516,7 +524,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void spamWithResponse() throws MessageTooLargeException, MismatchedDevicesException {
void spamWithResponse()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -544,6 +553,8 @@ class MessagesAnonymousGrpcServiceTest extends
final SendSealedSenderMessageRequest request =
generateRequest(serviceIdentifier, false, true, messages, UNIDENTIFIED_ACCESS_KEY, null);
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.RESOURCE_EXHAUSTED, () ->
unauthenticatedServiceStub().sendSingleRecipientMessage(request));
@@ -555,6 +566,42 @@ class MessagesAnonymousGrpcServiceTest extends
verify(messageSender, never()).sendMessages(any(), any(), any(), any(), any(), any());
}
@Test
void messageDeliveryNotAllowed()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
final Device destinationDevice = DevicesHelper.createDevice(deviceId, CLOCK.millis(), registrationId);
final Account destinationAccount = mock(Account.class);
when(destinationAccount.getDevices()).thenReturn(List.of(destinationDevice));
when(destinationAccount.getDevice(deviceId)).thenReturn(Optional.of(destinationDevice));
when(destinationAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of(UNIDENTIFIED_ACCESS_KEY));
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
when(accountsManager.getByServiceIdentifier(serviceIdentifier)).thenReturn(Optional.of(destinationAccount));
final byte[] payload = TestRandomUtil.nextBytes(128);
final Map<Byte, IndividualRecipientMessageBundle.Message> messages =
Map.of(deviceId, IndividualRecipientMessageBundle.Message.newBuilder()
.setRegistrationId(registrationId)
.setPayload(ByteString.copyFrom(payload))
.setType(SendMessageType.UNIDENTIFIED_SENDER)
.build());
doThrow(MessageDeliveryNotAllowedException.class)
.when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.UNAVAILABLE,
() -> unauthenticatedServiceStub().sendSingleRecipientMessage(
generateRequest(serviceIdentifier, false, false, messages,
null,
GROUP_SEND_TOKEN)));
}
private static SendSealedSenderMessageRequest generateRequest(final ServiceIdentifier serviceIdentifier,
final boolean ephemeral,
final boolean urgent,
@@ -595,7 +642,7 @@ class MessagesAnonymousGrpcServiceTest extends
@CartesianTest
void sendMessage(@CartesianTest.Values(booleans = {true, false}) final boolean ephemeral,
@CartesianTest.Values(booleans = {true, false}) final boolean urgent)
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -656,7 +703,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void mismatchedDevices() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void mismatchedDevices()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -704,7 +752,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void badCredentials() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void badCredentials()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -739,7 +788,7 @@ class MessagesAnonymousGrpcServiceTest extends
.setUrgent(true)
.build()));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT, () ->
unauthenticatedServiceStub().sendMultiRecipientMessage(SendMultiRecipientMessageRequest.newBuilder()
.setMessage(MultiRecipientMessage.newBuilder()
@@ -755,8 +804,9 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void badPayload() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
//noinspection ResultOfMethodCallIgnored
void badPayload()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT, () ->
unauthenticatedServiceStub().sendMultiRecipientMessage(SendMultiRecipientMessageRequest.newBuilder()
.setMessage(MultiRecipientMessage.newBuilder()
@@ -765,7 +815,7 @@ class MessagesAnonymousGrpcServiceTest extends
.build())
.build()));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT, () ->
unauthenticatedServiceStub().sendMultiRecipientMessage(SendMultiRecipientMessageRequest.newBuilder().build()));
@@ -774,7 +824,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void repeatedRecipient() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void repeatedRecipient()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final Device destinationDevice = DevicesHelper.createDevice(Device.PRIMARY_ID, CLOCK.millis(), 1);
final Account destinationAccount = mock(Account.class);
@@ -790,7 +841,7 @@ class MessagesAnonymousGrpcServiceTest extends
final byte[] payload = MultiRecipientMessageHelper.generateMultiRecipientMessage(List.of(recipient, recipient));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT, () ->
unauthenticatedServiceStub().sendMultiRecipientMessage(SendMultiRecipientMessageRequest.newBuilder()
.setGroupSendToken(ByteString.copyFrom(GROUP_SEND_TOKEN))
@@ -807,7 +858,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void oversizedMessage() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void oversizedMessage()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final Account destinationAccount = mock(Account.class);
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
@@ -831,13 +883,14 @@ class MessagesAnonymousGrpcServiceTest extends
doThrow(new MessageTooLargeException())
.when(messageSender).sendMultiRecipientMessage(any(), any(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), any());
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT,
() -> unauthenticatedServiceStub().sendMultiRecipientMessage(request));
}
@Test
void spamWithStatus() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void spamWithStatus()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -872,7 +925,7 @@ class MessagesAnonymousGrpcServiceTest extends
Optional.of(GrpcChallengeResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.RESOURCE_EXHAUSTED,
() -> unauthenticatedServiceStub().sendMultiRecipientMessage(request));
@@ -883,7 +936,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void spamWithResponse() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void spamWithResponse()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -918,7 +972,7 @@ class MessagesAnonymousGrpcServiceTest extends
when(spamChecker.checkForMultiRecipientSpamGrpc(any()))
.thenReturn(new SpamCheckResult<>(Optional.of(GrpcChallengeResponse.withResponse(challengeResponse)), Optional.empty()));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.RESOURCE_EXHAUSTED,
() -> unauthenticatedServiceStub().sendMultiRecipientMessage(request));
@@ -927,6 +981,41 @@ class MessagesAnonymousGrpcServiceTest extends
verify(messageSender, never())
.sendMultiRecipientMessage(any(), any(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), any());
}
@Test
void messageDeliveryNotAllowed()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
final Account destinationAccount = mock(Account.class);
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
when(accountsManager.getByServiceIdentifierAsync(serviceIdentifier))
.thenReturn(CompletableFuture.completedFuture(Optional.of(destinationAccount)));
final byte[] payload = MultiRecipientMessageHelper.generateMultiRecipientMessage(List.of(
new TestRecipient(serviceIdentifier, staleDeviceId, 17, new byte[48])));
final SendMultiRecipientMessageRequest request = SendMultiRecipientMessageRequest.newBuilder()
.setGroupSendToken(ByteString.copyFrom(GROUP_SEND_TOKEN))
.setMessage(MultiRecipientMessage.newBuilder()
.setTimestamp(CLOCK.millis())
.setPayload(ByteString.copyFrom(payload))
.build())
.setEphemeral(false)
.setUrgent(true)
.build();
doThrow(MessageDeliveryNotAllowedException.class)
.when(messageSender).sendMultiRecipientMessage(any(), any(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), any());
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.UNAVAILABLE,
() -> unauthenticatedServiceStub().sendMultiRecipientMessage(request));
}
}
@Nested
@@ -935,7 +1024,7 @@ class MessagesAnonymousGrpcServiceTest extends
@CartesianTest
void sendStory(@CartesianTest.Values(booleans = {true, false}) final boolean urgent,
@CartesianTest.Values(booleans = {true, false}) final boolean includeReportSpamToken)
throws MessageTooLargeException, MismatchedDevicesException {
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -998,7 +1087,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void mismatchedDevices() throws MessageTooLargeException, MismatchedDevicesException {
void mismatchedDevices()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -1036,7 +1126,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void destinationNotFound() throws MessageTooLargeException, MismatchedDevicesException {
void destinationNotFound()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final Map<Byte, IndividualRecipientMessageBundle.Message> messages =
Map.of(Device.PRIMARY_ID, IndividualRecipientMessageBundle.Message.newBuilder()
.setRegistrationId(7)
@@ -1053,7 +1144,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void rateLimited() throws RateLimitExceededException, MessageTooLargeException, MismatchedDevicesException {
void rateLimited()
throws RateLimitExceededException, MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -1085,7 +1177,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void oversizedMessage() throws MessageTooLargeException, MismatchedDevicesException {
void oversizedMessage()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -1105,13 +1198,14 @@ class MessagesAnonymousGrpcServiceTest extends
doThrow(new MessageTooLargeException()).when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusInvalidArgument(
() -> unauthenticatedServiceStub().sendStory(generateRequest(serviceIdentifier, false, messages)));
}
@Test
void spamWithStatus() throws MessageTooLargeException, MismatchedDevicesException {
void spamWithStatus()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -1136,7 +1230,7 @@ class MessagesAnonymousGrpcServiceTest extends
Optional.of(GrpcChallengeResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.RESOURCE_EXHAUSTED,
() -> unauthenticatedServiceStub().sendStory(generateRequest(serviceIdentifier, true, messages)));
@@ -1149,7 +1243,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void spamWithResponse() throws MessageTooLargeException, MismatchedDevicesException {
void spamWithResponse()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -1174,6 +1269,7 @@ class MessagesAnonymousGrpcServiceTest extends
when(spamChecker.checkForIndividualRecipientSpamGrpc(any(), any(), any(), any()))
.thenReturn(new SpamCheckResult<>(Optional.of(GrpcChallengeResponse.withResponse(challengeResponse)), Optional.empty()));
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.RESOURCE_EXHAUSTED, () ->
unauthenticatedServiceStub().sendStory(generateRequest(serviceIdentifier, true, messages)));
@@ -1185,6 +1281,38 @@ class MessagesAnonymousGrpcServiceTest extends
verify(messageSender, never()).sendMessages(any(), any(), any(), any(), any(), any());
}
@Test
void messageDeliveryNotAllowed()
throws MessageTooLargeException, MessageDeliveryNotAllowedException, MismatchedDevicesException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
final Device destinationDevice = DevicesHelper.createDevice(deviceId, CLOCK.millis(), registrationId);
final Account destinationAccount = mock(Account.class);
when(destinationAccount.getDevices()).thenReturn(List.of(destinationDevice));
when(destinationAccount.getDevice(deviceId)).thenReturn(Optional.of(destinationDevice));
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
when(accountsManager.getByServiceIdentifier(serviceIdentifier)).thenReturn(Optional.of(destinationAccount));
final byte[] payload = TestRandomUtil.nextBytes(128);
final Map<Byte, IndividualRecipientMessageBundle.Message> messages =
Map.of(deviceId, IndividualRecipientMessageBundle.Message.newBuilder()
.setRegistrationId(registrationId)
.setPayload(ByteString.copyFrom(payload))
.setType(SendMessageType.UNIDENTIFIED_SENDER)
.build());
doThrow(MessageDeliveryNotAllowedException.class)
.when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.UNAVAILABLE,
() -> unauthenticatedServiceStub().sendStory(generateRequest(serviceIdentifier, false, messages)));
}
private static SendStoryMessageRequest generateRequest(final ServiceIdentifier serviceIdentifier,
final boolean urgent,
final Map<Byte, IndividualRecipientMessageBundle.Message> messages) {
@@ -1207,7 +1335,8 @@ class MessagesAnonymousGrpcServiceTest extends
@ParameterizedTest
@ValueSource(booleans = {true, false})
void sendStory(final boolean urgent) throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void sendStory(final boolean urgent)
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -1260,7 +1389,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void mismatchedDevices() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void mismatchedDevices()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -1306,8 +1436,9 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void badPayload() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
//noinspection ResultOfMethodCallIgnored
void badPayload()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT, () ->
unauthenticatedServiceStub().sendMultiRecipientStory(SendMultiRecipientStoryRequest.newBuilder()
.setMessage(MultiRecipientMessage.newBuilder()
@@ -1316,7 +1447,7 @@ class MessagesAnonymousGrpcServiceTest extends
.build())
.build()));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT, () ->
unauthenticatedServiceStub().sendMultiRecipientMessage(
SendMultiRecipientMessageRequest.newBuilder().build()));
@@ -1326,7 +1457,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void repeatedRecipient() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void repeatedRecipient()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final Device destinationDevice = DevicesHelper.createDevice(Device.PRIMARY_ID, CLOCK.millis(), 1);
final Account destinationAccount = mock(Account.class);
@@ -1342,7 +1474,7 @@ class MessagesAnonymousGrpcServiceTest extends
final byte[] payload = MultiRecipientMessageHelper.generateMultiRecipientMessage(List.of(recipient, recipient));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT, () ->
unauthenticatedServiceStub().sendMultiRecipientStory(SendMultiRecipientStoryRequest.newBuilder()
.setMessage(MultiRecipientMessage.newBuilder()
@@ -1357,7 +1489,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void oversizedMessage() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void oversizedMessage()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final Account destinationAccount = mock(Account.class);
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
@@ -1379,12 +1512,13 @@ class MessagesAnonymousGrpcServiceTest extends
doThrow(new MessageTooLargeException())
.when(messageSender).sendMultiRecipientMessage(any(), any(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), any());
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusInvalidArgument(() -> unauthenticatedServiceStub().sendMultiRecipientStory(request));
}
@Test
void spamWithStatus() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void spamWithStatus()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -1417,7 +1551,7 @@ class MessagesAnonymousGrpcServiceTest extends
Optional.of(GrpcChallengeResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.RESOURCE_EXHAUSTED,
() -> unauthenticatedServiceStub().sendMultiRecipientStory(request));
@@ -1428,7 +1562,8 @@ class MessagesAnonymousGrpcServiceTest extends
}
@Test
void spamWithResponse() throws MessageTooLargeException, MultiRecipientMismatchedDevicesException {
void spamWithResponse()
throws MessageTooLargeException, MultiRecipientMismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -1461,6 +1596,7 @@ class MessagesAnonymousGrpcServiceTest extends
when(spamChecker.checkForMultiRecipientSpamGrpc(any()))
.thenReturn(new SpamCheckResult<>(Optional.of(GrpcChallengeResponse.withResponse(challengeResponse)), Optional.empty()));
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.RESOURCE_EXHAUSTED, () ->
unauthenticatedServiceStub().sendMultiRecipientStory(request));
@@ -1469,5 +1605,47 @@ class MessagesAnonymousGrpcServiceTest extends
verify(messageSender, never())
.sendMultiRecipientMessage(any(), any(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), any());
}
@Test
void messageDeliveryNotAllowed()
throws MessageTooLargeException, MessageDeliveryNotAllowedException, MultiRecipientMismatchedDevicesException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
final Device destinationDevice = DevicesHelper.createDevice(deviceId, CLOCK.millis(), registrationId);
final Account resolvedAccount = mock(Account.class);
when(resolvedAccount.getDevices()).thenReturn(List.of(destinationDevice));
when(resolvedAccount.getDevice(deviceId)).thenReturn(Optional.of(destinationDevice));
final AciServiceIdentifier resolvedServiceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
final AciServiceIdentifier unresolvedServiceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
when(accountsManager.getByServiceIdentifierAsync(resolvedServiceIdentifier))
.thenReturn(CompletableFuture.completedFuture(Optional.of(resolvedAccount)));
final TestRecipient resolvedRecipient =
new TestRecipient(resolvedServiceIdentifier, deviceId, registrationId, new byte[48]);
final TestRecipient unresolvedRecipient =
new TestRecipient(unresolvedServiceIdentifier, Device.PRIMARY_ID, 1, new byte[48]);
final byte[] payload = MultiRecipientMessageHelper.generateMultiRecipientMessage(List.of(
resolvedRecipient, unresolvedRecipient));
doThrow(MessageDeliveryNotAllowedException.class)
.when(messageSender).sendMultiRecipientMessage(any(), any(), anyLong(), anyBoolean(), anyBoolean(), anyBoolean(), any());
final SendMultiRecipientStoryRequest request = SendMultiRecipientStoryRequest.newBuilder()
.setMessage(MultiRecipientMessage.newBuilder()
.setTimestamp(CLOCK.millis())
.setPayload(ByteString.copyFrom(payload))
.build())
.build();
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.UNAVAILABLE,
() -> unauthenticatedServiceStub().sendMultiRecipientStory(request));
}
}
}
@@ -43,6 +43,7 @@ import org.signal.chat.messages.SendAuthenticatedSenderMessageRequest;
import org.signal.chat.messages.SendMessageAuthenticatedSenderResponse;
import org.signal.chat.messages.SendSyncMessageRequest;
import org.whispersystems.textsecuregcm.auth.grpc.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.controllers.MessageDeliveryNotAllowedException;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
@@ -156,7 +157,7 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
@CartesianTest.Values(booleans = {true, false}) final boolean ephemeral,
@CartesianTest.Values(booleans = {true, false}) final boolean urgent,
@CartesianTest.Values(booleans = {true, false}) final boolean includeReportSpamToken)
throws MessageTooLargeException, MismatchedDevicesException {
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -250,6 +251,7 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
.setType(SendMessageType.UNIDENTIFIED_SENDER)
.build());
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT, () -> authenticatedServiceStub()
.sendMessage(generateRequest(serviceIdentifier, false, true, messages)));
@@ -257,7 +259,8 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
}
@Test
void mismatchedDevices() throws MessageTooLargeException, MismatchedDevicesException {
void mismatchedDevices()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -294,7 +297,8 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
}
@Test
void destinationNotFound() throws MessageTooLargeException, MismatchedDevicesException {
void destinationNotFound()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
final Map<Byte, IndividualRecipientMessageBundle.Message> messages =
@@ -312,7 +316,8 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
}
@Test
void rateLimited() throws RateLimitExceededException, MessageTooLargeException, MismatchedDevicesException {
void rateLimited()
throws RateLimitExceededException, MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -347,7 +352,8 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
}
@Test
void oversizedMessage() throws MessageTooLargeException, MismatchedDevicesException {
void oversizedMessage()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -367,14 +373,15 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
doThrow(new MessageTooLargeException())
.when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT,
() -> authenticatedServiceStub().sendMessage(
generateRequest(serviceIdentifier, false, true, messages)));
}
@Test
void spamWithStatus() throws MessageTooLargeException, MismatchedDevicesException {
void spamWithStatus()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -399,7 +406,7 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
Optional.of(GrpcChallengeResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.RESOURCE_EXHAUSTED, () -> authenticatedServiceStub()
.sendMessage(generateRequest(serviceIdentifier, false, true, messages)));
@@ -412,7 +419,8 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
}
@Test
void spamWithResponse() throws MessageTooLargeException, MismatchedDevicesException {
void spamWithResponse()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
@@ -453,6 +461,38 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
verify(messageSender, never()).sendMessages(any(), any(), any(), any(), any(), any());
}
@Test
void messageDeliveryNotAllowed()
throws MessageTooLargeException, MessageDeliveryNotAllowedException, MismatchedDevicesException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
final Device destinationDevice = DevicesHelper.createDevice(deviceId, CLOCK.millis(), registrationId);
final Account destinationAccount = mock(Account.class);
when(destinationAccount.getDevices()).thenReturn(List.of(destinationDevice));
when(destinationAccount.getDevice(deviceId)).thenReturn(Optional.of(destinationDevice));
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
when(accountsManager.getByServiceIdentifier(serviceIdentifier)).thenReturn(Optional.of(destinationAccount));
final byte[] payload = TestRandomUtil.nextBytes(128);
final Map<Byte, IndividualRecipientMessageBundle.Message> messages =
Map.of(deviceId, IndividualRecipientMessageBundle.Message.newBuilder()
.setRegistrationId(registrationId)
.setPayload(ByteString.copyFrom(payload))
.setType(SendMessageType.DOUBLE_RATCHET)
.build());
doThrow(MessageDeliveryNotAllowedException.class)
.when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.UNAVAILABLE,
() -> authenticatedServiceStub().sendMessage(generateRequest(serviceIdentifier, false, false, messages)));
}
private static SendAuthenticatedSenderMessageRequest generateRequest(final ServiceIdentifier serviceIdentifier,
final boolean ephemeral,
final boolean urgent,
@@ -480,7 +520,7 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
void sendMessage(@CartesianTest.Enum(mode = CartesianTest.Enum.Mode.EXCLUDE, names = {"UNSPECIFIED", "UNRECOGNIZED", "UNIDENTIFIED_SENDER"}) final SendMessageType messageType,
@CartesianTest.Values(booleans = {true, false}) final boolean urgent,
@CartesianTest.Values(booleans = {true, false}) final boolean includeReportSpamToken)
throws MessageTooLargeException, MismatchedDevicesException {
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(AUTHENTICATED_ACI);
final byte[] payload = TestRandomUtil.nextBytes(128);
@@ -544,7 +584,7 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
));
if (includeReportSpamToken) {
expectedEnvelopes.replaceAll((deviceId, envelope) ->
expectedEnvelopes.replaceAll((_, envelope) ->
envelope.toBuilder().setReportSpamToken(ByteString.copyFrom(reportSpamToken)).build());
}
@@ -563,7 +603,8 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
}
@Test
void mismatchedDevices() throws MessageTooLargeException, MismatchedDevicesException {
void mismatchedDevices()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -595,7 +636,8 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
}
@Test
void rateLimited() throws RateLimitExceededException, MessageTooLargeException, MismatchedDevicesException {
void rateLimited()
throws RateLimitExceededException, MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final Duration retryDuration = Duration.ofHours(7);
doThrow(new RateLimitExceededException(retryDuration))
.when(rateLimiter).validate(eq(AUTHENTICATED_ACI), anyLong());
@@ -616,7 +658,8 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
}
@Test
void oversizedMessage() throws MessageTooLargeException, MismatchedDevicesException {
void oversizedMessage()
throws MessageTooLargeException, MismatchedDevicesException, MessageDeliveryNotAllowedException {
final byte missingDeviceId = Device.PRIMARY_ID;
final byte extraDeviceId = missingDeviceId + 1;
final byte staleDeviceId = extraDeviceId + 1;
@@ -636,11 +679,38 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
doThrow(new MessageTooLargeException())
.when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
//noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.INVALID_ARGUMENT, () -> authenticatedServiceStub()
.sendSyncMessage( generateRequest( true, messages)));
}
@Test
void messageDeliveryNotAllowed()
throws MessageTooLargeException, MessageDeliveryNotAllowedException, MismatchedDevicesException {
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(AUTHENTICATED_ACI);
final byte[] payload = TestRandomUtil.nextBytes(128);
final Map<Byte, IndividualRecipientMessageBundle.Message> messages =
Map.of(LINKED_DEVICE_ID, IndividualRecipientMessageBundle.Message.newBuilder()
.setRegistrationId(LINKED_DEVICE_REGISTRATION_ID)
.setPayload(ByteString.copyFrom(payload))
.setType(SendMessageType.DOUBLE_RATCHET)
.build(),
SECOND_LINKED_DEVICE_ID, IndividualRecipientMessageBundle.Message.newBuilder()
.setRegistrationId(SECOND_LINKED_DEVICE_REGISTRATION_ID)
.setPayload(ByteString.copyFrom(payload))
.setType(SendMessageType.DOUBLE_RATCHET)
.build());
doThrow(MessageDeliveryNotAllowedException.class)
.when(messageSender).sendMessages(any(), any(), any(), any(), any(), any());
//noinspection ResultOfMethodCallIgnored,ThrowableNotThrown
GrpcTestUtils.assertStatusException(Status.UNAVAILABLE,
() -> authenticatedServiceStub().sendSyncMessage(generateRequest(false, messages)));
}
private static SendSyncMessageRequest generateRequest(
final boolean urgent,
final Map<Byte, IndividualRecipientMessageBundle.Message> messages) {
@@ -39,6 +39,9 @@ import org.junitpioneer.jupiter.cartesian.CartesianTest;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.InvalidVersionException;
import org.signal.libsignal.protocol.SealedSenderMultiRecipientMessage;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicMessageDeliveryConfiguration;
import org.whispersystems.textsecuregcm.controllers.MessageDeliveryNotAllowedException;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevices;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.MultiRecipientMismatchedDevicesException;
@@ -51,6 +54,7 @@ import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.spam.MessageDeliveryListener;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.tests.util.MultiRecipientMessageHelper;
import org.whispersystems.textsecuregcm.tests.util.TestRecipient;
@@ -62,6 +66,8 @@ class MessageSenderTest {
private PushNotificationManager pushNotificationManager;
private MessageDeliveryListener messageDeliveryListener;
private DynamicMessageDeliveryConfiguration dynamicMessageDeliveryConfiguration;
private MessageSender messageSender;
@BeforeEach
@@ -70,7 +76,17 @@ class MessageSenderTest {
pushNotificationManager = mock(PushNotificationManager.class);
messageDeliveryListener = mock(MessageDeliveryListener.class);
messageSender = new MessageSender(messagesManager, pushNotificationManager);
dynamicMessageDeliveryConfiguration = mock(DynamicMessageDeliveryConfiguration.class);
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
when(dynamicConfiguration.getMessageDeliveryConfiguration()).thenReturn(dynamicMessageDeliveryConfiguration);
@SuppressWarnings("unchecked") final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
mock(DynamicConfigurationManager.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
messageSender = new MessageSender(messagesManager, pushNotificationManager, dynamicConfigurationManager);
messageSender.addMessageDeliveryListener(messageDeliveryListener);
}
@@ -182,6 +198,36 @@ class MessageSenderTest {
anyBoolean());
}
@Test
void sendMessageReadOnlyMode() {
final UUID accountIdentifier = UUID.randomUUID();
final ServiceIdentifier serviceIdentifier = new AciServiceIdentifier(accountIdentifier);
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 17;
final Account account = mock(Account.class);
final Device device = mock(Device.class);
final MessageProtos.Envelope message = MessageProtos.Envelope.newBuilder().build();
when(account.getUuid()).thenReturn(accountIdentifier);
when(account.getIdentifier(IdentityType.ACI)).thenReturn(accountIdentifier);
when(account.isIdentifiedBy(serviceIdentifier)).thenReturn(true);
when(account.getDevices()).thenReturn(List.of(device));
when(account.getDevice(deviceId)).thenReturn(Optional.of(device));
when(device.getId()).thenReturn(deviceId);
when(device.getRegistrationId(IdentityType.ACI)).thenReturn(registrationId);
when(device.getApnId()).thenReturn("apns-token");
when(dynamicMessageDeliveryConfiguration.isReadOnly()).thenReturn(true);
assertThrows(MessageDeliveryNotAllowedException.class, () -> messageSender.sendMessages(account,
serviceIdentifier,
Map.of(device.getId(), message),
Map.of(device.getId(), registrationId),
Optional.empty(),
null));
}
@CartesianTest
void sendMultiRecipientMessage(@CartesianTest.Values(booleans = {true, false}) final boolean clientPresent,
@CartesianTest.Values(booleans = {true, false}) final boolean ephemeral,
@@ -303,6 +349,48 @@ class MessageSenderTest {
anyBoolean());
}
@Test
void sendMultiRecipientMessageReadOnlyMode()
throws NotPushRegisteredException, InvalidMessageException, InvalidVersionException {
final UUID accountIdentifier = UUID.randomUUID();
final ServiceIdentifier serviceIdentifier = new AciServiceIdentifier(accountIdentifier);
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 17;
final Account account = mock(Account.class);
final Device device = mock(Device.class);
when(account.getUuid()).thenReturn(accountIdentifier);
when(account.getIdentifier(IdentityType.ACI)).thenReturn(accountIdentifier);
when(account.isIdentifiedBy(serviceIdentifier)).thenReturn(true);
when(account.getDevices()).thenReturn(List.of(device));
when(account.getDevice(deviceId)).thenReturn(Optional.of(device));
when(device.getId()).thenReturn(deviceId);
when(device.getRegistrationId(IdentityType.ACI)).thenReturn(registrationId);
when(device.getApnId()).thenReturn("apns-token");
when(device.getApnId()).thenReturn("apns-token");
final SealedSenderMultiRecipientMessage multiRecipientMessage =
SealedSenderMultiRecipientMessage.parse(MultiRecipientMessageHelper.generateMultiRecipientMessage(
List.of(new TestRecipient(serviceIdentifier, deviceId, registrationId, new byte[48]))));
final SealedSenderMultiRecipientMessage.Recipient recipient =
multiRecipientMessage.getRecipients().values().iterator().next();
when(dynamicMessageDeliveryConfiguration.isReadOnly()).thenReturn(true);
assertThrows(MessageDeliveryNotAllowedException.class,
() -> messageSender.sendMultiRecipientMessage(multiRecipientMessage,
Map.of(recipient, account),
System.currentTimeMillis(),
false,
false,
false,
null)
.join());
}
@ParameterizedTest
@MethodSource
void validateIndividualMessageBundle(final Account destination,
@@ -35,6 +35,7 @@ import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.whispersystems.textsecuregcm.auth.PhoneVerificationTokenManager;
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
import org.whispersystems.textsecuregcm.controllers.MessageDeliveryNotAllowedException;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
@@ -284,7 +285,7 @@ public class ChangeNumberManagerTest {
@Test
void changeNumberRateLimited()
throws MismatchedDevicesException, InterruptedException, MessageTooLargeException, RateLimitExceededException {
throws MismatchedDevicesException, InterruptedException, MessageTooLargeException, RateLimitExceededException, MessageDeliveryNotAllowedException {
final String originalNumber = PhoneNumberUtil.getInstance().format(
PhoneNumberUtil.getInstance().getExampleNumber("DE"), PhoneNumberUtil.PhoneNumberFormat.E164);
@@ -332,7 +333,7 @@ public class ChangeNumberManagerTest {
@Test
void changeNumberRegistrationLockFailed()
throws MismatchedDevicesException, InterruptedException, MessageTooLargeException, RateLimitExceededException {
throws MismatchedDevicesException, InterruptedException, MessageTooLargeException, RateLimitExceededException, MessageDeliveryNotAllowedException {
final String originalNumber = PhoneNumberUtil.getInstance().format(
PhoneNumberUtil.getInstance().getExampleNumber("DE"), PhoneNumberUtil.PhoneNumberFormat.E164);