Migrate challenge-issuing rate limiters to the abusive message filter

This commit is contained in:
Jon Chambers
2021-11-18 15:39:46 -05:00
committed by Jon Chambers
parent 9628f147f1
commit 14cff958e9
26 changed files with 311 additions and 982 deletions

View File

@@ -1,66 +0,0 @@
package org.whispersystems.textsecuregcm.limits;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRateLimitChallengeConfiguration;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
class PreKeyRateLimiterTest {
private Account account;
private PreKeyRateLimiter preKeyRateLimiter;
private DynamicRateLimitChallengeConfiguration rateLimitChallengeConfiguration;
private RateLimiter dailyPreKeyLimiter;
@BeforeEach
void setup() {
final DynamicRateLimiters rateLimiters = mock(DynamicRateLimiters.class);
dailyPreKeyLimiter = mock(RateLimiter.class);
when(rateLimiters.getDailyPreKeysLimiter()).thenReturn(dailyPreKeyLimiter);
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
rateLimitChallengeConfiguration = mock(DynamicRateLimitChallengeConfiguration.class);
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
when(dynamicConfiguration.getRateLimitChallengeConfiguration()).thenReturn(rateLimitChallengeConfiguration);
preKeyRateLimiter = new PreKeyRateLimiter(rateLimiters, dynamicConfigurationManager, mock(RateLimitResetMetricsManager.class));
account = mock(Account.class);
when(account.getNumber()).thenReturn("+18005551111");
when(account.getUuid()).thenReturn(UUID.randomUUID());
}
@Test
void enforcementConfiguration() throws RateLimitExceededException {
doThrow(RateLimitExceededException.class)
.when(dailyPreKeyLimiter).validate(any(UUID.class));
when(rateLimitChallengeConfiguration.isPreKeyLimitEnforced()).thenReturn(false);
preKeyRateLimiter.validate(account);
when(rateLimitChallengeConfiguration.isPreKeyLimitEnforced()).thenReturn(true);
assertThrows(RateLimitExceededException.class, () -> preKeyRateLimiter.validate(account));
when(rateLimitChallengeConfiguration.isPreKeyLimitEnforced()).thenReturn(false);
preKeyRateLimiter.validate(account);
}
}

View File

