Add HttpServletRequestUtil

This commit is contained in:
Chris Eager 2024-01-31 14:05:22 -06:00 committed by Chris Eager
parent fb39af67e5
commit c838df90ef
6 changed files with 144 additions and 3 deletions

View File

@ -43,6 +43,7 @@ import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.PushChallengeConfig;
import org.whispersystems.textsecuregcm.spam.ScoreThreshold;
import org.whispersystems.textsecuregcm.util.HeaderUtils;
import org.whispersystems.textsecuregcm.util.HttpServletRequestUtil;
@Path("/v1/challenge")
@Tag(name = "Challenge")
@ -103,7 +104,7 @@ public class ChallengeController {
tags = tags.and(CHALLENGE_TYPE_TAG, "recaptcha");
final String remoteAddress = useRemoteAddress
? request.getRemoteAddr()
? HttpServletRequestUtil.getRemoteAddress(request)
: HeaderUtils.getMostRecentProxy(forwardedFor).orElseThrow(BadRequestException::new);
boolean success = rateLimitChallengeManager.answerRecaptchaChallenge(
auth.getAccount(),

View File

@ -89,6 +89,7 @@ import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsMan
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import org.whispersystems.textsecuregcm.util.HeaderUtils;
import org.whispersystems.textsecuregcm.util.HttpServletRequestUtil;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.Util;
@ -212,7 +213,7 @@ public class VerificationController {
@NotNull @Extract final SenderOverride senderOverride) {
final String sourceHost = useRemoteAddress
? request.getRemoteAddr()
? HttpServletRequestUtil.getRemoteAddress(request)
: HeaderUtils.getMostRecentProxy(forwardedFor).orElseThrow();
final Pair<String, PushNotification.TokenType> pushTokenAndType = validateAndExtractPushToken(

View File

@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.util.HeaderUtils;
import org.whispersystems.textsecuregcm.util.HttpServletRequestUtil;
public class RateLimitByIpFilter implements ContainerRequestFilter {
@ -71,7 +72,7 @@ public class RateLimitByIpFilter implements ContainerRequestFilter {
try {
final String xffHeader = requestContext.getHeaders().getFirst(HttpHeaders.X_FORWARDED_FOR);
final Optional<String> remoteAddress = useRemoteAddress
? Optional.of(httpServletRequestProvider.get().getRemoteAddr())
? Optional.of(HttpServletRequestUtil.getRemoteAddress(httpServletRequestProvider.get()))
: Optional.ofNullable(xffHeader)
.flatMap(HeaderUtils::getMostRecentProxy);

View File

@ -0,0 +1,25 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.util;
import javax.servlet.http.HttpServletRequest;
public class HttpServletRequestUtil {
/**
* Returns the remote address of the request, removing bracket ("[…]") host notation from IPv6 addresses present in
* some implementations, notably {@link org.eclipse.jetty.server.HttpChannel}.
*/
public static String getRemoteAddress(final HttpServletRequest request) {
final String remoteAddr = request.getRemoteAddr();
if (remoteAddr.startsWith("[")) {
return remoteAddr.substring(1, remoteAddr.length() - 1);
}
return remoteAddr;
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import io.dropwizard.core.Application;
import io.dropwizard.core.Configuration;
import io.dropwizard.core.setup.Environment;
import io.dropwizard.testing.junit5.DropwizardAppExtension;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Context;
import org.eclipse.jetty.util.HostPort;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@ExtendWith(DropwizardExtensionsSupport.class)
class HttpServletRequestUtilIntegrationTest {
private static final String PATH = "/test";
// The Grizzly test container does not match the Jetty container used in real deployments, and JettyTestContainerFactory
// in jersey-test-framework-provider-jetty doesnt easily support @Context HttpServletRequest, so this test runs a
// full Jetty server in a separate process
private final DropwizardAppExtension<TestConfiguration> EXTENSION = new DropwizardAppExtension<>(
TestApplication.class);
@ParameterizedTest
@ValueSource(strings = {"127.0.0.1", "0:0:0:0:0:0:0:1"})
void test(String ip) throws Exception {
final Set<String> addresses = Arrays.stream(InetAddress.getAllByName("localhost"))
.map(InetAddress::getHostAddress)
.collect(Collectors.toSet());
assumeTrue(addresses.contains(ip), String.format("localhost does not resolve to %s", ip));
Client client = EXTENSION.client();
final TestResponse response = client.target(
String.format("http://%s:%d%s", HostPort.normalizeHost(ip), EXTENSION.getLocalPort(), PATH))
.request("application/json")
.get(TestResponse.class);
assertEquals(ip, response.remoteAddress());
}
@Path(PATH)
public static class TestController {
@GET
public TestResponse get(@Context HttpServletRequest request) {
return new TestResponse(HttpServletRequestUtil.getRemoteAddress(request));
}
}
public record TestResponse(String remoteAddress) {
}
public static class TestApplication extends Application<TestConfiguration> {
@Override
public void run(final TestConfiguration configuration, final Environment environment) throws Exception {
environment.jersey().register(new TestController());
}
}
public static class TestConfiguration extends Configuration {}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
class HttpServletRequestUtilTest {
@ParameterizedTest
@CsvSource({
"127.0.0.1, 127.0.0.1",
"[0:0:0:0:0:0:0:1], 0:0:0:0:0:0:0:1"
})
void testGetRemoteAddress(final String remoteAddr, final String expectedRemoteAddr) {
final HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
when(httpServletRequest.getRemoteAddr()).thenReturn(remoteAddr);
assertEquals(expectedRemoteAddr, HttpServletRequestUtil.getRemoteAddress(httpServletRequest));
}
}