Skip to content

Commit 3d02450

Browse files
authored
Validate location header 308 (#1561)
* Create custom redirect strategy * Add tests for custom redirect strategy * Address Gemini PR comments
1 parent f52c682 commit 3d02450

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.adyen.httpclient;
2+
3+
import java.net.URI;
4+
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
5+
import org.apache.hc.core5.http.HttpException;
6+
import org.apache.hc.core5.http.HttpRequest;
7+
import org.apache.hc.core5.http.HttpResponse;
8+
import org.apache.hc.core5.http.protocol.HttpContext;
9+
10+
/**
11+
* Implements a custom redirect strategy when the API returns 308
12+
*/
13+
public class AdyenCustomRedirectStrategy extends DefaultRedirectStrategy {
14+
15+
/**
16+
* Override getLocationURI to validate the location header
17+
* @param request the request
18+
* @param response the response
19+
* @param context the context
20+
* @return location url as URI
21+
* @throws HttpException an error has occurred
22+
*/
23+
@Override
24+
public URI getLocationURI(HttpRequest request, HttpResponse response, HttpContext context)
25+
throws HttpException {
26+
URI uri = super.getLocationURI(request, response, context);
27+
28+
int statusCode = response.getCode();
29+
if (statusCode == 308) {
30+
// validate 308 redirect
31+
if (!isVerifyLocation(uri.toString())) {
32+
throw new HttpException("Redirect location is invalid: " + uri);
33+
}
34+
}
35+
36+
return uri;
37+
}
38+
39+
/**
40+
* True when location header is valid
41+
*
42+
* @param location Value of the location header
43+
* @return true if valid
44+
*/
45+
boolean isVerifyLocation(String location) {
46+
if (location == null) {
47+
return false;
48+
}
49+
try {
50+
URI uri = new URI(location);
51+
String host = uri.getHost();
52+
if (host == null) {
53+
return false;
54+
}
55+
String lowerCaseHost = host.toLowerCase();
56+
return lowerCaseHost.endsWith(".adyen.com") || lowerCaseHost.endsWith(".adyenpayments.com");
57+
} catch (java.net.URISyntaxException e) {
58+
return false;
59+
}
60+
}
61+
}

src/main/java/com/adyen/httpclient/AdyenHttpClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ private CloseableHttpClient createHttpClientWithSocketFactory(
261261
PoolingHttpClientConnectionManagerBuilder.create()
262262
.setSSLSocketFactory(socketFactory)
263263
.build())
264+
.setRedirectStrategy(new AdyenCustomRedirectStrategy())
264265
.build();
265266
}
266267

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.adyen.httpclient;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertTrue;
6+
import static org.junit.Assert.fail;
7+
import static org.mockito.Mockito.when;
8+
9+
import java.net.URI;
10+
import org.apache.hc.core5.http.Header;
11+
import org.apache.hc.core5.http.HttpException;
12+
import org.apache.hc.core5.http.HttpRequest;
13+
import org.apache.hc.core5.http.HttpResponse;
14+
import org.apache.hc.core5.http.protocol.HttpContext;
15+
import org.junit.Test;
16+
import org.junit.runner.RunWith;
17+
import org.mockito.Mock;
18+
import org.mockito.junit.MockitoJUnitRunner;
19+
20+
@RunWith(MockitoJUnitRunner.class)
21+
public class AdyenCustomRedirectStrategyTest {
22+
23+
private final AdyenCustomRedirectStrategy redirectStrategy = new AdyenCustomRedirectStrategy();
24+
25+
@Mock private HttpRequest request;
26+
@Mock private HttpResponse response;
27+
@Mock private HttpContext context;
28+
@Mock private Header locationHeader;
29+
30+
@Test
31+
public void testIsVerifyLocationAdyenDomain() {
32+
assertTrue(redirectStrategy.isVerifyLocation("https://test.adyen.com/redirect"));
33+
}
34+
35+
@Test
36+
public void testIsVerifyLocationAdyenPaymentsDomain() {
37+
assertTrue(redirectStrategy.isVerifyLocation("https://test.adyenpayments.com/redirect"));
38+
}
39+
40+
@Test
41+
public void testIsVerifyLocationMixedCase() {
42+
assertTrue(redirectStrategy.isVerifyLocation("https://TEST.ADYEN.COM/redirect"));
43+
}
44+
45+
@Test
46+
public void testIsVerifyLocationInvalidDomain() {
47+
assertFalse(redirectStrategy.isVerifyLocation("https://example.com/redirect"));
48+
}
49+
50+
@Test
51+
public void testIsVerifyLocationMaliciousDomain() {
52+
assertFalse(redirectStrategy.isVerifyLocation("https://adyen.com.evil.com"));
53+
assertFalse(redirectStrategy.isVerifyLocation("https://evil.com?q=.adyen.com"));
54+
assertFalse(redirectStrategy.isVerifyLocation("https://evil.com/.adyen.com"));
55+
}
56+
57+
@Test
58+
public void testIsVerifyLocationEmpty() {
59+
assertFalse(redirectStrategy.isVerifyLocation(""));
60+
}
61+
62+
@Test
63+
public void testGetLocationURI308AdyenDomain() throws HttpException {
64+
when(response.getCode()).thenReturn(308);
65+
when(response.getFirstHeader("Location")).thenReturn(locationHeader);
66+
when(locationHeader.getValue()).thenReturn("https://api.adyen.com");
67+
68+
URI uri = redirectStrategy.getLocationURI(request, response, context);
69+
assertEquals("https://api.adyen.com/", uri.toString());
70+
}
71+
72+
@Test
73+
public void testGetLocationURI308AdyenPaymentsDomain() throws HttpException {
74+
when(response.getCode()).thenReturn(308);
75+
when(response.getFirstHeader("Location")).thenReturn(locationHeader);
76+
when(locationHeader.getValue()).thenReturn("https://api.adyenpayments.com");
77+
78+
URI uri = redirectStrategy.getLocationURI(request, response, context);
79+
assertEquals("https://api.adyenpayments.com/", uri.toString());
80+
}
81+
82+
@Test
83+
public void testGetLocationURI308InvadidDomainThrowsException() {
84+
when(response.getCode()).thenReturn(308);
85+
when(response.getFirstHeader("Location")).thenReturn(locationHeader);
86+
String location = "https://test.example.com/";
87+
when(locationHeader.getValue()).thenReturn(location);
88+
89+
try {
90+
redirectStrategy.getLocationURI(request, response, context);
91+
fail("Expected HttpException was not thrown");
92+
} catch (HttpException e) {
93+
assertEquals("Redirect location is invalid: " + location, e.getMessage());
94+
}
95+
}
96+
97+
}

0 commit comments

Comments
 (0)