@@ -1,41 +1,28 @@
package org.whispersystems.textsecuregcm.limits;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import com.vdurmont.semver4j.Semver;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRateLimitChallengeConfiguration;
import org.whispersystems.textsecuregcm.abuse.RateLimitChallengeListener;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
class RateLimitChallengeManagerTest {
private PushChallengeManager pushChallengeManager;
private RecaptchaClient recaptchaClient;
private PreKeyRateLimiter preKeyRateLimiter;
private UnsealedSenderRateLimiter unsealedSenderRateLimiter;
private DynamicRateLimitChallengeConfiguration rateLimitChallengeConfiguration;
private DynamicRateLimiters rateLimiters;
private RateLimitChallengeListener rateLimitChallengeListener;
private RateLimitChallengeManager rateLimitChallengeManager;
@@ -43,24 +30,15 @@ class RateLimitChallengeManagerTest {
void setUp() {
pushChallengeManager = mock(PushChallengeManager.class);
recaptchaClient = mock(RecaptchaClient.class);
preKeyRateLimiter = mock(PreKeyRateLimiter.class);
unsealedSenderRateLimiter = mock(UnsealedSenderRateLimiter.class);
rateLimiters = mock(DynamicRateLimiters.class);
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
rateLimitChallengeConfiguration = mock(DynamicRateLimitChallengeConfiguration.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
when(dynamicConfiguration.getRateLimitChallengeConfiguration()).thenReturn(rateLimitChallengeConfiguration);
rateLimitChallengeListener = mock(RateLimitChallengeListener.class);
rateLimitChallengeManager = new RateLimitChallengeManager(
pushChallengeManager,
recaptchaClient,
preKeyRateLimiter,
unsealedSenderRateLimiter,
rateLimiters,
dynamicConfigurationManager);
rateLimiters);
rateLimitChallengeManager.addListener(rateLimitChallengeListener);
}
@ParameterizedTest
@@ -78,11 +56,9 @@ class RateLimitChallengeManagerTest {
rateLimitChallengeManager.answerPushChallenge(account, "challenge");
if (successfulChallenge) {
verify(preKeyRateLimiter).handleRateLimitReset(account);
verify(unsealedSenderRateLimiter).handleRateLimitReset(account);
verify(rateLimitChallengeListener).handleRateLimitChallengeAnswered(account);
} else {
verifyNoInteractions(preKeyRateLimiter);
verifyNoInteractions(unsealedSenderRateLimiter);
verifyNoInteractions(rateLimitChallengeListener);
}
}
@@ -102,98 +78,9 @@ class RateLimitChallengeManagerTest {
rateLimitChallengeManager.answerRecaptchaChallenge(account, "captcha", "10.0.0.1");
if (successfulChallenge) {
verify(preKeyRateLimiter).handleRateLimitReset(account);
verify(unsealedSenderRateLimiter).handleRateLimitReset(account);
verify(rateLimitChallengeListener).handleRateLimitChallengeAnswered(account);
} else {
verifyNoInteractions(preKeyRateLimiter);
verifyNoInteractions(unsealedSenderRateLimiter);
verifyNoInteractions(rateLimitChallengeListener);
}
}
@ParameterizedTest
@MethodSource
void isClientBelowMinimumVersion(final String userAgent, final boolean expectBelowMinimumVersion) {
when(rateLimitChallengeConfiguration.getMinimumSupportedVersion(any())).thenReturn(Optional.empty());
when(rateLimitChallengeConfiguration.getMinimumSupportedVersion(ClientPlatform.ANDROID))
.thenReturn(Optional.of(new Semver("5.6.0")));
when(rateLimitChallengeConfiguration.getMinimumSupportedVersion(ClientPlatform.DESKTOP))
.thenReturn(Optional.of(new Semver("5.0.0-beta.2")));
assertEquals(expectBelowMinimumVersion, rateLimitChallengeManager.isClientBelowMinimumVersion(userAgent));
}
private static Stream<Arguments> isClientBelowMinimumVersion() {
return Stream.of(
Arguments.of("Signal-Android/5.1.2 Android/30", true),
Arguments.of("Signal-Android/5.6.0 Android/30", false),
Arguments.of("Signal-Android/5.11.1 Android/30", false),
Arguments.of("Signal-Desktop/5.0.0-beta.3 macOS/11", false),
Arguments.of("Signal-Desktop/5.0.0-beta.1 Windows/3.1", true),
Arguments.of("Signal-Desktop/5.2.0 Debian/11", false),
Arguments.of("Signal-iOS/5.1.2 iOS/12.2", true),
Arguments.of("anything-else", false)
);
}
@ParameterizedTest
@MethodSource
void getChallengeOptions(final boolean captchaAttemptPermitted,
final boolean captchaSuccessPermitted,
final boolean pushAttemptPermitted,
final boolean pushSuccessPermitted,
final boolean expectCaptcha,
final boolean expectPushChallenge) {
final RateLimiter recaptchaChallengeAttemptLimiter = mock(RateLimiter.class);
final RateLimiter recaptchaChallengeSuccessLimiter = mock(RateLimiter.class);
final RateLimiter pushChallengeAttemptLimiter = mock(RateLimiter.class);
final RateLimiter pushChallengeSuccessLimiter = mock(RateLimiter.class);
when(rateLimiters.getRecaptchaChallengeAttemptLimiter()).thenReturn(recaptchaChallengeAttemptLimiter);
when(rateLimiters.getRecaptchaChallengeSuccessLimiter()).thenReturn(recaptchaChallengeSuccessLimiter);
when(rateLimiters.getPushChallengeAttemptLimiter()).thenReturn(pushChallengeAttemptLimiter);
when(rateLimiters.getPushChallengeSuccessLimiter()).thenReturn(pushChallengeSuccessLimiter);
when(recaptchaChallengeAttemptLimiter.hasAvailablePermits(any(UUID.class), anyInt())).thenReturn(captchaAttemptPermitted);
when(recaptchaChallengeSuccessLimiter.hasAvailablePermits(any(UUID.class), anyInt())).thenReturn(captchaSuccessPermitted);
when(pushChallengeAttemptLimiter.hasAvailablePermits(any(UUID.class), anyInt())).thenReturn(pushAttemptPermitted);
when(pushChallengeSuccessLimiter.hasAvailablePermits(any(UUID.class), anyInt())).thenReturn(pushSuccessPermitted);
final int expectedLength = (expectCaptcha ? 1 : 0) + (expectPushChallenge ? 1 : 0);
final Account account = mock(Account.class);
when(account.getUuid()).thenReturn(UUID.randomUUID());
final List<String> options = rateLimitChallengeManager.getChallengeOptions(account);
assertEquals(expectedLength, options.size());
if (expectCaptcha) {
assertTrue(options.contains(RateLimitChallengeManager.OPTION_RECAPTCHA));
}
if (expectPushChallenge) {
assertTrue(options.contains(RateLimitChallengeManager.OPTION_PUSH_CHALLENGE));
}
}
private static Stream<Arguments> getChallengeOptions() {
return Stream.of(
Arguments.of(false, false, false, false, false, false),
Arguments.of(false, false, false, true, false, false),
Arguments.of(false, false, true, false, false, false),
Arguments.of(false, false, true, true, false, true),
Arguments.of(false, true, false, false, false, false),
Arguments.of(false, true, false, true, false, false),
Arguments.of(false, true, true, false, false, false),
Arguments.of(false, true, true, true, false, true),
Arguments.of(true, false, false, false, false, false),
Arguments.of(true, false, false, true, false, false),
Arguments.of(true, false, true, false, false, false),
Arguments.of(true, false, true, true, false, true),
Arguments.of(true, true, false, false, true, false),
Arguments.of(true, true, false, true, true, false),
Arguments.of(true, true, true, false, true, false),
Arguments.of(true, true, true, true, true, true)
);
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.limits;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.vdurmont.semver4j.Semver;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRateLimitChallengeConfiguration;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
class RateLimitChallengeOptionManagerTest {
private DynamicRateLimitChallengeConfiguration rateLimitChallengeConfiguration;
private DynamicRateLimiters rateLimiters;
private RateLimitChallengeOptionManager rateLimitChallengeOptionManager;
@BeforeEach
void setUp() {
rateLimiters = mock(DynamicRateLimiters.class);
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
mock(DynamicConfigurationManager.class);
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
rateLimitChallengeConfiguration = mock(DynamicRateLimitChallengeConfiguration.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
when(dynamicConfiguration.getRateLimitChallengeConfiguration()).thenReturn(rateLimitChallengeConfiguration);
rateLimitChallengeOptionManager = new RateLimitChallengeOptionManager(rateLimiters, dynamicConfigurationManager);
}
@ParameterizedTest
@MethodSource
void isClientBelowMinimumVersion(final String userAgent, final boolean expectBelowMinimumVersion) {
when(rateLimitChallengeConfiguration.getMinimumSupportedVersion(any())).thenReturn(Optional.empty());
when(rateLimitChallengeConfiguration.getMinimumSupportedVersion(ClientPlatform.ANDROID))
.thenReturn(Optional.of(new Semver("5.6.0")));
when(rateLimitChallengeConfiguration.getMinimumSupportedVersion(ClientPlatform.DESKTOP))
.thenReturn(Optional.of(new Semver("5.0.0-beta.2")));
assertEquals(expectBelowMinimumVersion, rateLimitChallengeOptionManager.isClientBelowMinimumVersion(userAgent));
}
private static Stream<Arguments> isClientBelowMinimumVersion() {
return Stream.of(
Arguments.of("Signal-Android/5.1.2 Android/30", true),
Arguments.of("Signal-Android/5.6.0 Android/30", false),
Arguments.of("Signal-Android/5.11.1 Android/30", false),
Arguments.of("Signal-Desktop/5.0.0-beta.3 macOS/11", false),
Arguments.of("Signal-Desktop/5.0.0-beta.1 Windows/3.1", true),
Arguments.of("Signal-Desktop/5.2.0 Debian/11", false),
Arguments.of("Signal-iOS/5.1.2 iOS/12.2", true),
Arguments.of("anything-else", false)
);
}
@ParameterizedTest
@MethodSource
void getChallengeOptions(final boolean captchaAttemptPermitted,
final boolean captchaSuccessPermitted,
final boolean pushAttemptPermitted,
final boolean pushSuccessPermitted,
final boolean expectCaptcha,
final boolean expectPushChallenge) {
final RateLimiter recaptchaChallengeAttemptLimiter = mock(RateLimiter.class);
final RateLimiter recaptchaChallengeSuccessLimiter = mock(RateLimiter.class);
final RateLimiter pushChallengeAttemptLimiter = mock(RateLimiter.class);
final RateLimiter pushChallengeSuccessLimiter = mock(RateLimiter.class);
when(rateLimiters.getRecaptchaChallengeAttemptLimiter()).thenReturn(recaptchaChallengeAttemptLimiter);
when(rateLimiters.getRecaptchaChallengeSuccessLimiter()).thenReturn(recaptchaChallengeSuccessLimiter);
when(rateLimiters.getPushChallengeAttemptLimiter()).thenReturn(pushChallengeAttemptLimiter);
when(rateLimiters.getPushChallengeSuccessLimiter()).thenReturn(pushChallengeSuccessLimiter);
when(recaptchaChallengeAttemptLimiter.hasAvailablePermits(any(UUID.class), anyInt())).thenReturn(captchaAttemptPermitted);
when(recaptchaChallengeSuccessLimiter.hasAvailablePermits(any(UUID.class), anyInt())).thenReturn(captchaSuccessPermitted);
when(pushChallengeAttemptLimiter.hasAvailablePermits(any(UUID.class), anyInt())).thenReturn(pushAttemptPermitted);
when(pushChallengeSuccessLimiter.hasAvailablePermits(any(UUID.class), anyInt())).thenReturn(pushSuccessPermitted);
final int expectedLength = (expectCaptcha ? 1 : 0) + (expectPushChallenge ? 1 : 0);
final Account account = mock(Account.class);
when(account.getUuid()).thenReturn(UUID.randomUUID());
final List<String> options = rateLimitChallengeOptionManager.getChallengeOptions(account);
assertEquals(expectedLength, options.size());
if (expectCaptcha) {
assertTrue(options.contains(RateLimitChallengeOptionManager.OPTION_RECAPTCHA));
}
if (expectPushChallenge) {
assertTrue(options.contains(RateLimitChallengeOptionManager.OPTION_PUSH_CHALLENGE));
}
}
private static Stream<Arguments> getChallengeOptions() {
return Stream.of(
Arguments.of(false, false, false, false, false, false),
Arguments.of(false, false, false, true, false, false),
Arguments.of(false, false, true, false, false, false),
Arguments.of(false, false, true, true, false, true),
Arguments.of(false, true, false, false, false, false),
Arguments.of(false, true, false, true, false, false),
Arguments.of(false, true, true, false, false, false),
Arguments.of(false, true, true, true, false, true),
Arguments.of(true, false, false, false, false, false),
Arguments.of(true, false, false, true, false, false),
Arguments.of(true, false, true, false, false, false),
Arguments.of(true, false, true, true, false, true),
Arguments.of(true, true, false, false, true, false),
Arguments.of(true, true, false, true, true, false),
Arguments.of(true, true, true, false, true, false),
Arguments.of(true, true, true, true, true, true)
);
}
}

View File

@@ -1,61 +0,0 @@
package org.whispersystems.textsecuregcm.limits;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import io.dropwizard.util.Duration;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.Account;
class RateLimitResetMetricsManagerTest {
@RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
private RateLimitResetMetricsManager metricsManager;
private SimpleMeterRegistry meterRegistry;
@BeforeEach
void setUp() {
meterRegistry = new SimpleMeterRegistry();
metricsManager = new RateLimitResetMetricsManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(), meterRegistry);
}
@Test
void testRecordMetrics() {
final Account firstAccount = mock(Account.class);
when(firstAccount.getUuid()).thenReturn(UUID.randomUUID());
final Account secondAccount = mock(Account.class);
when(secondAccount.getUuid()).thenReturn(UUID.randomUUID());
metricsManager.recordMetrics(firstAccount, true, "counter", "enforced", "total", Duration.hours(1).toSeconds());
metricsManager.recordMetrics(firstAccount, true, "counter", "enforced", "total", Duration.hours(1).toSeconds());
metricsManager.recordMetrics(secondAccount, false, "counter", "unenforced", "total", Duration.hours(1).toSeconds());
final double counterTotal = meterRegistry.get("counter").counters().stream()
.map(Counter::count)
.reduce(Double::sum)
.orElseThrow();
assertEquals(3, counterTotal, 0.0);
final long enforcedCount = REDIS_CLUSTER_EXTENSION.getRedisCluster()
.withCluster(conn -> conn.sync().pfcount("enforced"));
assertEquals(1L, enforcedCount);
final long unenforcedCount = REDIS_CLUSTER_EXTENSION.getRedisCluster()
.withCluster(conn -> conn.sync().pfcount("unenforced"));
assertEquals(1L, unenforcedCount);
final long total = REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(conn -> conn.sync().pfcount("total"));
assertEquals(2L, total);
}
}

View File

@@ -1,120 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.limits;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.time.Duration;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRateLimitChallengeConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRateLimitsConfiguration;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
class UnsealedSenderRateLimiterTest {
@RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
private Account sender;
private Account firstDestination;
private Account secondDestination;
private UnsealedSenderRateLimiter unsealedSenderRateLimiter;
private DynamicRateLimitChallengeConfiguration rateLimitChallengeConfiguration;
@BeforeEach
void setUp() throws Exception {
final DynamicRateLimiters rateLimiters = mock(DynamicRateLimiters.class);
final CardinalityRateLimiter cardinalityRateLimiter =
new CardinalityRateLimiter(REDIS_CLUSTER_EXTENSION.getRedisCluster(), "test", Duration.ofDays(1), 1);
when(rateLimiters.getUnsealedSenderCardinalityLimiter()).thenReturn(cardinalityRateLimiter);
when(rateLimiters.getRateLimitResetLimiter()).thenReturn(mock(RateLimiter.class));
final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
final DynamicRateLimitsConfiguration rateLimitsConfiguration = mock(DynamicRateLimitsConfiguration.class);
rateLimitChallengeConfiguration = mock(DynamicRateLimitChallengeConfiguration.class);
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
when(dynamicConfiguration.getLimits()).thenReturn(rateLimitsConfiguration);
when(rateLimitsConfiguration.getUnsealedSenderDefaultCardinalityLimit()).thenReturn(1);
when(rateLimitsConfiguration.getUnsealedSenderPermitIncrement()).thenReturn(1);
when(dynamicConfiguration.getRateLimitChallengeConfiguration()).thenReturn(rateLimitChallengeConfiguration);
when(rateLimitChallengeConfiguration.isUnsealedSenderLimitEnforced()).thenReturn(true);
unsealedSenderRateLimiter = new UnsealedSenderRateLimiter(rateLimiters, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
dynamicConfigurationManager,
mock(RateLimitResetMetricsManager.class));
sender = mock(Account.class);
when(sender.getNumber()).thenReturn("+18005551111");
when(sender.getUuid()).thenReturn(UUID.randomUUID());
firstDestination = mock(Account.class);
when(firstDestination.getNumber()).thenReturn("+18005552222");
when(firstDestination.getUuid()).thenReturn(UUID.randomUUID());
secondDestination = mock(Account.class);
when(secondDestination.getNumber()).thenReturn("+18005553333");
when(secondDestination.getUuid()).thenReturn(UUID.randomUUID());
}
@Test
void validate() throws RateLimitExceededException {
unsealedSenderRateLimiter.validate(sender, firstDestination);
assertThrows(RateLimitExceededException.class, () -> unsealedSenderRateLimiter.validate(sender, secondDestination));
unsealedSenderRateLimiter.validate(sender, firstDestination);
}
@Test
void handleRateLimitReset() throws RateLimitExceededException {
unsealedSenderRateLimiter.validate(sender, firstDestination);
assertThrows(RateLimitExceededException.class, () -> unsealedSenderRateLimiter.validate(sender, secondDestination));
unsealedSenderRateLimiter.handleRateLimitReset(sender);
unsealedSenderRateLimiter.validate(sender, firstDestination);
unsealedSenderRateLimiter.validate(sender, secondDestination);
}
@Test
void enforcementConfiguration() throws RateLimitExceededException {
when(rateLimitChallengeConfiguration.isUnsealedSenderLimitEnforced()).thenReturn(false);
unsealedSenderRateLimiter.validate(sender, firstDestination);
unsealedSenderRateLimiter.validate(sender, secondDestination);
when(rateLimitChallengeConfiguration.isUnsealedSenderLimitEnforced()).thenReturn(true);
final Account thirdDestination = mock(Account.class);
when(thirdDestination.getNumber()).thenReturn("+18005554444");
when(thirdDestination.getUuid()).thenReturn(UUID.randomUUID());
assertThrows(RateLimitExceededException.class, () -> unsealedSenderRateLimiter.validate(sender, thirdDestination));
when(rateLimitChallengeConfiguration.isUnsealedSenderLimitEnforced()).thenReturn(false);
final Account fourthDestination = mock(Account.class);
when(fourthDestination.getNumber()).thenReturn("+18005555555");
when(fourthDestination.getUuid()).thenReturn(UUID.randomUUID());
unsealedSenderRateLimiter.validate(sender, fourthDestination);
}
}