mirror of
https://github.com/signalapp/Signal-Server
synced 2026-05-21 03:08:51 +01:00
Introduce an emergency "read only" mode for messages
This commit is contained in:
@@ -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(),
|
||||
|
||||
+8
-1
@@ -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;
|
||||
}
|
||||
|
||||
+18
@@ -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;
|
||||
}
|
||||
}
|
||||
+3
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -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
|
||||
|
||||
+6
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-4
@@ -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);
|
||||
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
|
||||
+23
-1
@@ -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
|
||||
*/
|
||||
|
||||
+89
-3
@@ -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) {
|
||||
|
||||
+228
-50
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+85
-15
@@ -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,
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user