mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-22 09:18:05 +01:00
Migrate challenge-issuing rate limiters to the abusive message filter
This commit is contained in:
committed by
Jon Chambers
parent
9628f147f1
commit
14cff958e9
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user