diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index c19354acb..d0f9ecd6d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -567,7 +567,7 @@ public class WhisperServerService extends Application commonControllers = List.of( new AttachmentControllerV1(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getBucket()), diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index ac61b0c34..6dd6a747c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -207,17 +207,17 @@ public class AccountController { throw new WebApplicationException(Response.status(400).build()); } - String requester = ForwardedIpUtil.getMostRecentProxy(forwardedFor).orElseThrow(); + String sourceHost = ForwardedIpUtil.getMostRecentProxy(forwardedFor).orElseThrow(); Optional storedChallenge = pendingAccounts.getCodeForNumber(number); - CaptchaRequirement requirement = requiresCaptcha(number, transport, forwardedFor, requester, captcha, storedChallenge, pushChallenge); + CaptchaRequirement requirement = requiresCaptcha(number, transport, forwardedFor, sourceHost, captcha, storedChallenge, pushChallenge); if (requirement.isCaptchaRequired()) { captchaRequiredMeter.mark(); - if (requirement.isAutoBlock() && shouldAutoBlock(requester)) { - logger.info("Auto-block: " + requester); - abusiveHostRules.setBlockedHost(requester, "Auto-Block"); + if (requirement.isAutoBlock() && shouldAutoBlock(sourceHost)) { + logger.info("Auto-block: {}", sourceHost); + abusiveHostRules.setBlockedHost(sourceHost, "Auto-Block"); } return Response.status(402).build(); @@ -405,7 +405,7 @@ public class AccountController { @Path("/turn/") @Produces(MediaType.APPLICATION_JSON) public TurnToken getTurnToken(@Auth Account account) throws RateLimitExceededException { - rateLimiters.getTurnLimiter().validate(account.getNumber()); + rateLimiters.getTurnLimiter().validate(account.getUuid()); return turnTokenGenerator.generate(); } @@ -568,7 +568,7 @@ public class AccountController { @Path("/username/{username}") @Produces(MediaType.APPLICATION_JSON) public Response setUsername(@Auth Account account, @PathParam("username") String username) throws RateLimitExceededException { - rateLimiters.getUsernameSetLimiter().validate(account.getUuid().toString()); + rateLimiters.getUsernameSetLimiter().validate(account.getUuid()); if (username == null || username.isEmpty()) { return Response.status(Response.Status.BAD_REQUEST).build(); @@ -588,14 +588,14 @@ public class AccountController { } private CaptchaRequirement requiresCaptcha(String number, String transport, String forwardedFor, - String requester, + String sourceHost, Optional captchaToken, Optional storedVerificationCode, Optional pushChallenge) { if (captchaToken.isPresent()) { - boolean validToken = recaptchaClient.verify(captchaToken.get(), requester); + boolean validToken = recaptchaClient.verify(captchaToken.get(), sourceHost); if (validToken) { captchaSuccessMeter.mark(); @@ -633,18 +633,18 @@ public class AccountController { } } - List abuseRules = abusiveHostRules.getAbusiveHostRulesFor(requester); + List abuseRules = abusiveHostRules.getAbusiveHostRulesFor(sourceHost); for (AbusiveHostRule abuseRule : abuseRules) { if (abuseRule.isBlocked()) { - logger.info("Blocked host: " + transport + ", " + number + ", " + requester + " (" + forwardedFor + ")"); + logger.info("Blocked host: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor); blockedHostMeter.mark(); return new CaptchaRequirement(true, false); } if (!abuseRule.getRegions().isEmpty()) { if (abuseRule.getRegions().stream().noneMatch(number::startsWith)) { - logger.info("Restricted host: " + transport + ", " + number + ", " + requester + " (" + forwardedFor + ")"); + logger.info("Restricted host: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor); filteredHostMeter.mark(); return new CaptchaRequirement(true, false); } @@ -652,9 +652,9 @@ public class AccountController { } try { - rateLimiters.getSmsVoiceIpLimiter().validate(requester); + rateLimiters.getSmsVoiceIpLimiter().validate(sourceHost); } catch (RateLimitExceededException e) { - logger.info("Rate limited exceeded: " + transport + ", " + number + ", " + requester + " (" + forwardedFor + ")"); + logger.info("Rate limit exceeded: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor); rateLimitedHostMeter.mark(); return new CaptchaRequirement(true, true); } @@ -662,7 +662,7 @@ public class AccountController { try { rateLimiters.getSmsVoicePrefixLimiter().validate(Util.getNumberPrefix(number)); } catch (RateLimitExceededException e) { - logger.info("Prefix rate limit exceeded: " + transport + ", " + number + ", (" + forwardedFor + ")"); + logger.info("Prefix rate limit exceeded: {}, {}, {} ({})", transport, number, sourceHost, forwardedFor); rateLimitedPrefixMeter.mark(); return new CaptchaRequirement(true, true); } @@ -682,9 +682,9 @@ public class AccountController { accounts.delete(account, AccountsManager.DeletionReason.USER_REQUEST); } - private boolean shouldAutoBlock(String requester) { + private boolean shouldAutoBlock(String sourceHost) { try { - rateLimiters.getAutoBlockLimiter().validate(requester); + rateLimiters.getAutoBlockLimiter().validate(sourceHost); } catch (RateLimitExceededException e) { return true; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV1.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV1.java index 67ed5bbc2..cf25aef65 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV1.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV1.java @@ -49,7 +49,7 @@ public class AttachmentControllerV1 extends AttachmentControllerBase { throws RateLimitExceededException { if (account.isRateLimited()) { - rateLimiters.getAttachmentLimiter().validate(account.getNumber()); + rateLimiters.getAttachmentLimiter().validate(account.getUuid()); } long attachmentId = generateAttachmentId(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java index 44bdf9dfa..54b576ca3 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java @@ -41,7 +41,7 @@ public class AttachmentControllerV2 extends AttachmentControllerBase { @Produces(MediaType.APPLICATION_JSON) @Path("/form/upload") public AttachmentDescriptorV2 getAttachmentUploadForm(@Auth Account account) throws RateLimitExceededException { - rateLimiter.validate(account.getNumber()); + rateLimiter.validate(account.getUuid()); ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); long attachmentId = generateAttachmentId(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV3.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV3.java index e5f1bb4f4..fc767cca0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV3.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV3.java @@ -58,7 +58,7 @@ public class AttachmentControllerV3 extends AttachmentControllerBase { @Produces(MediaType.APPLICATION_JSON) @Path("/form/upload") public AttachmentDescriptorV3 getAttachmentUploadForm(@Auth Account account) throws RateLimitExceededException { - rateLimiter.validate(account.getNumber()); + rateLimiter.validate(account.getUuid()); final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); final String key = generateAttachmentKey(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index 229bd4550..31f0faf16 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -112,7 +112,7 @@ public class DeviceController { public VerificationCode createDeviceToken(@Auth Account account) throws RateLimitExceededException, DeviceLimitExceededException { - rateLimiters.getAllocateDeviceLimiter().validate(account.getNumber()); + rateLimiters.getAllocateDeviceLimiter().validate(account.getUuid()); int maxDeviceLimit = MAX_DEVICES; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java index 529f4ee86..e8a44fd55 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java @@ -40,11 +40,9 @@ import org.whispersystems.textsecuregcm.limits.PreKeyRateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimitChallengeException; import org.whispersystems.textsecuregcm.limits.RateLimitChallengeManager; import org.whispersystems.textsecuregcm.limits.RateLimiters; -import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.KeysDynamoDb; import org.whispersystems.textsecuregcm.util.Util; @@ -57,7 +55,6 @@ public class KeysController { private final AccountsManager accounts; private final PreKeyRateLimiter preKeyRateLimiter; - private final DynamicConfigurationManager dynamicConfigurationManager; private final RateLimitChallengeManager rateLimitChallengeManager; private static final String PREKEY_REQUEST_COUNTER_NAME = name(KeysController.class, "preKeyGet"); @@ -69,15 +66,12 @@ public class KeysController { public KeysController(RateLimiters rateLimiters, KeysDynamoDb keysDynamoDb, AccountsManager accounts, PreKeyRateLimiter preKeyRateLimiter, - DynamicConfigurationManager dynamicConfigurationManager, RateLimitChallengeManager rateLimitChallengeManager) { this.rateLimiters = rateLimiters; this.keysDynamoDb = keysDynamoDb; this.accounts = accounts; this.preKeyRateLimiter = preKeyRateLimiter; - - this.dynamicConfigurationManager = dynamicConfigurationManager; - this.rateLimitChallengeManager = rateLimitChallengeManager; + this.rateLimitChallengeManager = rateLimitChallengeManager; } @GET @@ -152,7 +146,7 @@ public class KeysController { } if (account.isPresent()) { - rateLimiters.getPreKeysLimiter().validate(account.get().getNumber() + "." + account.get().getAuthenticatedDevice().get().getId() + "__" + target.get().getNumber() + "." + deviceId); + rateLimiters.getPreKeysLimiter().validate(account.get().getUuid() + "." + account.get().getAuthenticatedDevice().get().getId() + "__" + target.get().getUuid() + "." + deviceId); try { preKeyRateLimiter.validate(account.get()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index a03d81cfa..4cffc8b53 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -257,7 +257,7 @@ public class MessageController { assert(destination.isPresent()); if (source.isPresent() && !source.get().isFor(destinationName)) { - rateLimiters.getMessagesLimiter().validate(source.get().getNumber() + "__" + destination.get().getUuid()); + rateLimiters.getMessagesLimiter().validate(source.get().getUuid(), destination.get().getUuid()); final String senderCountryCode = Util.getCountryCode(source.get().getNumber()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index caa3d0245..49ee627ce 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -210,7 +210,7 @@ public class ProfileController { } if (requestAccount.isPresent()) { - rateLimiters.getProfileLimiter().validate(requestAccount.get().getNumber()); + rateLimiters.getProfileLimiter().validate(requestAccount.get().getUuid()); } Optional accountProfile = accountsManager.get(uuid); @@ -260,7 +260,7 @@ public class ProfileController { @Produces(MediaType.APPLICATION_JSON) @Path("/username/{username}") public Profile getProfileByUsername(@Auth Account account, @PathParam("username") String username) throws RateLimitExceededException { - rateLimiters.getUsernameLookupLimiter().validate(account.getUuid().toString()); + rateLimiters.getUsernameLookupLimiter().validate(account.getUuid()); username = username.toLowerCase(); @@ -341,7 +341,7 @@ public class ProfileController { } if (requestAccount.isPresent()) { - rateLimiters.getProfileLimiter().validate(requestAccount.get().getNumber()); + rateLimiters.getProfileLimiter().validate(requestAccount.get().getUuid()); } Optional accountProfile = accountsManager.get(identifier); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProvisioningController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProvisioningController.java index dbc1de953..a8d4ee177 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProvisioningController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProvisioningController.java @@ -6,13 +6,8 @@ package org.whispersystems.textsecuregcm.controllers; import com.codahale.metrics.annotation.Timed; -import org.whispersystems.textsecuregcm.entities.ProvisioningMessage; -import org.whispersystems.textsecuregcm.limits.RateLimiters; -import org.whispersystems.textsecuregcm.push.ProvisioningManager; -import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.websocket.InvalidWebsocketAddressException; -import org.whispersystems.textsecuregcm.websocket.ProvisioningAddress; - +import io.dropwizard.auth.Auth; +import java.util.Base64; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.PUT; @@ -22,10 +17,11 @@ import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.io.IOException; -import java.util.Base64; - -import io.dropwizard.auth.Auth; +import org.whispersystems.textsecuregcm.entities.ProvisioningMessage; +import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.textsecuregcm.push.ProvisioningManager; +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.websocket.ProvisioningAddress; @Path("/v1/provisioning") public class ProvisioningController { @@ -46,9 +42,9 @@ public class ProvisioningController { public void sendProvisioningMessage(@Auth Account source, @PathParam("destination") String destinationName, @Valid ProvisioningMessage message) - throws RateLimitExceededException, InvalidWebsocketAddressException, IOException - { - rateLimiters.getMessagesLimiter().validate(source.getNumber()); + throws RateLimitExceededException { + + rateLimiters.getMessagesLimiter().validate(source.getUuid()); if (!provisioningManager.sendProvisioningMessage(new ProvisioningAddress(destinationName, 0), Base64.getDecoder().decode(message.getBody()))) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/StickerController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/StickerController.java index 7ba7e3ae8..5d626af5e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/StickerController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/StickerController.java @@ -49,7 +49,7 @@ public class StickerController { @PathParam("count") @Min(1) @Max(201) int stickerCount) throws RateLimitExceededException { - rateLimiters.getStickerPackLimiter().validate(account.getNumber()); + rateLimiters.getStickerPackLimiter().validate(account.getUuid()); ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); String packId = generatePackId(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/limits/PreKeyRateLimiter.java b/service/src/main/java/org/whispersystems/textsecuregcm/limits/PreKeyRateLimiter.java index ce56db9a4..9486561dc 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/limits/PreKeyRateLimiter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/limits/PreKeyRateLimiter.java @@ -49,7 +49,7 @@ public class PreKeyRateLimiter { public void validate(final Account account) throws RateLimitExceededException { try { - rateLimiters.getDailyPreKeysLimiter().validate(account.getNumber()); + rateLimiters.getDailyPreKeysLimiter().validate(account.getUuid()); } catch (final RateLimitExceededException e) { final boolean enforceLimit = dynamicConfigurationManager.getConfiguration() @@ -70,7 +70,7 @@ public class PreKeyRateLimiter { public void handleRateLimitReset(final Account account) { - rateLimiters.getDailyPreKeysLimiter().clear(account.getNumber()); + rateLimiters.getDailyPreKeysLimiter().clear(account.getUuid()); Metrics.counter(RATE_LIMIT_RESET_COUNTER_NAME, "countryCode", Util.getCountryCode(account.getNumber())) .increment(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimitChallengeManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimitChallengeManager.java index e2adac3d2..d756158bc 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimitChallengeManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimitChallengeManager.java @@ -54,12 +54,12 @@ public class RateLimitChallengeManager { } public void answerPushChallenge(final Account account, final String challenge) throws RateLimitExceededException { - rateLimiters.getPushChallengeAttemptLimiter().validate(account.getNumber()); + rateLimiters.getPushChallengeAttemptLimiter().validate(account.getUuid()); final boolean challengeSuccess = pushChallengeManager.answerChallenge(account, challenge); if (challengeSuccess) { - rateLimiters.getPushChallengeSuccessLimiter().validate(account.getNumber()); + rateLimiters.getPushChallengeSuccessLimiter().validate(account.getUuid()); resetRateLimits(account); } } @@ -67,7 +67,7 @@ public class RateLimitChallengeManager { public void answerRecaptchaChallenge(final Account account, final String captcha, final String mostRecentProxyIp) throws RateLimitExceededException { - rateLimiters.getRecaptchaChallengeAttemptLimiter().validate(account.getNumber()); + rateLimiters.getRecaptchaChallengeAttemptLimiter().validate(account.getUuid()); final boolean challengeSuccess = recaptchaClient.verify(captcha, mostRecentProxyIp); @@ -76,14 +76,14 @@ public class RateLimitChallengeManager { SUCCESS_TAG_NAME, String.valueOf(challengeSuccess)).increment(); if (challengeSuccess) { - rateLimiters.getRecaptchaChallengeSuccessLimiter().validate(account.getNumber()); + rateLimiters.getRecaptchaChallengeSuccessLimiter().validate(account.getUuid()); resetRateLimits(account); } } private void resetRateLimits(final Account account) throws RateLimitExceededException { try { - rateLimiters.getRateLimitResetLimiter().validate(account.getNumber()); + rateLimiters.getRateLimitResetLimiter().validate(account.getUuid()); } catch (final RateLimitExceededException e) { Metrics.counter(RESET_RATE_LIMIT_EXCEEDED_COUNTER_NAME, SOURCE_COUNTRY_TAG_NAME, Util.getCountryCode(account.getNumber())).increment(); @@ -112,16 +112,14 @@ public class RateLimitChallengeManager { public List getChallengeOptions(final Account account) { final List options = new ArrayList<>(2); - final String key = account.getNumber(); - - if (rateLimiters.getRecaptchaChallengeAttemptLimiter().hasAvailablePermits(key, 1) && - rateLimiters.getRecaptchaChallengeSuccessLimiter().hasAvailablePermits(key, 1)) { + if (rateLimiters.getRecaptchaChallengeAttemptLimiter().hasAvailablePermits(account.getUuid(), 1) && + rateLimiters.getRecaptchaChallengeSuccessLimiter().hasAvailablePermits(account.getUuid(), 1)) { options.add(OPTION_RECAPTCHA); } - if (rateLimiters.getPushChallengeAttemptLimiter().hasAvailablePermits(key, 1) && - rateLimiters.getPushChallengeSuccessLimiter().hasAvailablePermits(key, 1)) { + if (rateLimiters.getPushChallengeAttemptLimiter().hasAvailablePermits(account.getUuid(), 1) && + rateLimiters.getPushChallengeSuccessLimiter().hasAvailablePermits(account.getUuid(), 1)) { options.add(OPTION_PUSH_CHALLENGE); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiter.java b/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiter.java index 2c25edddd..463bef678 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiter.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.time.Duration; +import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration.RateLimitConfiguration; @@ -61,14 +62,32 @@ public class RateLimiter { } } + public void validate(final UUID accountUuid) throws RateLimitExceededException { + validate(accountUuid.toString()); + } + + public void validate(final UUID sourceAccountUuid, final UUID destinationAccountUuid) + throws RateLimitExceededException { + + validate(sourceAccountUuid.toString() + "__" + destinationAccountUuid.toString()); + } + public void validate(String key) throws RateLimitExceededException { validate(key, 1); } + public boolean hasAvailablePermits(final UUID accountUuid, final int permits) { + return hasAvailablePermits(accountUuid.toString(), permits); + } + public boolean hasAvailablePermits(final String key, final int permits) { return getBucket(key).getTimeUntilSpaceAvailable(permits).equals(Duration.ZERO); } + public void clear(final UUID accountUuid) { + clear(accountUuid.toString()); + } + public void clear(String key) { cacheCluster.useCluster(connection -> connection.sync().del(getBucketName(key))); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/limits/UnsealedSenderRateLimiter.java b/service/src/main/java/org/whispersystems/textsecuregcm/limits/UnsealedSenderRateLimiter.java index 238b6ccc9..cde5b4a99 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/limits/UnsealedSenderRateLimiter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/limits/UnsealedSenderRateLimiter.java @@ -65,7 +65,7 @@ public class UnsealedSenderRateLimiter { try { rateLimiters.getUnsealedSenderCardinalityLimiter() - .validate(sender.getNumber(), destination.getUuid().toString(), maxCardinality); + .validate(sender.getUuid().toString(), destination.getUuid().toString(), maxCardinality); } catch (final RateLimitExceededException e) { final boolean enforceLimit = dynamicConfigurationManager.getConfiguration() @@ -91,7 +91,7 @@ public class UnsealedSenderRateLimiter { final long ttl; { - final long remainingTtl = unsealedSenderCardinalityLimiter.getRemainingTtl(account.getNumber()); + final long remainingTtl = unsealedSenderCardinalityLimiter.getRemainingTtl(account.getUuid().toString()); ttl = remainingTtl > 0 ? remainingTtl : unsealedSenderCardinalityLimiter.getInitialTtl().toSeconds(); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/limits/PreKeyRateLimiterTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/limits/PreKeyRateLimiterTest.java index 3af2fb5fe..8790b894b 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/limits/PreKeyRateLimiterTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/limits/PreKeyRateLimiterTest.java @@ -49,7 +49,7 @@ class PreKeyRateLimiterTest { void enforcementConfiguration() throws RateLimitExceededException { doThrow(RateLimitExceededException.class) - .when(dailyPreKeyLimiter).validate(any()); + .when(dailyPreKeyLimiter).validate(any(UUID.class)); when(rateLimitChallengeConfiguration.isPreKeyLimitEnforced()).thenReturn(false); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitChallengeManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitChallengeManagerTest.java index 7202aa780..77d109eef 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitChallengeManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitChallengeManagerTest.java @@ -7,12 +7,13 @@ 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.verifyZeroInteractions; +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; @@ -66,6 +67,8 @@ class RateLimitChallengeManagerTest { @ValueSource(booleans = {true, false}) void answerPushChallenge(final boolean successfulChallenge) throws RateLimitExceededException { final Account account = mock(Account.class); + when(account.getUuid()).thenReturn(UUID.randomUUID()); + when(pushChallengeManager.answerChallenge(eq(account), any())).thenReturn(successfulChallenge); when(rateLimiters.getPushChallengeAttemptLimiter()).thenReturn(mock(RateLimiter.class)); @@ -78,8 +81,8 @@ class RateLimitChallengeManagerTest { verify(preKeyRateLimiter).handleRateLimitReset(account); verify(unsealedSenderRateLimiter).handleRateLimitReset(account); } else { - verifyZeroInteractions(preKeyRateLimiter); - verifyZeroInteractions(unsealedSenderRateLimiter); + verifyNoInteractions(preKeyRateLimiter); + verifyNoInteractions(unsealedSenderRateLimiter); } } @@ -88,6 +91,7 @@ class RateLimitChallengeManagerTest { void answerRecaptchaChallenge(final boolean successfulChallenge) throws RateLimitExceededException { final Account account = mock(Account.class); when(account.getNumber()).thenReturn("+18005551234"); + when(account.getUuid()).thenReturn(UUID.randomUUID()); when(recaptchaClient.verify(any(), any())).thenReturn(successfulChallenge); @@ -101,8 +105,8 @@ class RateLimitChallengeManagerTest { verify(preKeyRateLimiter).handleRateLimitReset(account); verify(unsealedSenderRateLimiter).handleRateLimitReset(account); } else { - verifyZeroInteractions(preKeyRateLimiter); - verifyZeroInteractions(unsealedSenderRateLimiter); + verifyNoInteractions(preKeyRateLimiter); + verifyNoInteractions(unsealedSenderRateLimiter); } } @@ -150,14 +154,17 @@ class RateLimitChallengeManagerTest { when(rateLimiters.getPushChallengeAttemptLimiter()).thenReturn(pushChallengeAttemptLimiter); when(rateLimiters.getPushChallengeSuccessLimiter()).thenReturn(pushChallengeSuccessLimiter); - when(recaptchaChallengeAttemptLimiter.hasAvailablePermits(any(), anyInt())).thenReturn(captchaAttemptPermitted); - when(recaptchaChallengeSuccessLimiter.hasAvailablePermits(any(), anyInt())).thenReturn(captchaSuccessPermitted); - when(pushChallengeAttemptLimiter.hasAvailablePermits(any(), anyInt())).thenReturn(pushAttemptPermitted); - when(pushChallengeSuccessLimiter.hasAvailablePermits(any(), anyInt())).thenReturn(pushSuccessPermitted); + 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 List options = rateLimitChallengeManager.getChallengeOptions(mock(Account.class)); + final Account account = mock(Account.class); + when(account.getUuid()).thenReturn(UUID.randomUUID()); + + final List options = rateLimitChallengeManager.getChallengeOptions(account); assertEquals(expectedLength, options.size()); if (expectCaptcha) { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java index 1eca5981c..87d644ea6 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java @@ -60,7 +60,6 @@ import org.whispersystems.textsecuregcm.mappers.RateLimitChallengeExceptionMappe import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.KeysDynamoDb; import org.whispersystems.textsecuregcm.tests.util.AccountsHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; @@ -95,8 +94,6 @@ class KeysControllerTest { private final static RateLimitChallengeManager rateLimitChallengeManager = mock(RateLimitChallengeManager.class ); private final static Account existsAccount = mock(Account.class ); - private final static DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class); - private static final RateLimiters rateLimiters = mock(RateLimiters.class); private static final RateLimiter rateLimiter = mock(RateLimiter.class ); @@ -105,7 +102,7 @@ class KeysControllerTest { .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new RateLimitChallengeExceptionMapper(rateLimitChallengeManager)) - .addResource(new KeysController(rateLimiters, keysDynamoDb, accounts, preKeyRateLimiter, dynamicConfigurationManager, rateLimitChallengeManager)) + .addResource(new KeysController(rateLimiters, keysDynamoDb, accounts, preKeyRateLimiter, rateLimitChallengeManager)) .build(); @BeforeEach @@ -186,7 +183,6 @@ class KeysControllerTest { existsAccount, rateLimiters, rateLimiter, - dynamicConfigurationManager, rateLimitChallengeManager ); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java index 26703c115..a92ac3d1a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java @@ -185,7 +185,7 @@ class ProfileControllerTest { verify(accountsManager, times(1)).get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasUuid() && identifier.getUuid().equals(AuthHelper.VALID_UUID_TWO))); verify(usernamesManager, times(1)).get(eq(AuthHelper.VALID_UUID_TWO)); - verify(rateLimiter, times(1)).validate(eq(AuthHelper.VALID_NUMBER)); + verify(rateLimiter, times(1)).validate(AuthHelper.VALID_UUID); } @Test @@ -206,7 +206,7 @@ class ProfileControllerTest { verify(accountsManager, times(1)).get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(AuthHelper.VALID_NUMBER_TWO))); verifyNoMoreInteractions(usernamesManager); - verify(rateLimiter, times(1)).validate(eq(AuthHelper.VALID_NUMBER)); + verify(rateLimiter, times(1)).validate(AuthHelper.VALID_UUID); } @Test @@ -225,7 +225,7 @@ class ProfileControllerTest { verify(accountsManager, times(1)).get(eq(AuthHelper.VALID_UUID_TWO)); verify(usernamesManager, times(1)).get(eq("n00bkiller")); - verify(usernameRateLimiter, times(1)).validate(eq(AuthHelper.VALID_UUID.toString())); + verify(usernameRateLimiter, times(1)).validate(eq(AuthHelper.VALID_UUID)); } @Test @@ -260,7 +260,7 @@ class ProfileControllerTest { assertThat(response.getStatus()).isEqualTo(404); verify(usernamesManager, times(1)).get(eq("n00bkillerzzzzz")); - verify(usernameRateLimiter, times(1)).validate(eq(AuthHelper.VALID_UUID.toString())); + verify(usernameRateLimiter, times(1)).validate(eq(AuthHelper.VALID_UUID)); } @@ -587,7 +587,7 @@ class ProfileControllerTest { verify(usernamesManager, times(1)).get(eq(AuthHelper.VALID_UUID_TWO)); verify(profilesManager, times(1)).get(eq(AuthHelper.VALID_UUID_TWO), eq("validversion")); - verify(rateLimiter, times(1)).validate(eq(AuthHelper.VALID_NUMBER)); + verify(rateLimiter, times(1)).validate(AuthHelper.VALID_UUID); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/StickerControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/StickerControllerTest.java index 61c53da50..132d6f9fc 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/StickerControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/StickerControllerTest.java @@ -6,7 +6,6 @@ package org.whispersystems.textsecuregcm.tests.controllers; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -35,8 +34,8 @@ import org.whispersystems.textsecuregcm.util.SystemMapper; @ExtendWith(DropwizardExtensionsSupport.class) class StickerControllerTest { - private static RateLimiter rateLimiter = mock(RateLimiter.class ); - private static RateLimiters rateLimiters = mock(RateLimiters.class); + private static final RateLimiter rateLimiter = mock(RateLimiter.class ); + private static final RateLimiters rateLimiters = mock(RateLimiters.class); private static final ResourceExtension resources = ResourceExtension.builder() .addProvider(AuthHelper.getAuthFilter()) @@ -86,7 +85,7 @@ class StickerControllerTest { } verify(rateLimiters, times(1)).getStickerPackLimiter(); - verify(rateLimiter, times(1)).validate(eq(AuthHelper.VALID_NUMBER)); + verify(rateLimiter, times(1)).validate(AuthHelper.VALID_UUID); } @Test