mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 23:28:04 +01:00
Rate limiters code refactored
This commit is contained in:
@@ -20,7 +20,8 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration.RateLimitConfiguration;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiterConfig;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
|
||||
|
||||
@@ -274,30 +275,19 @@ class DynamicConfigurationTest {
|
||||
|
||||
@Test
|
||||
void testParseLimits() throws JsonProcessingException {
|
||||
{
|
||||
final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true");
|
||||
final DynamicConfiguration emptyConfig =
|
||||
DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow();
|
||||
|
||||
assertThat(emptyConfig.getLimits().getRateLimitReset().getBucketSize()).isEqualTo(2);
|
||||
assertThat(emptyConfig.getLimits().getRateLimitReset().getLeakRatePerMinute()).isEqualTo(2.0 / (60 * 24));
|
||||
}
|
||||
|
||||
{
|
||||
final String limitsConfig = REQUIRED_CONFIG.concat("""
|
||||
final String limitsConfig = REQUIRED_CONFIG.concat("""
|
||||
limits:
|
||||
rateLimitReset:
|
||||
bucketSize: 17
|
||||
leakRatePerMinute: 44
|
||||
""");
|
||||
|
||||
final RateLimitConfiguration resetRateLimitConfiguration =
|
||||
DynamicConfigurationManager.parseConfiguration(limitsConfig, DynamicConfiguration.class).orElseThrow()
|
||||
.getLimits().getRateLimitReset();
|
||||
final RateLimiterConfig resetRateLimiterConfig =
|
||||
DynamicConfigurationManager.parseConfiguration(limitsConfig, DynamicConfiguration.class).orElseThrow()
|
||||
.getLimits().get(RateLimiters.For.RATE_LIMIT_RESET.id());
|
||||
|
||||
assertThat(resetRateLimitConfiguration.getBucketSize()).isEqualTo(17);
|
||||
assertThat(resetRateLimitConfiguration.getLeakRatePerMinute()).isEqualTo(44);
|
||||
}
|
||||
assertThat(resetRateLimiterConfig.bucketSize()).isEqualTo(17);
|
||||
assertThat(resetRateLimiterConfig.leakRatePerMinute()).isEqualTo(44);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -171,6 +171,7 @@ class AccountControllerTest {
|
||||
private static RateLimiter usernameSetLimiter = mock(RateLimiter.class);
|
||||
private static RateLimiter usernameReserveLimiter = mock(RateLimiter.class);
|
||||
private static RateLimiter usernameLookupLimiter = mock(RateLimiter.class);
|
||||
private static RateLimiter checkAccountExistence = mock(RateLimiter.class);
|
||||
private static RegistrationServiceClient registrationServiceClient = mock(RegistrationServiceClient.class);
|
||||
private static TurnTokenGenerator turnTokenGenerator = mock(TurnTokenGenerator.class);
|
||||
private static Account senderPinAccount = mock(Account.class);
|
||||
@@ -250,6 +251,8 @@ class AccountControllerTest {
|
||||
when(rateLimiters.getUsernameSetLimiter()).thenReturn(usernameSetLimiter);
|
||||
when(rateLimiters.getUsernameReserveLimiter()).thenReturn(usernameReserveLimiter);
|
||||
when(rateLimiters.getUsernameLookupLimiter()).thenReturn(usernameLookupLimiter);
|
||||
when(rateLimiters.forDescriptor(eq(RateLimiters.For.USERNAME_LOOKUP))).thenReturn(usernameLookupLimiter);
|
||||
when(rateLimiters.forDescriptor(eq(RateLimiters.For.CHECK_ACCOUNT_EXISTENCE))).thenReturn(checkAccountExistence);
|
||||
|
||||
when(senderPinAccount.getLastSeen()).thenReturn(System.currentTimeMillis());
|
||||
when(senderPinAccount.getRegistrationLock()).thenReturn(
|
||||
@@ -2124,7 +2127,7 @@ class AccountControllerTest {
|
||||
when(accountsManager.getByAccountIdentifier(accountIdentifier)).thenReturn(Optional.of(account));
|
||||
|
||||
MockUtils.updateRateLimiterResponseToFail(
|
||||
rateLimiters, RateLimiters.Handle.CHECK_ACCOUNT_EXISTENCE, "127.0.0.1", expectedRetryAfter, true);
|
||||
rateLimiters, RateLimiters.For.CHECK_ACCOUNT_EXISTENCE, "127.0.0.1", expectedRetryAfter, true);
|
||||
|
||||
final Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/account/%s", accountIdentifier))
|
||||
@@ -2189,7 +2192,7 @@ class AccountControllerTest {
|
||||
void testLookupUsernameRateLimited() throws RateLimitExceededException {
|
||||
final Duration expectedRetryAfter = Duration.ofSeconds(13);
|
||||
MockUtils.updateRateLimiterResponseToFail(
|
||||
rateLimiters, RateLimiters.Handle.USERNAME_LOOKUP, "127.0.0.1", expectedRetryAfter, true);
|
||||
rateLimiters, RateLimiters.For.USERNAME_LOOKUP, "127.0.0.1", expectedRetryAfter, true);
|
||||
final Response response = resources.getJerseyTest()
|
||||
.target(String.format("v1/accounts/username_hash/%s", BASE_64_URL_USERNAME_HASH_1))
|
||||
.request()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2022 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2022 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.limits;
|
||||
package org.whispersystems.textsecuregcm.limits;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
@@ -15,7 +15,6 @@ import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.limits.LeakyBucket;
|
||||
|
||||
class LeakyBucketTest {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.limits;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -12,17 +17,17 @@ import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.whispersystems.textsecuregcm.spam.RateLimitChallengeListener;
|
||||
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
|
||||
import org.whispersystems.textsecuregcm.captcha.CaptchaChecker;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.spam.RateLimitChallengeListener;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
|
||||
class RateLimitChallengeManagerTest {
|
||||
|
||||
private PushChallengeManager pushChallengeManager;
|
||||
private CaptchaChecker captchaChecker;
|
||||
private DynamicRateLimiters rateLimiters;
|
||||
private RateLimiters rateLimiters;
|
||||
private RateLimitChallengeListener rateLimitChallengeListener;
|
||||
|
||||
private RateLimitChallengeManager rateLimitChallengeManager;
|
||||
@@ -31,7 +36,7 @@ class RateLimitChallengeManagerTest {
|
||||
void setUp() {
|
||||
pushChallengeManager = mock(PushChallengeManager.class);
|
||||
captchaChecker = mock(CaptchaChecker.class);
|
||||
rateLimiters = mock(DynamicRateLimiters.class);
|
||||
rateLimiters = mock(RateLimiters.class);
|
||||
rateLimitChallengeListener = mock(RateLimitChallengeListener.class);
|
||||
|
||||
rateLimitChallengeManager = new RateLimitChallengeManager(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
@@ -30,13 +30,13 @@ import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
|
||||
class RateLimitChallengeOptionManagerTest {
|
||||
|
||||
private DynamicRateLimitChallengeConfiguration rateLimitChallengeConfiguration;
|
||||
private DynamicRateLimiters rateLimiters;
|
||||
private RateLimiters rateLimiters;
|
||||
|
||||
private RateLimitChallengeOptionManager rateLimitChallengeOptionManager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
rateLimiters = mock(DynamicRateLimiters.class);
|
||||
rateLimiters = mock(RateLimiters.class);
|
||||
|
||||
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
|
||||
mock(DynamicConfigurationManager.class);
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.google.common.net.HttpHeaders;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Response;
|
||||
@@ -43,14 +42,14 @@ public class RateLimitedByIpTest {
|
||||
public static class Controller {
|
||||
@GET
|
||||
@Path("/strict")
|
||||
@RateLimitedByIp(RateLimiters.Handle.BACKUP_AUTH_CHECK)
|
||||
@RateLimitedByIp(RateLimiters.For.BACKUP_AUTH_CHECK)
|
||||
public Response strict() {
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/loose")
|
||||
@RateLimitedByIp(value = RateLimiters.Handle.BACKUP_AUTH_CHECK, failOnUnresolvedIp = false)
|
||||
@RateLimitedByIp(value = RateLimiters.For.BACKUP_AUTH_CHECK, failOnUnresolvedIp = false)
|
||||
public Response loose() {
|
||||
return Response.ok().build();
|
||||
}
|
||||
@@ -59,7 +58,7 @@ public class RateLimitedByIpTest {
|
||||
private static final RateLimiter RATE_LIMITER = Mockito.mock(RateLimiter.class);
|
||||
|
||||
private static final RateLimiters RATE_LIMITERS = MockUtils.buildMock(RateLimiters.class, rl ->
|
||||
Mockito.when(rl.byHandle(Mockito.eq(RateLimiters.Handle.BACKUP_AUTH_CHECK))).thenReturn(Optional.of(RATE_LIMITER)));
|
||||
Mockito.when(rl.forDescriptor(Mockito.eq(RateLimiters.For.BACKUP_AUTH_CHECK))).thenReturn(RATE_LIMITER));
|
||||
|
||||
private static final ResourceExtension RESOURCES = ResourceExtension.builder()
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2023 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.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.util.MockUtils;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class RateLimitersTest {
|
||||
|
||||
private final DynamicConfiguration configuration = mock(DynamicConfiguration.class);
|
||||
|
||||
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfig =
|
||||
MockUtils.buildMock(DynamicConfigurationManager.class, cfg -> when(cfg.getConfiguration()).thenReturn(configuration));
|
||||
|
||||
private final FaultTolerantRedisCluster redisCluster = mock(FaultTolerantRedisCluster.class);
|
||||
|
||||
private static final String BAD_YAML = """
|
||||
limits:
|
||||
smsVoicePrefix:
|
||||
bucketSize: 150
|
||||
leakRatePerMinute: 10
|
||||
unexpected:
|
||||
bucketSize: 4
|
||||
leakRatePerMinute: 2
|
||||
""";
|
||||
|
||||
private static final String GOOD_YAML = """
|
||||
limits:
|
||||
smsVoicePrefix:
|
||||
bucketSize: 150
|
||||
leakRatePerMinute: 10
|
||||
attachmentCreate:
|
||||
bucketSize: 4
|
||||
leakRatePerMinute: 2
|
||||
""";
|
||||
|
||||
public record GenericHolder(@Valid @NotNull @JsonProperty Map<String, RateLimiterConfig> limits) {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateConfigs() throws Exception {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
final GenericHolder cfg = DynamicConfigurationManager.parseConfiguration(BAD_YAML, GenericHolder.class).orElseThrow();
|
||||
final RateLimiters rateLimiters = new RateLimiters(cfg.limits(), dynamicConfig, redisCluster);
|
||||
rateLimiters.validateValuesAndConfigs();
|
||||
});
|
||||
|
||||
final GenericHolder cfg = DynamicConfigurationManager.parseConfiguration(GOOD_YAML, GenericHolder.class).orElseThrow();
|
||||
final RateLimiters rateLimiters = new RateLimiters(cfg.limits(), dynamicConfig, redisCluster);
|
||||
rateLimiters.validateValuesAndConfigs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDuplicates() throws Exception {
|
||||
final TestDescriptor td1 = new TestDescriptor("id1");
|
||||
final TestDescriptor td2 = new TestDescriptor("id2");
|
||||
final TestDescriptor td3 = new TestDescriptor("id3");
|
||||
final TestDescriptor tdDup = new TestDescriptor("id1");
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> new BaseRateLimiters<>(
|
||||
new TestDescriptor[] { td1, td2, td3, tdDup },
|
||||
Collections.emptyMap(),
|
||||
dynamicConfig,
|
||||
redisCluster) {});
|
||||
|
||||
new BaseRateLimiters<>(
|
||||
new TestDescriptor[] { td1, td2, td3 },
|
||||
Collections.emptyMap(),
|
||||
dynamicConfig,
|
||||
redisCluster) {};
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnchangingConfiguration() {
|
||||
final RateLimiters rateLimiters = new RateLimiters(Collections.emptyMap(), dynamicConfig, redisCluster);
|
||||
final RateLimiter limiter = rateLimiters.getRateLimitResetLimiter();
|
||||
final RateLimiterConfig expected = RateLimiters.For.RATE_LIMIT_RESET.defaultConfig();
|
||||
assertEquals(expected, limiter.config());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangingConfiguration() {
|
||||
final RateLimiterConfig initialRateLimiterConfig = new RateLimiterConfig(4, 1);
|
||||
final RateLimiterConfig updatedRateLimiterCongig = new RateLimiterConfig(17, 19);
|
||||
final RateLimiterConfig baseConfig = new RateLimiterConfig(1, 1);
|
||||
|
||||
final Map<String, RateLimiterConfig> limitsConfigMap = new HashMap<>();
|
||||
|
||||
limitsConfigMap.put(RateLimiters.For.RECAPTCHA_CHALLENGE_ATTEMPT.id(), baseConfig);
|
||||
limitsConfigMap.put(RateLimiters.For.RECAPTCHA_CHALLENGE_SUCCESS.id(), baseConfig);
|
||||
|
||||
when(configuration.getLimits()).thenReturn(limitsConfigMap);
|
||||
|
||||
final RateLimiters rateLimiters = new RateLimiters(Collections.emptyMap(), dynamicConfig, redisCluster);
|
||||
final RateLimiter limiter = rateLimiters.getRateLimitResetLimiter();
|
||||
|
||||
limitsConfigMap.put(RateLimiters.For.RATE_LIMIT_RESET.id(), initialRateLimiterConfig);
|
||||
assertEquals(initialRateLimiterConfig, limiter.config());
|
||||
|
||||
assertEquals(baseConfig, rateLimiters.getRecaptchaChallengeAttemptLimiter().config());
|
||||
assertEquals(baseConfig, rateLimiters.getRecaptchaChallengeSuccessLimiter().config());
|
||||
|
||||
limitsConfigMap.put(RateLimiters.For.RATE_LIMIT_RESET.id(), updatedRateLimiterCongig);
|
||||
assertEquals(updatedRateLimiterCongig, limiter.config());
|
||||
|
||||
assertEquals(baseConfig, rateLimiters.getRecaptchaChallengeAttemptLimiter().config());
|
||||
assertEquals(baseConfig, rateLimiters.getRecaptchaChallengeSuccessLimiter().config());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRateLimiterHasItsPrioritiesStraight() throws Exception {
|
||||
final RateLimiters.For descriptor = RateLimiters.For.RECAPTCHA_CHALLENGE_ATTEMPT;
|
||||
final RateLimiterConfig configForDynamic = new RateLimiterConfig(1, 1);
|
||||
final RateLimiterConfig configForStatic = new RateLimiterConfig(2, 2);
|
||||
final RateLimiterConfig defaultConfig = descriptor.defaultConfig();
|
||||
|
||||
final Map<String, RateLimiterConfig> mapForDynamic = new HashMap<>();
|
||||
final Map<String, RateLimiterConfig> mapForStatic = new HashMap<>();
|
||||
|
||||
when(configuration.getLimits()).thenReturn(mapForDynamic);
|
||||
|
||||
final RateLimiters rateLimiters = new RateLimiters(mapForStatic, dynamicConfig, redisCluster);
|
||||
final RateLimiter limiter = rateLimiters.forDescriptor(descriptor);
|
||||
|
||||
// test only default is present
|
||||
mapForDynamic.remove(descriptor.id());
|
||||
mapForStatic.remove(descriptor.id());
|
||||
assertEquals(defaultConfig, limiter.config());
|
||||
|
||||
// test dynamic and no static
|
||||
mapForDynamic.put(descriptor.id(), configForDynamic);
|
||||
mapForStatic.remove(descriptor.id());
|
||||
assertEquals(configForDynamic, limiter.config());
|
||||
|
||||
// test dynamic and static
|
||||
mapForDynamic.put(descriptor.id(), configForDynamic);
|
||||
mapForStatic.put(descriptor.id(), configForStatic);
|
||||
assertEquals(configForDynamic, limiter.config());
|
||||
|
||||
// test static, but no dynamic
|
||||
mapForDynamic.remove(descriptor.id());
|
||||
mapForStatic.put(descriptor.id(), configForStatic);
|
||||
assertEquals(configForStatic, limiter.config());
|
||||
}
|
||||
|
||||
private record TestDescriptor(String id) implements RateLimiterDescriptor {
|
||||
|
||||
@Override
|
||||
public boolean isDynamic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RateLimiterConfig defaultConfig() {
|
||||
return new RateLimiterConfig(1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
@@ -42,17 +42,16 @@ import org.whispersystems.textsecuregcm.entities.AttachmentDescriptorV3;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.MockUtils;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
class AttachmentControllerTest {
|
||||
|
||||
private static RateLimiters rateLimiters = mock(RateLimiters.class );
|
||||
private static RateLimiter rateLimiter = mock(RateLimiter.class );
|
||||
private static final RateLimiter RATE_LIMITER = mock(RateLimiter.class);
|
||||
|
||||
static {
|
||||
when(rateLimiters.getAttachmentLimiter()).thenReturn(rateLimiter);
|
||||
}
|
||||
private static final RateLimiters RATE_LIMITERS = MockUtils.buildMock(RateLimiters.class, rateLimiters ->
|
||||
when(rateLimiters.getAttachmentLimiter()).thenReturn(RATE_LIMITER));
|
||||
|
||||
public static final String RSA_PRIVATE_KEY_PEM;
|
||||
|
||||
@@ -80,8 +79,8 @@ class AttachmentControllerTest {
|
||||
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new AttachmentControllerV2(rateLimiters, "accessKey", "accessSecret", "us-east-1", "attachmentv2-bucket"))
|
||||
.addResource(new AttachmentControllerV3(rateLimiters, "some-cdn.signal.org", "signal@example.com", 1000, "/attach-here", RSA_PRIVATE_KEY_PEM))
|
||||
.addResource(new AttachmentControllerV2(RATE_LIMITERS, "accessKey", "accessSecret", "us-east-1", "attachmentv2-bucket"))
|
||||
.addResource(new AttachmentControllerV3(RATE_LIMITERS, "some-cdn.signal.org", "signal@example.com", 1000, "/attach-here", RSA_PRIVATE_KEY_PEM))
|
||||
.build();
|
||||
} catch (IOException | InvalidKeyException | InvalidKeySpecException e) {
|
||||
throw new AssertionError(e);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2022 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package org.whispersystems.textsecuregcm.tests.limits;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration.RateLimitConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRateLimitsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.limits.DynamicRateLimiters;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
|
||||
class DynamicRateLimitsTest {
|
||||
|
||||
private DynamicConfigurationManager<DynamicConfiguration> dynamicConfig;
|
||||
private FaultTolerantRedisCluster redisCluster;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.dynamicConfig = mock(DynamicConfigurationManager.class);
|
||||
this.redisCluster = mock(FaultTolerantRedisCluster.class);
|
||||
|
||||
DynamicConfiguration defaultConfig = new DynamicConfiguration();
|
||||
when(dynamicConfig.getConfiguration()).thenReturn(defaultConfig);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnchangingConfiguration() {
|
||||
DynamicRateLimiters rateLimiters = new DynamicRateLimiters(redisCluster, dynamicConfig);
|
||||
|
||||
RateLimiter limiter = rateLimiters.getRateLimitResetLimiter();
|
||||
|
||||
assertThat(limiter.getBucketSize()).isEqualTo(dynamicConfig.getConfiguration().getLimits().getRateLimitReset().getBucketSize());
|
||||
assertThat(limiter.getLeakRatePerMinute()).isEqualTo(dynamicConfig.getConfiguration().getLimits().getRateLimitReset().getLeakRatePerMinute());
|
||||
assertSame(rateLimiters.getRateLimitResetLimiter(), limiter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangingConfiguration() {
|
||||
DynamicConfiguration configuration = mock(DynamicConfiguration.class);
|
||||
DynamicRateLimitsConfiguration limitsConfiguration = mock(DynamicRateLimitsConfiguration.class);
|
||||
|
||||
when(configuration.getLimits()).thenReturn(limitsConfiguration);
|
||||
when(limitsConfiguration.getRecaptchaChallengeAttempt()).thenReturn(new RateLimitConfiguration());
|
||||
when(limitsConfiguration.getRecaptchaChallengeSuccess()).thenReturn(new RateLimitConfiguration());
|
||||
when(limitsConfiguration.getPushChallengeAttempt()).thenReturn(new RateLimitConfiguration());
|
||||
when(limitsConfiguration.getPushChallengeSuccess()).thenReturn(new RateLimitConfiguration());
|
||||
|
||||
final RateLimitConfiguration initialRateLimitConfiguration = new RateLimitConfiguration(4, 1);
|
||||
when(limitsConfiguration.getRateLimitReset()).thenReturn(initialRateLimitConfiguration);
|
||||
|
||||
when(dynamicConfig.getConfiguration()).thenReturn(configuration);
|
||||
|
||||
DynamicRateLimiters rateLimiters = new DynamicRateLimiters(redisCluster, dynamicConfig);
|
||||
|
||||
RateLimiter limiter = rateLimiters.getRateLimitResetLimiter();
|
||||
|
||||
assertThat(limiter.getBucketSize()).isEqualTo(4);
|
||||
assertThat(limiter.getLeakRatePerMinute()).isEqualTo(1);
|
||||
assertSame(rateLimiters.getRateLimitResetLimiter(), limiter);
|
||||
|
||||
when(limitsConfiguration.getRateLimitReset()).thenReturn(new RateLimitConfiguration(17, 19));
|
||||
|
||||
RateLimiter changed = rateLimiters.getRateLimitResetLimiter();
|
||||
|
||||
assertThat(changed.getBucketSize()).isEqualTo(17);
|
||||
assertThat(changed.getLeakRatePerMinute()).isEqualTo(19);
|
||||
assertNotSame(limiter, changed);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,10 +45,10 @@ public final class MockUtils {
|
||||
|
||||
public static void updateRateLimiterResponseToAllow(
|
||||
final RateLimiters rateLimitersMock,
|
||||
final RateLimiters.Handle handle,
|
||||
final RateLimiters.For handle,
|
||||
final String input) {
|
||||
final RateLimiter mockRateLimiter = Mockito.mock(RateLimiter.class);
|
||||
doReturn(Optional.of(mockRateLimiter)).when(rateLimitersMock).byHandle(eq(handle));
|
||||
doReturn(Optional.of(mockRateLimiter)).when(rateLimitersMock).forDescriptor(eq(handle));
|
||||
try {
|
||||
doNothing().when(mockRateLimiter).validate(eq(input));
|
||||
} catch (final RateLimitExceededException e) {
|
||||
@@ -58,12 +58,12 @@ public final class MockUtils {
|
||||
|
||||
public static void updateRateLimiterResponseToFail(
|
||||
final RateLimiters rateLimitersMock,
|
||||
final RateLimiters.Handle handle,
|
||||
final RateLimiters.For handle,
|
||||
final String input,
|
||||
final Duration retryAfter,
|
||||
final boolean legacyStatusCode) {
|
||||
final RateLimiter mockRateLimiter = Mockito.mock(RateLimiter.class);
|
||||
doReturn(Optional.of(mockRateLimiter)).when(rateLimitersMock).byHandle(eq(handle));
|
||||
doReturn(mockRateLimiter).when(rateLimitersMock).forDescriptor(eq(handle));
|
||||
try {
|
||||
doThrow(new RateLimitExceededException(retryAfter, legacyStatusCode)).when(mockRateLimiter).validate(eq(input));
|
||||
} catch (final RateLimitExceededException e) {
|
||||
|
||||
Reference in New Issue
Block a user