This is an automated email from the ASF dual-hosted git repository. papegaaij pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/wicket.git
commit 5d9d4b598ec070cb863d67124d276e74cd3cdc87 Author: Emond Papegaaij <[email protected]> AuthorDate: Fri Aug 7 12:13:32 2020 +0200 WICKET-6786: code reformatting --- .../http/CsrfPreventionRequestCycleListener.java | 4 +- .../http/DefaultResourceIsolationPolicy.java | 6 +- .../http/FetchMetadataRequestCycleListener.java | 212 +++++----- .../http/OriginBasedResourceIsolationPolicy.java | 446 +++++++++++---------- .../FetchMetadataRequestCycleListenerTest.java | 307 +++++++------- 5 files changed, 497 insertions(+), 478 deletions(-) diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/CsrfPreventionRequestCycleListener.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/CsrfPreventionRequestCycleListener.java index db39cc5..f4d4040 100644 --- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/CsrfPreventionRequestCycleListener.java +++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/CsrfPreventionRequestCycleListener.java @@ -579,8 +579,8 @@ public class CsrfPreventionRequestCycleListener extends OriginBasedResourceIsola { onAborted(request, origin, page); log.info( - "Possible CSRF attack, request URL: {}, Origin: {}, action: aborted with error {} {}", - request.getRequestURL(), origin, errorCode, errorMessage); + "Possible CSRF attack, request URL: {}, Origin: {}, action: aborted with error {} {}", + request.getRequestURL(), origin, errorCode, errorMessage); throw new AbortWithHttpErrorCodeException(errorCode, errorMessage); } diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/DefaultResourceIsolationPolicy.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/DefaultResourceIsolationPolicy.java index de9d155..b1d7cf7 100644 --- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/DefaultResourceIsolationPolicy.java +++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/DefaultResourceIsolationPolicy.java @@ -34,11 +34,11 @@ public class DefaultResourceIsolationPolicy implements ResourceIsolationPolicy { @Override - public boolean isRequestAllowed(HttpServletRequest request, - IRequestablePage targetPage) + public boolean isRequestAllowed(HttpServletRequest request, IRequestablePage targetPage) { // request made by a legacy browser with no support for Fetch Metadata - if (!hasFetchMetadataHeaders(request)) { + if (!hasFetchMetadataHeaders(request)) + { return true; } diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/FetchMetadataRequestCycleListener.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/FetchMetadataRequestCycleListener.java index 1d1f88d..ea9a8ef 100644 --- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/FetchMetadataRequestCycleListener.java +++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/FetchMetadataRequestCycleListener.java @@ -44,117 +44,121 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The Fetch Metadata Request Cycle Listener is Wicket's implementation of Fetch Metadata. - * This adds a layer of protection for modern browsers that prevents Cross-Site Request Forgery - * attacks. + * The Fetch Metadata Request Cycle Listener is Wicket's implementation of Fetch Metadata. This adds + * a layer of protection for modern browsers that prevents Cross-Site Request Forgery attacks. * * This request listener uses the {@link DefaultResourceIsolationPolicy} by default and can be * customized with additional Resource Isolation Policies. * - * This listener can be configured to add exempted URL paths that are intended to be used cross-site. + * This listener can be configured to add exempted URL paths that are intended to be used + * cross-site. * - * Learn more about Fetch Metadata and resource isolation - * at <a href="https://web.dev/fetch-metadata/">https://web.dev/fetch-metadata/</a> + * Learn more about Fetch Metadata and resource isolation at + * <a href="https://web.dev/fetch-metadata/">https://web.dev/fetch-metadata/</a> * * @author Santiago Diaz - [email protected] * @author Ecenaz Jen Ozmen - [email protected] */ -public class FetchMetadataRequestCycleListener implements IRequestCycleListener { - - private static final Logger log = LoggerFactory - .getLogger(FetchMetadataRequestCycleListener.class); - public static final int ERROR_CODE = 403; - public static final String ERROR_MESSAGE = "Forbidden"; - public static final String VARY_HEADER_VALUE = SEC_FETCH_DEST_HEADER + ", " - + SEC_FETCH_SITE_HEADER + ", " + SEC_FETCH_MODE_HEADER; - - private final Set<String> exemptedPaths = new HashSet<>(); - private final List<ResourceIsolationPolicy> resourceIsolationPolicies = new ArrayList<>(); - - public FetchMetadataRequestCycleListener(ResourceIsolationPolicy... additionalPolicies) { - this.resourceIsolationPolicies.addAll( - asList( - new DefaultResourceIsolationPolicy(), - new OriginBasedResourceIsolationPolicy() - ) - ); - - this.resourceIsolationPolicies.addAll(asList(additionalPolicies)); - } - - public void addExemptedPaths(String... exemptions) { - Arrays.stream(exemptions) - .filter(e -> !Strings.isEmpty(e)) - .forEach(exemptedPaths::add); - } - - @Override - public void onBeginRequest(RequestCycle cycle) - { - HttpServletRequest containerRequest = (HttpServletRequest)cycle.getRequest() - .getContainerRequest(); - - log.debug("Processing request to: {}", containerRequest.getPathInfo()); - } - - @Override - public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler) - { - handler = unwrap(handler); - IPageRequestHandler pageRequestHandler = getPageRequestHandler(handler); - if (pageRequestHandler == null) { - return; - } - - IRequestablePage targetedPage = pageRequestHandler.getPage(); - HttpServletRequest containerRequest = (HttpServletRequest)cycle.getRequest() - .getContainerRequest(); - - String pathInfo = containerRequest.getPathInfo(); - if (exemptedPaths.contains(pathInfo)) { - if (log.isDebugEnabled()) { - log.debug("Allowing request to {} because it matches an exempted path", - new Object[]{pathInfo}); - } - return; - } - - for (ResourceIsolationPolicy resourceIsolationPolicy : resourceIsolationPolicies) { - if (!resourceIsolationPolicy.isRequestAllowed(containerRequest, targetedPage)) { - log.debug("Isolation policy {} has rejected a request to {}", - Classes.simpleName(resourceIsolationPolicy.getClass()), pathInfo); - throw new AbortWithHttpErrorCodeException(ERROR_CODE, ERROR_MESSAGE); - } - } - } - - @Override - public void onEndRequest(RequestCycle cycle) - { - // set vary headers to avoid caching responses processed by Fetch Metadata - // caching these responses may return 403 responses to legitimate requests - // or defeat the protection - if (cycle.getResponse() instanceof WebResponse) - { - WebResponse webResponse = (WebResponse)cycle.getResponse(); - if (webResponse.isHeaderSupported()) - { - webResponse.addHeader(VARY_HEADER, VARY_HEADER_VALUE); - } - } - } - - private static IRequestHandler unwrap(IRequestHandler handler) { - while (handler instanceof IRequestHandlerDelegate) { - handler = ((IRequestHandlerDelegate)handler).getDelegateHandler(); - } - return handler; - } - - private IPageRequestHandler getPageRequestHandler(IRequestHandler handler) - { - boolean isPageRequestHandler = handler instanceof IPageRequestHandler && - !(handler instanceof RenderPageRequestHandler); - return isPageRequestHandler ? (IPageRequestHandler) handler : null; - } +public class FetchMetadataRequestCycleListener implements IRequestCycleListener +{ + + private static final Logger log = LoggerFactory + .getLogger(FetchMetadataRequestCycleListener.class); + public static final int ERROR_CODE = 403; + public static final String ERROR_MESSAGE = "Forbidden"; + public static final String VARY_HEADER_VALUE = SEC_FETCH_DEST_HEADER + ", " + + SEC_FETCH_SITE_HEADER + ", " + SEC_FETCH_MODE_HEADER; + + private final Set<String> exemptedPaths = new HashSet<>(); + private final List<ResourceIsolationPolicy> resourceIsolationPolicies = new ArrayList<>(); + + public FetchMetadataRequestCycleListener(ResourceIsolationPolicy... additionalPolicies) + { + this.resourceIsolationPolicies.addAll( + asList(new DefaultResourceIsolationPolicy(), new OriginBasedResourceIsolationPolicy())); + + this.resourceIsolationPolicies.addAll(asList(additionalPolicies)); + } + + public void addExemptedPaths(String... exemptions) + { + Arrays.stream(exemptions).filter(e -> !Strings.isEmpty(e)).forEach(exemptedPaths::add); + } + + @Override + public void onBeginRequest(RequestCycle cycle) + { + HttpServletRequest containerRequest = (HttpServletRequest)cycle.getRequest() + .getContainerRequest(); + + log.debug("Processing request to: {}", containerRequest.getPathInfo()); + } + + @Override + public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler) + { + handler = unwrap(handler); + IPageRequestHandler pageRequestHandler = getPageRequestHandler(handler); + if (pageRequestHandler == null) + { + return; + } + + IRequestablePage targetedPage = pageRequestHandler.getPage(); + HttpServletRequest containerRequest = (HttpServletRequest)cycle.getRequest() + .getContainerRequest(); + + String pathInfo = containerRequest.getPathInfo(); + if (exemptedPaths.contains(pathInfo)) + { + if (log.isDebugEnabled()) + { + log.debug("Allowing request to {} because it matches an exempted path", + new Object[] { pathInfo }); + } + return; + } + + for (ResourceIsolationPolicy resourceIsolationPolicy : resourceIsolationPolicies) + { + if (!resourceIsolationPolicy.isRequestAllowed(containerRequest, targetedPage)) + { + log.debug("Isolation policy {} has rejected a request to {}", + Classes.simpleName(resourceIsolationPolicy.getClass()), pathInfo); + throw new AbortWithHttpErrorCodeException(ERROR_CODE, ERROR_MESSAGE); + } + } + } + + @Override + public void onEndRequest(RequestCycle cycle) + { + // set vary headers to avoid caching responses processed by Fetch Metadata + // caching these responses may return 403 responses to legitimate requests + // or defeat the protection + if (cycle.getResponse() instanceof WebResponse) + { + WebResponse webResponse = (WebResponse)cycle.getResponse(); + if (webResponse.isHeaderSupported()) + { + webResponse.addHeader(VARY_HEADER, VARY_HEADER_VALUE); + } + } + } + + private static IRequestHandler unwrap(IRequestHandler handler) + { + while (handler instanceof IRequestHandlerDelegate) + { + handler = ((IRequestHandlerDelegate)handler).getDelegateHandler(); + } + return handler; + } + + private IPageRequestHandler getPageRequestHandler(IRequestHandler handler) + { + boolean isPageRequestHandler = handler instanceof IPageRequestHandler + && !(handler instanceof RenderPageRequestHandler); + return isPageRequestHandler ? (IPageRequestHandler)handler : null; + } } diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/OriginBasedResourceIsolationPolicy.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/OriginBasedResourceIsolationPolicy.java index e8fd195..bf07276 100644 --- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/OriginBasedResourceIsolationPolicy.java +++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/OriginBasedResourceIsolationPolicy.java @@ -29,251 +29,255 @@ import org.apache.wicket.util.string.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class OriginBasedResourceIsolationPolicy implements ResourceIsolationPolicy { - private static final Logger log = LoggerFactory - .getLogger(OriginBasedResourceIsolationPolicy.class); +public class OriginBasedResourceIsolationPolicy implements ResourceIsolationPolicy +{ + private static final Logger log = LoggerFactory + .getLogger(OriginBasedResourceIsolationPolicy.class); - /** - * A white list of accepted origins (host names/domain names) presented as - * <domainname>.<TLD>. The domain part can contain subdomains. - */ - private Collection<String> acceptedOrigins = new ArrayList<>(); + /** + * A white list of accepted origins (host names/domain names) presented as + * <domainname>.<TLD>. The domain part can contain subdomains. + */ + private Collection<String> acceptedOrigins = new ArrayList<>(); - /** - * Adds an origin (host name/domain name) to the white list. An origin is in the form of - * <domainname>.<TLD>, and can contain a subdomain. Every Origin header that matches - * a domain from the whitelist is accepted and not checked any further for CSRF issues. - * - * E.g. when {@code example.com} is in the white list, this allows requests from (i.e. with an - * {@code Origin:} header containing) {@code example.com} and {@code blabla.example.com} but - * rejects requests from {@code blablaexample.com} and {@code example2.com}. - * - * @param acceptedOrigin - * the acceptable origin - * @return this - */ - public OriginBasedResourceIsolationPolicy addAcceptedOrigin(String acceptedOrigin) - { - Checks.notNull("acceptedOrigin", acceptedOrigin); + /** + * Adds an origin (host name/domain name) to the white list. An origin is in the form of + * <domainname>.<TLD>, and can contain a subdomain. Every Origin header that matches + * a domain from the whitelist is accepted and not checked any further for CSRF issues. + * + * E.g. when {@code example.com} is in the white list, this allows requests from (i.e. with an + * {@code Origin:} header containing) {@code example.com} and {@code blabla.example.com} but + * rejects requests from {@code blablaexample.com} and {@code example2.com}. + * + * @param acceptedOrigin + * the acceptable origin + * @return this + */ + public OriginBasedResourceIsolationPolicy addAcceptedOrigin(String acceptedOrigin) + { + Checks.notNull("acceptedOrigin", acceptedOrigin); - // strip any leading dot characters - final int len = acceptedOrigin.length(); - int i = 0; - while (i < len && acceptedOrigin.charAt(i) == '.') - { - i++; - } - acceptedOrigins.add(acceptedOrigin.substring(i)); - return this; - } + // strip any leading dot characters + final int len = acceptedOrigin.length(); + int i = 0; + while (i < len && acceptedOrigin.charAt(i) == '.') + { + i++; + } + acceptedOrigins.add(acceptedOrigin.substring(i)); + return this; + } - /** - * This origin-based listener can be used in combination with the {@link FetchMetadataRequestCycleListener} - * to add support for legacy browsers that don't send Sec-Fetch-* headers yet. - * @return whether the request is allowed based on its origin - */ - @Override - public boolean isRequestAllowed(HttpServletRequest request, IRequestablePage targetPage) { - String sourceUri = getSourceUri(request); + /** + * This origin-based listener can be used in combination with the + * {@link FetchMetadataRequestCycleListener} to add support for legacy browsers that don't send + * Sec-Fetch-* headers yet. + * + * @return whether the request is allowed based on its origin + */ + @Override + public boolean isRequestAllowed(HttpServletRequest request, IRequestablePage targetPage) + { + String sourceUri = getSourceUri(request); - if (sourceUri == null || sourceUri.isEmpty()) - { - log.debug("Source URI not present in request to {}", request.getPathInfo()); - return true; - } - sourceUri = sourceUri.toLowerCase(Locale.ROOT); + if (sourceUri == null || sourceUri.isEmpty()) + { + log.debug("Source URI not present in request to {}", request.getPathInfo()); + return true; + } + sourceUri = sourceUri.toLowerCase(Locale.ROOT); - // if the origin is a know and trusted origin, don't check any further but allow the request - if (isWhitelistedHost(sourceUri)) - { - return true; - } + // if the origin is a know and trusted origin, don't check any further but allow the request + if (isWhitelistedHost(sourceUri)) + { + return true; + } - // check if the origin HTTP header matches the request URI - if (!isLocalOrigin(request, sourceUri)) - { - log.debug("Source URI conflicts with request origin"); - return false; - } + // check if the origin HTTP header matches the request URI + if (!isLocalOrigin(request, sourceUri)) + { + log.debug("Source URI conflicts with request origin"); + return false; + } - return true; - } + return true; + } - /** - * Checks whether the {@code Origin} HTTP header of the request matches where the request came - * from. - * - * @param containerRequest - * the current container request - * @param originHeader - * the contents of the {@code Origin} HTTP header - * @return {@code true} when the origin of the request matches the {@code Origin} HTTP header - */ - protected boolean isLocalOrigin(HttpServletRequest containerRequest, String originHeader) - { - // Make comparable strings from Origin and Location - String origin = normalizeUri(originHeader); - if (origin == null) - return false; + /** + * Checks whether the {@code Origin} HTTP header of the request matches where the request came + * from. + * + * @param containerRequest + * the current container request + * @param originHeader + * the contents of the {@code Origin} HTTP header + * @return {@code true} when the origin of the request matches the {@code Origin} HTTP header + */ + protected boolean isLocalOrigin(HttpServletRequest containerRequest, String originHeader) + { + // Make comparable strings from Origin and Location + String origin = normalizeUri(originHeader); + if (origin == null) + return false; - String request = getTargetUriFromRequest(containerRequest); - if (request == null) - return false; + String request = getTargetUriFromRequest(containerRequest); + if (request == null) + return false; - return origin.equalsIgnoreCase(request); - } + return origin.equalsIgnoreCase(request); + } - /** - * Creates a RFC-6454 comparable URI from the {@code request} requested resource. - * - * @param request - * the incoming request - * @return only the scheme://host[:port] part, or {@code null} when the origin string is not - * compliant - */ - protected final String getTargetUriFromRequest(HttpServletRequest request) - { - // Build scheme://host:port from request - StringBuilder target = new StringBuilder(); - String scheme = request.getScheme(); - if (scheme == null) - { - return null; - } - else - { - scheme = scheme.toLowerCase(Locale.ROOT); - } - target.append(scheme); - target.append("://"); + /** + * Creates a RFC-6454 comparable URI from the {@code request} requested resource. + * + * @param request + * the incoming request + * @return only the scheme://host[:port] part, or {@code null} when the origin string is not + * compliant + */ + protected final String getTargetUriFromRequest(HttpServletRequest request) + { + // Build scheme://host:port from request + StringBuilder target = new StringBuilder(); + String scheme = request.getScheme(); + if (scheme == null) + { + return null; + } + else + { + scheme = scheme.toLowerCase(Locale.ROOT); + } + target.append(scheme); + target.append("://"); - String host = request.getServerName(); - if (host == null) - { - return null; - } - target.append(host); + String host = request.getServerName(); + if (host == null) + { + return null; + } + target.append(host); - int port = request.getServerPort(); - if ("http".equals(scheme) && port != 80 || "https".equals(scheme) && port != 443) - { - target.append(':'); - target.append(port); - } + int port = request.getServerPort(); + if ("http".equals(scheme) && port != 80 || "https".equals(scheme) && port != 443) + { + target.append(':'); + target.append(port); + } - return target.toString(); - } + return target.toString(); + } - /** - * Resolves the source URI from the request headers ({@code Origin} or {@code Referer}). - * - * @param containerRequest - * the current container request - * @return the normalized source URI. - */ - private String getSourceUri(HttpServletRequest containerRequest) - { - String sourceUri = containerRequest.getHeader(WebRequest.HEADER_ORIGIN); - if (Strings.isEmpty(sourceUri)) - { - sourceUri = containerRequest.getHeader(WebRequest.HEADER_REFERER); - } - return normalizeUri(sourceUri); - } + /** + * Resolves the source URI from the request headers ({@code Origin} or {@code Referer}). + * + * @param containerRequest + * the current container request + * @return the normalized source URI. + */ + private String getSourceUri(HttpServletRequest containerRequest) + { + String sourceUri = containerRequest.getHeader(WebRequest.HEADER_ORIGIN); + if (Strings.isEmpty(sourceUri)) + { + sourceUri = containerRequest.getHeader(WebRequest.HEADER_REFERER); + } + return normalizeUri(sourceUri); + } - /** - * Creates a RFC-6454 comparable URI from the {@code uri} string. - * - * @param uri - * the contents of the Origin or Referer HTTP header - * @return only the scheme://host[:port] part, or {@code null} when the URI string is not - * compliant - */ - protected final String normalizeUri(String uri) - { - // the request comes from a privacy sensitive context, flag as non-local origin. If - // alternative action is required, an implementor can override any of the onAborted, - // onSuppressed or onAllowed and implement such needed action. + /** + * Creates a RFC-6454 comparable URI from the {@code uri} string. + * + * @param uri + * the contents of the Origin or Referer HTTP header + * @return only the scheme://host[:port] part, or {@code null} when the URI string is not + * compliant + */ + protected final String normalizeUri(String uri) + { + // the request comes from a privacy sensitive context, flag as non-local origin. If + // alternative action is required, an implementor can override any of the onAborted, + // onSuppressed or onAllowed and implement such needed action. - if (Strings.isEmpty(uri) || "null".equals(uri)) - return null; + if (Strings.isEmpty(uri) || "null".equals(uri)) + return null; - StringBuilder target = new StringBuilder(); + StringBuilder target = new StringBuilder(); - try - { - URI originUri = new URI(uri); - String scheme = originUri.getScheme(); - if (scheme == null) - { - return null; - } - else - { - scheme = scheme.toLowerCase(Locale.ROOT); - } + try + { + URI originUri = new URI(uri); + String scheme = originUri.getScheme(); + if (scheme == null) + { + return null; + } + else + { + scheme = scheme.toLowerCase(Locale.ROOT); + } - target.append(scheme); - target.append("://"); + target.append(scheme); + target.append("://"); - String host = originUri.getHost(); - if (host == null) - { - return null; - } - target.append(host); + String host = originUri.getHost(); + if (host == null) + { + return null; + } + target.append(host); - int port = originUri.getPort(); - boolean portIsSpecified = port != -1; - boolean isAlternateHttpPort = "http".equals(scheme) && port != 80; - boolean isAlternateHttpsPort = "https".equals(scheme) && port != 443; + int port = originUri.getPort(); + boolean portIsSpecified = port != -1; + boolean isAlternateHttpPort = "http".equals(scheme) && port != 80; + boolean isAlternateHttpsPort = "https".equals(scheme) && port != 443; - if (portIsSpecified && (isAlternateHttpPort || isAlternateHttpsPort)) - { - target.append(':'); - target.append(port); - } - return target.toString(); - } - catch (URISyntaxException e) - { - log.debug("Invalid URI provided: {}, marked conflicting", uri); - return null; - } - } + if (portIsSpecified && (isAlternateHttpPort || isAlternateHttpsPort)) + { + target.append(':'); + target.append(port); + } + return target.toString(); + } + catch (URISyntaxException e) + { + log.debug("Invalid URI provided: {}, marked conflicting", uri); + return null; + } + } - /** - * Checks whether the domain part of the {@code sourceUri} ({@code Origin} or {@code Referer} - * header) is whitelisted. - * - * @param sourceUri - * the contents of the {@code Origin} or {@code Referer} HTTP header - * @return {@code true} when the source domain was whitelisted - */ - protected boolean isWhitelistedHost(final String sourceUri) - { - try - { - final String sourceHost = new URI(sourceUri).getHost(); - if (Strings.isEmpty(sourceHost)) - return false; - for (String whitelistedOrigin : acceptedOrigins) - { - if (sourceHost.equalsIgnoreCase(whitelistedOrigin) || - sourceHost.endsWith("." + whitelistedOrigin)) - { - log.trace("Origin {} matched whitelisted origin {}, request accepted", - sourceUri, whitelistedOrigin); - return true; - } - } - } - catch (URISyntaxException e) - { - log.debug("Origin: {} not parseable as an URI. Whitelisted-origin check skipped.", - sourceUri); - } + /** + * Checks whether the domain part of the {@code sourceUri} ({@code Origin} or {@code Referer} + * header) is whitelisted. + * + * @param sourceUri + * the contents of the {@code Origin} or {@code Referer} HTTP header + * @return {@code true} when the source domain was whitelisted + */ + protected boolean isWhitelistedHost(final String sourceUri) + { + try + { + final String sourceHost = new URI(sourceUri).getHost(); + if (Strings.isEmpty(sourceHost)) + return false; + for (String whitelistedOrigin : acceptedOrigins) + { + if (sourceHost.equalsIgnoreCase(whitelistedOrigin) + || sourceHost.endsWith("." + whitelistedOrigin)) + { + log.trace("Origin {} matched whitelisted origin {}, request accepted", + sourceUri, whitelistedOrigin); + return true; + } + } + } + catch (URISyntaxException e) + { + log.debug("Origin: {} not parseable as an URI. Whitelisted-origin check skipped.", + sourceUri); + } - return false; - } + return false; + } } diff --git a/wicket-core/src/test/java/org/apache/wicket/protocol/http/FetchMetadataRequestCycleListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/protocol/http/FetchMetadataRequestCycleListenerTest.java index 487ed12..f2c290c 100644 --- a/wicket-core/src/test/java/org/apache/wicket/protocol/http/FetchMetadataRequestCycleListenerTest.java +++ b/wicket-core/src/test/java/org/apache/wicket/protocol/http/FetchMetadataRequestCycleListenerTest.java @@ -32,152 +32,163 @@ import org.apache.wicket.util.tester.WicketTestCase; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class FetchMetadataRequestCycleListenerTest extends WicketTestCase { - - private FetchMetadataRequestCycleListener fetchMetadataListener; - - @BeforeEach - void before() { - withCustomListener(new FetchMetadataRequestCycleListener()); - } - - void withCustomListener(FetchMetadataRequestCycleListener fetchMetadataListener) { - WebApplication application = tester.getApplication(); - - this.fetchMetadataListener = fetchMetadataListener; - application.getRequestCycleListeners().add(fetchMetadataListener); - - tester.startPage(FirstPage.class); - tester.assertRenderedPage(FirstPage.class); - } - - /** - * Tests whether a request with Sec-Fetch-Site = cross-site is aborted - */ - @Test - void crossSiteFMAborted() { - tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); - - assertRequestAborted(); - } - - /** - * Tests whether embed requests are aborted by fetch metadata checks - */ - @Test - void destEmbedFMAborted() { - tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); - tester.addRequestHeader(SEC_FETCH_DEST_HEADER, DEST_EMBED); - - assertRequestAborted(); - } - - /** - * Tests whether object requests (sec-fetch-dest :"object" ) are aborted by FM checks - */ - @Test - void destObjectAborted() { - tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); - tester.addRequestHeader(SEC_FETCH_DEST_HEADER, DEST_OBJECT); - - assertRequestAborted(); - } - - /** - * Tests whether a top level navigation request is allowed by FM checks - */ - @Test - void topLevelNavigationAllowedFM() { - tester.addRequestHeader(SEC_FETCH_SITE_HEADER, SAME_ORIGIN); - tester.addRequestHeader(SEC_FETCH_MODE_HEADER, MODE_NAVIGATE); - - assertRequestAccepted(); - } - - /** - * Tests that requests rejected by fetch metadata have the Vary header set - */ - @Test - void varyHeaderSetWhenFetchMetadataRejectsRequest() { - tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); - assertRequestAborted(); - - String vary = tester.getLastResponse().getHeader("Vary"); - - if (vary == null) { - throw new AssertionError("Vary header should not be null"); - } - - if (!vary.contains(SEC_FETCH_DEST_HEADER) || !vary.contains(SEC_FETCH_MODE_HEADER) - || !vary.contains(SEC_FETCH_SITE_HEADER)) { - throw new AssertionError("Unexpected vary header: " + vary); - } - } - - /** - * Tests that requests accepted by fetch metadata have the Vary header set - */ - @Test - void varyHeaderSetWhenFetchMetadataAcceptsRequest() { - tester.addRequestHeader(SEC_FETCH_SITE_HEADER, SAME_SITE); - assertRequestAccepted(); - - String vary = tester.getLastResponse().getHeader(VARY_HEADER); - if (vary == null) { - throw new AssertionError("Vary header should not be null"); - } - - if (!vary.contains(SEC_FETCH_DEST_HEADER) || !vary.contains(SEC_FETCH_MODE_HEADER) - || !vary.contains(SEC_FETCH_SITE_HEADER)) { - throw new AssertionError("Unexpected vary header: " + vary); - } - } - - @Test - void whenAtLeastOnePolicyRejectsRequest_thenRequestRejected() { - fetchMetadataListener = new FetchMetadataRequestCycleListener( - (request, page) -> true, - (request, page) -> true, - (request, page) -> false, - (request, page) -> true - ); - - withCustomListener(fetchMetadataListener); - assertRequestAborted(); - } - - @Test - void whenAllPoliciesAcceptRequest_thenRequestAccepted() { - fetchMetadataListener = new FetchMetadataRequestCycleListener( - (request, page) -> true, - (request, page) -> true, - (request, page) -> true, - (request, page) -> true - ); - - withCustomListener(fetchMetadataListener); - assertRequestAccepted(); - } - - @Test - void whenCrossOriginRequestToExempted_thenRequestAccepted() { - fetchMetadataListener.addExemptedPaths("/wicket/bookmarkable/org.apache.wicket.protocol.http.FirstPage"); - withCustomListener(fetchMetadataListener); - - tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); - assertRequestAccepted(); - } - - private void assertRequestAborted() { - tester.clickLink("link"); - assertEquals(tester.getLastResponse().getStatus(), - FetchMetadataRequestCycleListener.ERROR_CODE); - assertEquals(tester.getLastResponse().getErrorMessage(), - FetchMetadataRequestCycleListener.ERROR_MESSAGE); - } - - private void assertRequestAccepted() { - tester.clickLink("link"); - tester.assertRenderedPage(SecondPage.class); - } +public class FetchMetadataRequestCycleListenerTest extends WicketTestCase +{ + + private FetchMetadataRequestCycleListener fetchMetadataListener; + + @BeforeEach + void before() + { + withCustomListener(new FetchMetadataRequestCycleListener()); + } + + void withCustomListener(FetchMetadataRequestCycleListener fetchMetadataListener) + { + WebApplication application = tester.getApplication(); + + this.fetchMetadataListener = fetchMetadataListener; + application.getRequestCycleListeners().add(fetchMetadataListener); + + tester.startPage(FirstPage.class); + tester.assertRenderedPage(FirstPage.class); + } + + /** + * Tests whether a request with Sec-Fetch-Site = cross-site is aborted + */ + @Test + void crossSiteFMAborted() + { + tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); + + assertRequestAborted(); + } + + /** + * Tests whether embed requests are aborted by fetch metadata checks + */ + @Test + void destEmbedFMAborted() + { + tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); + tester.addRequestHeader(SEC_FETCH_DEST_HEADER, DEST_EMBED); + + assertRequestAborted(); + } + + /** + * Tests whether object requests (sec-fetch-dest :"object" ) are aborted by FM checks + */ + @Test + void destObjectAborted() + { + tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); + tester.addRequestHeader(SEC_FETCH_DEST_HEADER, DEST_OBJECT); + + assertRequestAborted(); + } + + /** + * Tests whether a top level navigation request is allowed by FM checks + */ + @Test + void topLevelNavigationAllowedFM() + { + tester.addRequestHeader(SEC_FETCH_SITE_HEADER, SAME_ORIGIN); + tester.addRequestHeader(SEC_FETCH_MODE_HEADER, MODE_NAVIGATE); + + assertRequestAccepted(); + } + + /** + * Tests that requests rejected by fetch metadata have the Vary header set + */ + @Test + void varyHeaderSetWhenFetchMetadataRejectsRequest() + { + tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); + assertRequestAborted(); + + String vary = tester.getLastResponse().getHeader("Vary"); + + if (vary == null) + { + throw new AssertionError("Vary header should not be null"); + } + + if (!vary.contains(SEC_FETCH_DEST_HEADER) || !vary.contains(SEC_FETCH_MODE_HEADER) + || !vary.contains(SEC_FETCH_SITE_HEADER)) + { + throw new AssertionError("Unexpected vary header: " + vary); + } + } + + /** + * Tests that requests accepted by fetch metadata have the Vary header set + */ + @Test + void varyHeaderSetWhenFetchMetadataAcceptsRequest() + { + tester.addRequestHeader(SEC_FETCH_SITE_HEADER, SAME_SITE); + assertRequestAccepted(); + + String vary = tester.getLastResponse().getHeader(VARY_HEADER); + if (vary == null) + { + throw new AssertionError("Vary header should not be null"); + } + + if (!vary.contains(SEC_FETCH_DEST_HEADER) || !vary.contains(SEC_FETCH_MODE_HEADER) + || !vary.contains(SEC_FETCH_SITE_HEADER)) + { + throw new AssertionError("Unexpected vary header: " + vary); + } + } + + @Test + void whenAtLeastOnePolicyRejectsRequest_thenRequestRejected() + { + fetchMetadataListener = new FetchMetadataRequestCycleListener((request, page) -> true, + (request, page) -> true, (request, page) -> false, (request, page) -> true); + + withCustomListener(fetchMetadataListener); + assertRequestAborted(); + } + + @Test + void whenAllPoliciesAcceptRequest_thenRequestAccepted() + { + fetchMetadataListener = new FetchMetadataRequestCycleListener((request, page) -> true, + (request, page) -> true, (request, page) -> true, (request, page) -> true); + + withCustomListener(fetchMetadataListener); + assertRequestAccepted(); + } + + @Test + void whenCrossOriginRequestToExempted_thenRequestAccepted() + { + fetchMetadataListener + .addExemptedPaths("/wicket/bookmarkable/org.apache.wicket.protocol.http.FirstPage"); + withCustomListener(fetchMetadataListener); + + tester.addRequestHeader(SEC_FETCH_SITE_HEADER, CROSS_SITE); + assertRequestAccepted(); + } + + private void assertRequestAborted() + { + tester.clickLink("link"); + assertEquals(tester.getLastResponse().getStatus(), + FetchMetadataRequestCycleListener.ERROR_CODE); + assertEquals(tester.getLastResponse().getErrorMessage(), + FetchMetadataRequestCycleListener.ERROR_MESSAGE); + } + + private void assertRequestAccepted() + { + tester.clickLink("link"); + tester.assertRenderedPage(SecondPage.class); + } }
