diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 64a011fb4..5a03c7518 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -39,6 +39,7 @@ import java.time.Duration; import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Optional; import java.util.ServiceLoader; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; @@ -300,6 +301,10 @@ public class WhisperServerService extends Application new BadRequestException()); + final String remoteAddress = useRemoteAddress + ? request.getRemoteAddr() + : HeaderUtils.getMostRecentProxy(forwardedFor).orElseThrow(BadRequestException::new); boolean success = rateLimitChallengeManager.answerRecaptchaChallenge( auth.getAccount(), recaptchaChallengeRequest.getCaptcha(), - mostRecentProxy, + remoteAddress, userAgent, captchaScoreThreshold.getScoreThreshold()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java index 8b1771695..f81980afb 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java @@ -31,6 +31,7 @@ import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletionException; import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.ws.rs.BadRequestException; @@ -48,6 +49,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.ServerErrorException; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -116,6 +118,7 @@ public class VerificationController { private final RateLimiters rateLimiters; private final AccountsManager accountsManager; + private final boolean useRemoteAddress; private final Clock clock; public VerificationController(final RegistrationServiceClient registrationServiceClient, @@ -125,6 +128,7 @@ public class VerificationController { final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager, final RateLimiters rateLimiters, final AccountsManager accountsManager, + final boolean useRemoteAddress, final Clock clock) { this.registrationServiceClient = registrationServiceClient; this.verificationSessionManager = verificationSessionManager; @@ -133,6 +137,7 @@ public class VerificationController { this.registrationRecoveryPasswordsManager = registrationRecoveryPasswordsManager; this.rateLimiters = rateLimiters; this.accountsManager = accountsManager; + this.useRemoteAddress = useRemoteAddress; this.clock = clock; } @@ -194,10 +199,13 @@ public class VerificationController { public VerificationSessionResponse updateSession(@PathParam("sessionId") final String encodedSessionId, @HeaderParam(com.google.common.net.HttpHeaders.X_FORWARDED_FOR) String forwardedFor, @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent, + @Context HttpServletRequest request, @NotNull @Valid final UpdateVerificationSessionRequest updateVerificationSessionRequest, @NotNull @Extract final ScoreThreshold captchaScoreThreshold) { - final String sourceHost = HeaderUtils.getMostRecentProxy(forwardedFor).orElseThrow(); + final String sourceHost = useRemoteAddress + ? request.getRemoteAddr() + : HeaderUtils.getMostRecentProxy(forwardedFor).orElseThrow(); final Pair pushTokenAndType = validateAndExtractPushToken( updateVerificationSessionRequest); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimitByIpFilter.java b/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimitByIpFilter.java index 17c6e3957..3ef1093af 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimitByIpFilter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimitByIpFilter.java @@ -12,9 +12,12 @@ import com.google.common.net.HttpHeaders; import java.io.IOException; import java.time.Duration; import java.util.Optional; +import javax.inject.Provider; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.ClientErrorException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import org.glassfish.jersey.server.ExtendedUriInfo; @@ -28,6 +31,9 @@ public class RateLimitByIpFilter implements ContainerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(RateLimitByIpFilter.class); + @Context + private Provider httpServletRequestProvider; + @VisibleForTesting static final RateLimitExceededException INVALID_HEADER_EXCEPTION = new RateLimitExceededException(Duration.ofHours(1), true); @@ -35,10 +41,12 @@ public class RateLimitByIpFilter implements ContainerRequestFilter { private static final ExceptionMapper EXCEPTION_MAPPER = new RateLimitExceededExceptionMapper(); private final RateLimiters rateLimiters; + private final boolean useRemoteAddress; - public RateLimitByIpFilter(final RateLimiters rateLimiters) { + public RateLimitByIpFilter(final RateLimiters rateLimiters, final boolean useRemoteAddress) { this.rateLimiters = requireNonNull(rateLimiters); + this.useRemoteAddress = useRemoteAddress; } @Override @@ -62,12 +70,14 @@ public class RateLimitByIpFilter implements ContainerRequestFilter { try { final String xffHeader = requestContext.getHeaders().getFirst(HttpHeaders.X_FORWARDED_FOR); - final Optional maybeMostRecentProxy = Optional.ofNullable(xffHeader) - .flatMap(HeaderUtils::getMostRecentProxy); + final Optional remoteAddress = useRemoteAddress + ? Optional.of(httpServletRequestProvider.get().getRemoteAddr()) + : Optional.ofNullable(xffHeader) + .flatMap(HeaderUtils::getMostRecentProxy); // checking if we failed to extract the most recent IP from the X-Forwarded-For header // for any reason - if (maybeMostRecentProxy.isEmpty()) { + if (remoteAddress.isEmpty()) { // checking if annotation is configured to fail when the most recent IP is not resolved if (annotation.failOnUnresolvedIp()) { logger.error("Missing/bad X-Forwarded-For: {}", xffHeader); @@ -78,7 +88,7 @@ public class RateLimitByIpFilter implements ContainerRequestFilter { } final RateLimiter rateLimiter = rateLimiters.forDescriptor(handle); - rateLimiter.validate(maybeMostRecentProxy.get()); + rateLimiter.validate(remoteAddress.get()); } catch (RateLimitExceededException e) { final Response response = EXCEPTION_MAPPER.toResponse(e); throw new ClientErrorException(response); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java index af09851ae..f398b5123 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java @@ -38,7 +38,6 @@ import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; @@ -153,7 +152,7 @@ class AccountControllerTest { .addProvider(new RateLimitExceededExceptionMapper()) .addProvider(new ImpossiblePhoneNumberExceptionMapper()) .addProvider(new NonNormalizedPhoneNumberExceptionMapper()) - .addProvider(new RateLimitByIpFilter(rateLimiters)) + .addProvider(new RateLimitByIpFilter(rateLimiters, true)) .addProvider(ScoreThresholdProvider.ScoreThresholdFeature.class) .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java index 924aa1e30..ca5532281 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java @@ -53,7 +53,8 @@ class ChallengeControllerTest { private static final RateLimitChallengeManager rateLimitChallengeManager = mock(RateLimitChallengeManager.class); - private static final ChallengeController challengeController = new ChallengeController(rateLimitChallengeManager); + private static final ChallengeController challengeController = new ChallengeController(rateLimitChallengeManager, + true); private static final AtomicReference scoreThreshold = new AtomicReference<>(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java index c5384d8e3..e37623283 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java @@ -109,7 +109,8 @@ class VerificationControllerTest { .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource( new VerificationController(registrationServiceClient, verificationSessionManager, pushNotificationManager, - registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters, accountsManager, clock)) + registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters, accountsManager, true, + clock)) .build(); @BeforeEach diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitedByIpTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitedByIpTest.java index 94c3dc767..fe5e2421f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitedByIpTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitedByIpTest.java @@ -64,7 +64,7 @@ public class RateLimitedByIpTest { .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new Controller()) - .addProvider(new RateLimitByIpFilter(RATE_LIMITERS)) + .addProvider(new RateLimitByIpFilter(RATE_LIMITERS, true)) .build(); @Test