Add a prefix limiter

This commit is contained in:
Moxie Marlinspike 2019-04-04 09:43:05 -07:00
parent b3a1e6d0a5
commit 0b5053d49a
6 changed files with 63 additions and 7 deletions

View File

@ -32,6 +32,9 @@ public class RateLimitsConfiguration {
@JsonProperty
private RateLimitConfiguration smsVoiceIp = new RateLimitConfiguration(1000, 1000);
@JsonProperty
private RateLimitConfiguration smsVoicePrefix = new RateLimitConfiguration(1000, 1000);
@JsonProperty
private RateLimitConfiguration verifyNumber = new RateLimitConfiguration(2, 2);
@ -102,6 +105,10 @@ public class RateLimitsConfiguration {
return smsVoiceIp;
}
public RateLimitConfiguration getSmsVoicePrefix() {
return smsVoicePrefix;
}
public RateLimitConfiguration getVerifyNumber() {
return verifyNumber;
}

View File

@ -80,13 +80,16 @@ import io.dropwizard.auth.Auth;
@Path("/v1/accounts")
public class AccountController {
private final Logger logger = LoggerFactory.getLogger(AccountController.class);
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
private final Meter newUserMeter = metricRegistry.meter(name(AccountController.class, "brand_new_user" ));
private final Meter blockedHostMeter = metricRegistry.meter(name(AccountController.class, "blocked_host" ));
private final Meter filteredHostMeter = metricRegistry.meter(name(AccountController.class, "filtered_host" ));
private final Meter captchaSuccessMeter = metricRegistry.meter(name(AccountController.class, "captcha_success"));
private final Meter captchaFailureMeter = metricRegistry.meter(name(AccountController.class, "captcha_failure"));
private final Logger logger = LoggerFactory.getLogger(AccountController.class);
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
private final Meter newUserMeter = metricRegistry.meter(name(AccountController.class, "brand_new_user" ));
private final Meter blockedHostMeter = metricRegistry.meter(name(AccountController.class, "blocked_host" ));
private final Meter filteredHostMeter = metricRegistry.meter(name(AccountController.class, "filtered_host" ));
private final Meter rateLimitedHostMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_host" ));
private final Meter rateLimitedPrefixMeter = metricRegistry.meter(name(AccountController.class, "rate_limited_prefix"));
private final Meter captchaSuccessMeter = metricRegistry.meter(name(AccountController.class, "captcha_success" ));
private final Meter captchaFailureMeter = metricRegistry.meter(name(AccountController.class, "captcha_failure" ));
private final PendingAccountsManager pendingAccounts;
private final AccountsManager accounts;
@ -420,10 +423,19 @@ public class AccountController {
rateLimiters.getSmsVoiceIpLimiter().validate(requester);
} catch (RateLimitExceededException e) {
logger.info("Rate limited exceeded: " + transport + ", " + number + ", " + requester + " (" + forwardedFor + ")");
rateLimitedPrefixMeter.mark();
return true;
}
}
try {
rateLimiters.getSmsVoicePrefixLimiter().validate(Util.getNumberPrefix(number));
} catch (RateLimitExceededException e) {
logger.info("Prefix rate limit exceeded: " + transport + ", " + number + ", (" + String.join(", ", requesters) + ")");
rateLimitedPrefixMeter.mark();
return true;
}
return false;
}

View File

@ -26,6 +26,7 @@ public class RateLimiters {
private final RateLimiter voiceDestinationLimiter;
private final RateLimiter voiceDestinationDailyLimiter;
private final RateLimiter smsVoiceIpLimiter;
private final RateLimiter smsVoicePrefixLimiter;
private final RateLimiter verifyLimiter;
private final RateLimiter pinLimiter;
@ -58,6 +59,10 @@ public class RateLimiters {
config.getSmsVoiceIp().getBucketSize(),
config.getSmsVoiceIp().getLeakRatePerMinute());
this.smsVoicePrefixLimiter = new RateLimiter(cacheClient, "smsVoicePrefix",
config.getSmsVoicePrefix().getBucketSize(),
config.getSmsVoicePrefix().getLeakRatePerMinute());
this.verifyLimiter = new RateLimiter(cacheClient, "verify",
config.getVerifyNumber().getBucketSize(),
config.getVerifyNumber().getLeakRatePerMinute());
@ -131,6 +136,10 @@ public class RateLimiters {
return smsVoiceIpLimiter;
}
public RateLimiter getSmsVoicePrefixLimiter() {
return smsVoicePrefixLimiter;
}
public RateLimiter getVoiceDestinationLimiter() {
return voiceDestinationLimiter;
}

View File

@ -60,6 +60,14 @@ public class Util {
else return "0";
}
public static String getNumberPrefix(String number) {
String countryCode = getCountryCode(number);
int remaining = number.length() - (1 + countryCode.length());
int prefixLength = Math.min(4, remaining);
return number.substring(0, 1 + countryCode.length() + prefixLength);
}
public static String encodeFormParams(Map<String, String> params) {
try {
StringBuffer buffer = new StringBuffer();

View File

@ -63,6 +63,7 @@ public class AccountControllerTest {
private RateLimiter rateLimiter = mock(RateLimiter.class );
private RateLimiter pinLimiter = mock(RateLimiter.class );
private RateLimiter smsVoiceIpLimiter = mock(RateLimiter.class );
private RateLimiter smsVoicePrefixLimiter = mock(RateLimiter.class);
private SmsSender smsSender = mock(SmsSender.class );
private DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
private MessagesManager storedMessages = mock(MessagesManager.class );
@ -98,6 +99,7 @@ public class AccountControllerTest {
when(rateLimiters.getVerifyLimiter()).thenReturn(rateLimiter);
when(rateLimiters.getPinLimiter()).thenReturn(pinLimiter);
when(rateLimiters.getSmsVoiceIpLimiter()).thenReturn(smsVoiceIpLimiter);
when(rateLimiters.getSmsVoicePrefixLimiter()).thenReturn(smsVoicePrefixLimiter);
when(timeProvider.getCurrentTimeMillis()).thenReturn(System.currentTimeMillis());

View File

@ -0,0 +1,18 @@
package org.whispersystems.textsecuregcm.tests.util;
import org.junit.Test;
import org.whispersystems.textsecuregcm.util.Util;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class NumberPrefixTest {
@Test
public void testPrefixes() {
assertThat(Util.getNumberPrefix("+14151234567")).isEqualTo("+14151");
assertThat(Util.getNumberPrefix("+22587654321")).isEqualTo("+2258765");
assertThat(Util.getNumberPrefix("+298654321")).isEqualTo("+2986543");
assertThat(Util.getNumberPrefix("+12")).isEqualTo("+12");
}
}