From 5f49772ca67126a70f4681f6cafe025487b6316d Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Fri, 12 Feb 2021 14:10:10 -0500 Subject: [PATCH] Control enforcement of unsealed sender rate limits via dynamic configuration. --- .../textsecuregcm/WhisperServerService.java | 2 +- .../dynamic/DynamicConfiguration.java | 8 +++++ .../DynamicMessageRateConfiguration.java | 18 ++++++++++ .../controllers/MessageController.java | 34 ++++++++++++------- .../dynamic/DynamicConfigurationTest.java | 22 ++++++++++++ .../controllers/MessageControllerTest.java | 22 ++++++------ 6 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicMessageRateConfiguration.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index fa7f5a0a7..8d543163f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -392,7 +392,7 @@ public class WhisperServerService extends Application featureFlags = Collections.emptySet(); @@ -37,6 +41,10 @@ public class DynamicConfiguration { return remoteDeprecation; } + public DynamicMessageRateConfiguration getMessageRateConfiguration() { + return messageRate; + } + public Set getActiveFeatureFlags() { return featureFlags; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicMessageRateConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicMessageRateConfiguration.java new file mode 100644 index 000000000..fdf8d145d --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicMessageRateConfiguration.java @@ -0,0 +1,18 @@ +/* + * Copyright 2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.configuration.dynamic; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class DynamicMessageRateConfiguration { + + @JsonProperty + private boolean enforceUnsealedSenderRateLimit = false; + + public boolean isEnforceUnsealedSenderRateLimit() { + return enforceUnsealedSenderRateLimit; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index 4e7d5ff99..15a15ffaf 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -59,6 +59,7 @@ import org.whispersystems.textsecuregcm.redis.RedisOperation; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; +import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.util.Base64; import org.whispersystems.textsecuregcm.util.Constants; @@ -79,12 +80,13 @@ public class MessageController { private final Timer sendMessageInternalTimer = metricRegistry.timer(name(getClass(), "sendMessageInternal")); private final Histogram outgoingMessageListSizeHistogram = metricRegistry.histogram(name(getClass(), "outgoingMessageListSize")); - private final RateLimiters rateLimiters; - private final MessageSender messageSender; - private final ReceiptSender receiptSender; - private final AccountsManager accountsManager; - private final MessagesManager messagesManager; - private final ApnFallbackManager apnFallbackManager; + private final RateLimiters rateLimiters; + private final MessageSender messageSender; + private final ReceiptSender receiptSender; + private final AccountsManager accountsManager; + private final MessagesManager messagesManager; + private final ApnFallbackManager apnFallbackManager; + private final DynamicConfigurationManager dynamicConfigurationManager; private static final String SENT_MESSAGE_COUNTER_NAME = name(MessageController.class, "sentMessages"); private static final String REJECT_UNSEALED_SENDER_COUNTER_NAME = name(MessageController.class, "rejectUnsealedSenderLimit"); @@ -102,14 +104,16 @@ public class MessageController { ReceiptSender receiptSender, AccountsManager accountsManager, MessagesManager messagesManager, - ApnFallbackManager apnFallbackManager) + ApnFallbackManager apnFallbackManager, + DynamicConfigurationManager dynamicConfigurationManager) { - this.rateLimiters = rateLimiters; - this.messageSender = messageSender; - this.receiptSender = receiptSender; - this.accountsManager = accountsManager; - this.messagesManager = messagesManager; - this.apnFallbackManager = apnFallbackManager; + this.rateLimiters = rateLimiters; + this.messageSender = messageSender; + this.receiptSender = receiptSender; + this.accountsManager = accountsManager; + this.messagesManager = messagesManager; + this.apnFallbackManager = apnFallbackManager; + this.dynamicConfigurationManager = dynamicConfigurationManager; } @Timed @@ -134,6 +138,10 @@ public class MessageController { } catch (RateLimitExceededException e) { Metrics.counter(REJECT_UNSEALED_SENDER_COUNTER_NAME, SENDER_COUNTRY_TAG_NAME, Util.getCountryCode(source.get().getNumber())).increment(); logger.debug("Rejected unsealed sender limit from: {}", source.get().getNumber()); + + if (dynamicConfigurationManager.getConfiguration().getMessageRateConfiguration().isEnforceUnsealedSenderRateLimit()) { + throw e; + } } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfigurationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfigurationTest.java index a80d7b18f..65a8fec35 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfigurationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfigurationTest.java @@ -107,6 +107,28 @@ public class DynamicConfigurationTest { } } + @Test + public void testParseMessageRateConfiguration() throws JsonProcessingException { + { + final String emptyConfigYaml = "test: true"; + final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER + .readValue(emptyConfigYaml, DynamicConfiguration.class); + + assertFalse(emptyConfig.getMessageRateConfiguration().isEnforceUnsealedSenderRateLimit()); + } + + { + final String messageRateConfigYaml = + "messageRate:\n" + + " enforceUnsealedSenderRateLimit: true"; + + final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER + .readValue(messageRateConfigYaml, DynamicConfiguration.class); + + assertTrue(emptyConfig.getMessageRateConfiguration().isEnforceUnsealedSenderRateLimit()); + } + } + @Test public void testParseFeatureFlags() throws JsonProcessingException { { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java index fb7f5704f..3fc39070e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java @@ -66,6 +66,7 @@ import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; +import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.Base64; @@ -78,16 +79,17 @@ public class MessageControllerTest { private static final String MULTI_DEVICE_RECIPIENT = "+14152222222"; private static final UUID MULTI_DEVICE_UUID = UUID.randomUUID(); - private final MessageSender messageSender = mock(MessageSender.class); - private final ReceiptSender receiptSender = mock(ReceiptSender.class); - private final AccountsManager accountsManager = mock(AccountsManager.class); - private final MessagesManager messagesManager = mock(MessagesManager.class); - private final RateLimiters rateLimiters = mock(RateLimiters.class); - private final RateLimiter rateLimiter = mock(RateLimiter.class); - private final CardinalityRateLimiter unsealedSenderLimiter = mock(CardinalityRateLimiter.class); - private final ApnFallbackManager apnFallbackManager = mock(ApnFallbackManager.class); + private final MessageSender messageSender = mock(MessageSender.class); + private final ReceiptSender receiptSender = mock(ReceiptSender.class); + private final AccountsManager accountsManager = mock(AccountsManager.class); + private final MessagesManager messagesManager = mock(MessagesManager.class); + private final RateLimiters rateLimiters = mock(RateLimiters.class); + private final RateLimiter rateLimiter = mock(RateLimiter.class); + private final CardinalityRateLimiter unsealedSenderLimiter = mock(CardinalityRateLimiter.class); + private final ApnFallbackManager apnFallbackManager = mock(ApnFallbackManager.class); + private final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class); - private final ObjectMapper mapper = new ObjectMapper(); + private final ObjectMapper mapper = new ObjectMapper(); @Rule public final ResourceTestRule resources = ResourceTestRule.builder() @@ -95,7 +97,7 @@ public class MessageControllerTest { .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, - messagesManager, apnFallbackManager)) + messagesManager, apnFallbackManager, dynamicConfigurationManager)) .build();