mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-29 22:10:50 +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
@@ -52,11 +52,9 @@ import org.whispersystems.textsecuregcm.entities.PreKeyResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyState;
|
||||
import org.whispersystems.textsecuregcm.entities.RateLimitChallenge;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.limits.PreKeyRateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimitChallengeManager;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitChallengeExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.ServerRejectedExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
@@ -97,8 +95,6 @@ class KeysControllerTest {
|
||||
|
||||
private final static Keys KEYS = mock(Keys.class );
|
||||
private final static AccountsManager accounts = mock(AccountsManager.class );
|
||||
private final static PreKeyRateLimiter preKeyRateLimiter = mock(PreKeyRateLimiter.class );
|
||||
private final static RateLimitChallengeManager rateLimitChallengeManager = mock(RateLimitChallengeManager.class );
|
||||
private final static Account existsAccount = mock(Account.class );
|
||||
|
||||
private static final RateLimiters rateLimiters = mock(RateLimiters.class);
|
||||
@@ -109,10 +105,8 @@ class KeysControllerTest {
|
||||
.addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(
|
||||
AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new RateLimitChallengeExceptionMapper(rateLimitChallengeManager))
|
||||
.addResource(new ServerRejectedExceptionMapper())
|
||||
.addResource(
|
||||
new KeysController(rateLimiters, KEYS, accounts, preKeyRateLimiter, rateLimitChallengeManager))
|
||||
.addResource(new KeysController(rateLimiters, KEYS, accounts))
|
||||
.build();
|
||||
|
||||
@BeforeEach
|
||||
@@ -190,11 +184,9 @@ class KeysControllerTest {
|
||||
reset(
|
||||
KEYS,
|
||||
accounts,
|
||||
preKeyRateLimiter,
|
||||
existsAccount,
|
||||
rateLimiters,
|
||||
rateLimiter,
|
||||
rateLimitChallengeManager
|
||||
rateLimiter
|
||||
);
|
||||
|
||||
clearInvocations(AuthHelper.VALID_DEVICE);
|
||||
@@ -582,51 +574,4 @@ class KeysControllerTest {
|
||||
verify(AuthHelper.DISABLED_DEVICE).setSignedPreKey(eq(signedPreKey));
|
||||
verify(accounts).update(eq(AuthHelper.DISABLED_ACCOUNT), any());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testRateLimitChallenge(boolean clientBelowMinimumVersion) throws RateLimitExceededException {
|
||||
|
||||
Duration retryAfter = Duration.ofMinutes(1);
|
||||
doThrow(new RateLimitExceededException(retryAfter))
|
||||
.when(preKeyRateLimiter).validate(any());
|
||||
|
||||
when(
|
||||
rateLimitChallengeManager.isClientBelowMinimumVersion("Signal-Android/5.1.2 Android/30")).thenReturn(
|
||||
clientBelowMinimumVersion);
|
||||
when(rateLimitChallengeManager.getChallengeOptions(AuthHelper.VALID_ACCOUNT))
|
||||
.thenReturn(
|
||||
List.of(RateLimitChallengeManager.OPTION_PUSH_CHALLENGE, RateLimitChallengeManager.OPTION_RECAPTCHA));
|
||||
|
||||
Response result = resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/*", EXISTS_UUID.toString()))
|
||||
.request()
|
||||
.header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("1337".getBytes()))
|
||||
.header("User-Agent", "Signal-Android/5.1.2 Android/30")
|
||||
.get();
|
||||
|
||||
// unidentified access should not be rate limited
|
||||
assertThat(result.getStatus()).isEqualTo(200);
|
||||
|
||||
result = resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/*", EXISTS_UUID.toString()))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.header("User-Agent", "Signal-Android/5.1.2 Android/30")
|
||||
.get();
|
||||
|
||||
if (clientBelowMinimumVersion) {
|
||||
assertThat(result.getStatus()).isEqualTo(508);
|
||||
} else {
|
||||
assertThat(result.getStatus()).isEqualTo(428);
|
||||
|
||||
RateLimitChallenge rateLimitChallenge = result.readEntity(RateLimitChallenge.class);
|
||||
|
||||
assertThat(rateLimitChallenge.getToken()).isNotBlank();
|
||||
assertThat(rateLimitChallenge.getOptions()).isNotEmpty();
|
||||
assertThat(rateLimitChallenge.getOptions()).contains("recaptcha");
|
||||
assertThat(rateLimitChallenge.getOptions()).contains("pushChallenge");
|
||||
assertThat(Long.parseLong(result.getHeaderString("Retry-After"))).isEqualTo(retryAfter.toSeconds());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,11 @@ import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
|
||||
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.CardinalityRateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.DynamicRateLimiters;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||
@@ -38,11 +35,11 @@ class DynamicRateLimitsTest {
|
||||
void testUnchangingConfiguration() {
|
||||
DynamicRateLimiters rateLimiters = new DynamicRateLimiters(redisCluster, dynamicConfig);
|
||||
|
||||
RateLimiter limiter = rateLimiters.getUnsealedIpLimiter();
|
||||
RateLimiter limiter = rateLimiters.getRateLimitResetLimiter();
|
||||
|
||||
assertThat(limiter.getBucketSize()).isEqualTo(dynamicConfig.getConfiguration().getLimits().getUnsealedSenderIp().getBucketSize());
|
||||
assertThat(limiter.getLeakRatePerMinute()).isEqualTo(dynamicConfig.getConfiguration().getLimits().getUnsealedSenderIp().getLeakRatePerMinute());
|
||||
assertSame(rateLimiters.getUnsealedIpLimiter(), limiter);
|
||||
assertThat(limiter.getBucketSize()).isEqualTo(dynamicConfig.getConfiguration().getLimits().getRateLimitReset().getBucketSize());
|
||||
assertThat(limiter.getLeakRatePerMinute()).isEqualTo(dynamicConfig.getConfiguration().getLimits().getRateLimitReset().getLeakRatePerMinute());
|
||||
assertSame(rateLimiters.getRateLimitResetLimiter(), limiter);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -51,33 +48,30 @@ class DynamicRateLimitsTest {
|
||||
DynamicRateLimitsConfiguration limitsConfiguration = mock(DynamicRateLimitsConfiguration.class);
|
||||
|
||||
when(configuration.getLimits()).thenReturn(limitsConfiguration);
|
||||
when(limitsConfiguration.getUnsealedSenderNumber()).thenReturn(new RateLimitsConfiguration.CardinalityRateLimitConfiguration(10, Duration.ofHours(1)));
|
||||
when(limitsConfiguration.getRecaptchaChallengeAttempt()).thenReturn(new RateLimitConfiguration());
|
||||
when(limitsConfiguration.getRecaptchaChallengeSuccess()).thenReturn(new RateLimitConfiguration());
|
||||
when(limitsConfiguration.getPushChallengeAttempt()).thenReturn(new RateLimitConfiguration());
|
||||
when(limitsConfiguration.getPushChallengeSuccess()).thenReturn(new RateLimitConfiguration());
|
||||
when(limitsConfiguration.getDailyPreKeys()).thenReturn(new RateLimitConfiguration());
|
||||
|
||||
final RateLimitConfiguration initialRateLimitConfiguration = new RateLimitConfiguration(4, 1.0);
|
||||
when(limitsConfiguration.getUnsealedSenderIp()).thenReturn(initialRateLimitConfiguration);
|
||||
final RateLimitConfiguration initialRateLimitConfiguration = new RateLimitConfiguration(4, 1);
|
||||
when(limitsConfiguration.getRateLimitReset()).thenReturn(initialRateLimitConfiguration);
|
||||
|
||||
when(dynamicConfig.getConfiguration()).thenReturn(configuration);
|
||||
|
||||
DynamicRateLimiters rateLimiters = new DynamicRateLimiters(redisCluster, dynamicConfig);
|
||||
|
||||
CardinalityRateLimiter limiter = rateLimiters.getUnsealedSenderCardinalityLimiter();
|
||||
RateLimiter limiter = rateLimiters.getRateLimitResetLimiter();
|
||||
|
||||
assertThat(limiter.getDefaultMaxCardinality()).isEqualTo(10);
|
||||
assertThat(limiter.getInitialTtl()).isEqualTo(Duration.ofHours(1));
|
||||
assertSame(rateLimiters.getUnsealedSenderCardinalityLimiter(), limiter);
|
||||
assertThat(limiter.getBucketSize()).isEqualTo(4);
|
||||
assertThat(limiter.getLeakRatePerMinute()).isEqualTo(1);
|
||||
assertSame(rateLimiters.getRateLimitResetLimiter(), limiter);
|
||||
|
||||
when(limitsConfiguration.getUnsealedSenderNumber()).thenReturn(new RateLimitsConfiguration.CardinalityRateLimitConfiguration(20, Duration.ofHours(2)));
|
||||
when(limitsConfiguration.getRateLimitReset()).thenReturn(new RateLimitConfiguration(17, 19));
|
||||
|
||||
CardinalityRateLimiter changed = rateLimiters.getUnsealedSenderCardinalityLimiter();
|
||||
RateLimiter changed = rateLimiters.getRateLimitResetLimiter();
|
||||
|
||||
assertThat(changed.getDefaultMaxCardinality()).isEqualTo(20);
|
||||
assertThat(changed.getInitialTtl()).isEqualTo(Duration.ofHours(2));
|
||||
assertThat(changed.getBucketSize()).isEqualTo(17);
|
||||
assertThat(changed.getLeakRatePerMinute()).isEqualTo(19);
|
||||
assertNotSame(limiter, changed);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user