diff --git a/.travis.yml b/.travis.yml index 2a91c913..0ac889fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ env: matrix: - PROFILE=non-existant - PROFILE=spring5-next + - PROFILE=spring51-next addons: apt: packages: diff --git a/pom.xml b/pom.xml index a5878402..f2081ac2 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,20 @@ + + spring51-next + + 5.1.0.BUILD-SNAPSHOT + 2.9.2 + + + + spring-libs-snapshot + http://repo.spring.io/libs-snapshot + + + + diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java index 23796a69..c02c3aca 100755 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java @@ -15,7 +15,7 @@ */ package org.springframework.hateoas.mvc; -import static org.springframework.util.StringUtils.*; +import static org.springframework.hateoas.mvc.ForwardedHeader.handleXForwardedSslHeader; import lombok.RequiredArgsConstructor; import lombok.experimental.Delegate; @@ -28,6 +28,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; +import org.springframework.context.ApplicationContext; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.hateoas.Affordance; import org.springframework.hateoas.Link; @@ -306,27 +307,37 @@ public class ControllerLinkBuilder extends LinkBuilderSupport= 5 && minorVersion >= 1) || (majorVersion > 5); } /** @@ -361,13 +372,13 @@ public class ControllerLinkBuilder extends LinkBuilderSupport templates = new ConcurrentReferenceHashMap<>(); + private final Map templates = new ConcurrentReferenceHashMap<>(); public UriTemplate getMappingAsUriTemplate(Class type, Method method) { String mapping = delegate.getMapping(type, method); - - return templates.computeIfAbsent(mapping, UriTemplate::new); + + return templates.computeIfAbsent(mapping, UriTemplate::new); } } diff --git a/src/main/java/org/springframework/hateoas/mvc/ForwardedHeader.java b/src/main/java/org/springframework/hateoas/mvc/ForwardedHeader.java index 3301f518..f1f3b957 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ForwardedHeader.java +++ b/src/main/java/org/springframework/hateoas/mvc/ForwardedHeader.java @@ -15,38 +15,70 @@ */ package org.springframework.hateoas.mvc; +import static org.springframework.util.StringUtils.hasText; + import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; + import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponentsBuilder; /** * Value object to partially implement the {@literal Forwarded} header defined in RFC 7239. * * @author Oliver Gierke * @see http://tools.ietf.org/html/rfc7239 + * @deprecated In Spring 5.1, all Forwarded headers will by handled by Spring MVC. */ +@Deprecated class ForwardedHeader { - public static String NAME = "Forwarded"; private static final ForwardedHeader NO_HEADER = new ForwardedHeader(Collections.emptyMap()); - private final Map elements; private ForwardedHeader(Map elements) { this.elements = elements; } + /** + * Utility method to pull handling of {@literal X-Forwarded-Ssl} into a class that will be removed when + * rebaselined against Spring 5.1 + * + * @param request + * @param builder + * @return + * @deprecated No longer needed with Spring 5.1 + */ + @Deprecated + public static UriComponentsBuilder handleXForwardedSslHeader(HttpServletRequest request, UriComponentsBuilder builder) { + + // special case handling for X-Forwarded-Ssl: + // apply it, but only if X-Forwarded-Proto is unset. + + String forwardedSsl = request.getHeader("X-Forwarded-Ssl"); + ForwardedHeader forwarded = ForwardedHeader.of(request.getHeader("Forwarded")); + String proto = hasText(forwarded.getProto()) ? forwarded.getProto() : request.getHeader("X-Forwarded-Proto"); + + if (!hasText(proto) && hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on")) { + builder.scheme("https"); + } + + return builder; + + } + /** * Creates a new {@link ForwardedHeader} from the given source. * * @param source can be {@literal null}. * @return */ - public static ForwardedHeader of(String source) { + static ForwardedHeader of(String source) { if (!StringUtils.hasText(source)) { return NO_HEADER; @@ -67,7 +99,7 @@ class ForwardedHeader { * * @return */ - public String getProto() { + String getProto() { return elements.get("proto"); } @@ -76,7 +108,7 @@ class ForwardedHeader { * * @return */ - public String getHost() { + String getHost() { return elements.get("host"); } } diff --git a/src/test/java/org/springframework/hateoas/TestUtils.java b/src/test/java/org/springframework/hateoas/TestUtils.java index 0315aa52..c7a21c13 100644 --- a/src/test/java/org/springframework/hateoas/TestUtils.java +++ b/src/test/java/org/springframework/hateoas/TestUtils.java @@ -17,10 +17,18 @@ package org.springframework.hateoas; import static org.assertj.core.api.Assertions.*; +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + import org.junit.Before; +import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.filter.ForwardedHeaderFilter; /** * Utility class to ease testing. @@ -43,6 +51,24 @@ public class TestUtils { assertThat(link.getHref()).startsWith("http://localhost"); } + /** + * Provide a mechanism to simulate inserting a {@link ForwardedHeaderFilter} into the servlet + * filter chain, so {@literal Forwarded} headers are properly inserted into the test web request. + * + * @see https://jira.spring.io/browse/SPR-16668 + */ + protected void adaptRequestFromForwardedHeaders() { + + MockFilterChain chain = new MockFilterChain(); + try { + new ForwardedHeaderFilter().doFilter(this.request, new MockHttpServletResponse(), chain); + } catch (ServletException | IOException e) { + throw new RuntimeException(e); + } + HttpServletRequest adaptedRequest = (HttpServletRequest) chain.getRequest(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(adaptedRequest)); + } + public static void assertEqualAndSameHashCode(Object left, Object right) { assertThat(left).isEqualTo(right); diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java index 25e31619..fcf75f15 100755 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java @@ -20,11 +20,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; +import java.io.IOException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Optional; +import javax.servlet.ServletException; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -162,6 +165,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { request.addHeader("X-Forwarded-Host", "somethingDifferent"); + adaptRequestFromForwardedHeaders(); + Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith("http://somethingDifferent")); } @@ -174,6 +179,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { request.addHeader("X-Forwarded-Ssl", "on"); + adaptRequestFromForwardedHeaders(); + Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith("https://")); } @@ -186,6 +193,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { request.addHeader("X-Forwarded-Ssl", "off"); + adaptRequestFromForwardedHeaders(); + Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith("http://")); } @@ -199,6 +208,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { request.addHeader("X-Forwarded-Host", "somethingDifferent"); request.addHeader("X-Forwarded-Ssl", "on"); + adaptRequestFromForwardedHeaders(); + Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith("https://somethingDifferent")); } @@ -270,6 +281,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { request.addHeader("X-Forwarded-Host", "foobar:8088"); + adaptRequestFromForwardedHeaders(); + Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith("http://foobar:8088")); } @@ -282,6 +295,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { request.addHeader("X-Forwarded-Host", "barfoo:8888, localhost:8088"); + adaptRequestFromForwardedHeaders(); + Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith("http://barfoo:8888")); } @@ -335,6 +350,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { request.addHeader("X-Forwarded-Port", "9090"); request.setServerPort(8080); + adaptRequestFromForwardedHeaders(); + Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith("http://foobarhost:9090/")); @@ -349,6 +366,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { request.addHeader("X-Forwarded-Host", "foobarhost"); request.setServerPort(8080); + adaptRequestFromForwardedHeaders(); + Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith("http://foobarhost/")); } @@ -410,12 +429,14 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { * @see #257, #107 */ @Test - public void usesXForwardedProtoHeaderAsLinkSchema() { + public void usesXForwardedProtoHeaderAsLinkSchema() throws ServletException, IOException { for (String proto : Arrays.asList("http", "https")) { setUp(); request.addHeader("X-Forwarded-Proto", proto); + + adaptRequestFromForwardedHeaders(); Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith(proto + "://")); @@ -433,6 +454,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { setUp(); request.addHeader("Forwarded", new String[] { "proto=" + proto }); + adaptRequestFromForwardedHeaders(); + Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith(proto.concat("://"))); } @@ -445,7 +468,9 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { public void favorsStandardForwardHeaderOverXForwardedProto() { request.addHeader("X-Forwarded-Proto", "foo"); - request.addHeader(ForwardedHeader.NAME, "proto=bar"); + request.addHeader("Forwarded", "proto=bar"); + + adaptRequestFromForwardedHeaders(); Link link = linkTo(PersonControllerImpl.class).withSelfRel(); assertThat(link.getHref(), startsWith("bar://")); @@ -527,6 +552,8 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { request.addHeader("X-Forwarded-Port", "1443,8443"); request.addHeader("X-Forwarded-Host", "proxy1,proxy2"); + adaptRequestFromForwardedHeaders(); + assertThat(linkTo(PersonControllerImpl.class).withSelfRel().getHref(), startsWith("http://proxy1:1443")); }