You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by pa...@apache.org on 2020/01/24 10:50:31 UTC
[wicket] branch csp-configurable updated: WICKET-6727: easy
reporting and use nonce per page
This is an automated email from the ASF dual-hosted git repository.
papegaaij pushed a commit to branch csp-configurable
in repository https://gitbox.apache.org/repos/asf/wicket.git
The following commit(s) were added to refs/heads/csp-configurable by this push:
new cc2da2e WICKET-6727: easy reporting and use nonce per page
cc2da2e is described below
commit cc2da2e0c427c263482facfc10ec05957955afc7
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Fri Jan 24 11:47:44 2020 +0100
WICKET-6727: easy reporting and use nonce per page
---
.../java/org/apache/wicket/csp/CSPDirective.java | 8 +-
.../wicket/csp/CSPDirectiveSandboxValue.java | 4 +-
.../apache/wicket/csp/CSPDirectiveSrcValue.java | 9 +-
.../apache/wicket/csp/CSPHeaderConfiguration.java | 91 +++++++++++++++--
...r.java => CSPNonceHeaderResponseDecorator.java} | 7 +-
.../java/org/apache/wicket/csp/CSPRenderable.java | 8 +-
.../wicket/csp/ContentSecurityPolicyEnforcer.java | 55 ++++++++++-
.../{FixedCSPDirective.java => FixedCSPValue.java} | 12 ++-
...dCSPDirective.java => RelativeURICSPValue.java} | 52 ++++------
.../wicket/csp/ReportCSPViolationMapper.java | 110 +++++++++++++++++++++
.../wicket/protocol/http/WebApplication.java | 17 ++--
.../head/filter/FilteringHeaderResponseTest.java | 7 +-
12 files changed, 308 insertions(+), 72 deletions(-)
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirective.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirective.java
index 6507a1c..a73ec66 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirective.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirective.java
@@ -98,14 +98,18 @@ public enum CSPDirective
"A report-uri directive can only contain one URI, it already contains "
+ existingDirectiveValues);
}
- if (!(value instanceof FixedCSPDirective))
+ if (value instanceof RelativeURICSPValue)
+ {
+ return;
+ }
+ if (!(value instanceof FixedCSPValue))
{
throw new IllegalArgumentException(
"A report-uri directive can only contain an URI, not " + value);
}
try
{
- new URI(value.render(null, null));
+ new URI(value.toString());
}
catch (URISyntaxException urise)
{
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSandboxValue.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSandboxValue.java
index 8236ee6..d42d72b 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSandboxValue.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSandboxValue.java
@@ -16,6 +16,7 @@
*/
package org.apache.wicket.csp;
+import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.RequestCycle;
/**
@@ -42,7 +43,8 @@ public enum CSPDirectiveSandboxValue implements CSPRenderable
}
@Override
- public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
+ public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle,
+ IRequestHandler currentHandler)
{
return value;
}
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSrcValue.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSrcValue.java
index 0bb4a4e..e897849 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSrcValue.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSrcValue.java
@@ -16,6 +16,7 @@
*/
package org.apache.wicket.csp;
+import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.RequestCycle;
/**
@@ -32,9 +33,10 @@ public enum CSPDirectiveSrcValue implements CSPRenderable
NONCE("'nonce-%1$s'")
{
@Override
- public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
+ public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle,
+ IRequestHandler currentHandler)
{
- return String.format(getValue(), listener.getNonce(cycle));
+ return String.format(getValue(), listener.getNonce(cycle, currentHandler));
}
};
@@ -46,7 +48,8 @@ public enum CSPDirectiveSrcValue implements CSPRenderable
}
@Override
- public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
+ public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle,
+ IRequestHandler currentHandler)
{
return value;
}
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderConfiguration.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderConfiguration.java
index 76c7a6a..836e23b 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderConfiguration.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderConfiguration.java
@@ -22,6 +22,7 @@ import static org.apache.wicket.csp.CSPDirective.DEFAULT_SRC;
import static org.apache.wicket.csp.CSPDirective.FONT_SRC;
import static org.apache.wicket.csp.CSPDirective.IMG_SRC;
import static org.apache.wicket.csp.CSPDirective.MANIFEST_SRC;
+import static org.apache.wicket.csp.CSPDirective.REPORT_URI;
import static org.apache.wicket.csp.CSPDirective.SCRIPT_SRC;
import static org.apache.wicket.csp.CSPDirective.STYLE_SRC;
import static org.apache.wicket.csp.CSPDirectiveSrcValue.NONCE;
@@ -37,6 +38,7 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.RequestCycle;
/**
@@ -52,11 +54,15 @@ import org.apache.wicket.request.cycle.RequestCycle;
*/
public class CSPHeaderConfiguration
{
+ public static final String CSP_VIOLATION_REPORTING_URI = "cspviolation";
+
private Map<CSPDirective, List<CSPRenderable>> directives = new EnumMap<>(CSPDirective.class);
private boolean addLegacyHeaders = false;
private boolean nonceEnabled = false;
+
+ private String reportUriMountPath = null;
public CSPHeaderConfiguration()
{
@@ -126,6 +132,54 @@ public class CSPHeaderConfiguration
}
/**
+ * Configures the CSP to report violations back at the application.
+ *
+ * WARNING: CSP reporting can generate a lot of traffic. A single page load can trigger multiple
+ * violations and flood your logs or even DDoS your server. In addition, it is an open endpoint
+ * for your application and can be used by an attacker to flood your application logs. Do not
+ * enable this feature on a production application unless you take the needed precautions to
+ * prevent this.
+ *
+ * @return {@code this} for chaining
+ * @see <a href=
+ * "https://scotthelme.co.uk/just-how-much-traffic-can-you-generate-using-csp">https://scotthelme.co.uk/just-how-much-traffic-can-you-generate-using-csp</a>
+ */
+ public CSPHeaderConfiguration reportBack()
+ {
+ return reportBackAt(CSP_VIOLATION_REPORTING_URI);
+ }
+
+ /**
+ * Configures the CSP to report violations at the specified relative URI.
+ *
+ * WARNING: CSP reporting can generate a lot of traffic. A single page load can trigger multiple
+ * violations and flood your logs or even DDoS your server. In addition, it is an open endpoint
+ * for your application and can be used by an attacker to flood your application logs. Do not
+ * enable this feature on a production application unless you take the needed precautions to
+ * prevent this.
+ *
+ * @param mountPath
+ * The path to report the violations at.
+ * @return {@code this} for chaining
+ * @see <a href=
+ * "https://scotthelme.co.uk/just-how-much-traffic-can-you-generate-using-csp">https://scotthelme.co.uk/just-how-much-traffic-can-you-generate-using-csp</a>
+ */
+ public CSPHeaderConfiguration reportBackAt(String mountPath)
+ {
+ return add(REPORT_URI, new RelativeURICSPValue(mountPath));
+ }
+
+ /**
+ * Returns the report URI mount path.
+ *
+ * @return the report URI mount path.
+ */
+ String getReportUriMountPath()
+ {
+ return reportUriMountPath;
+ }
+
+ /**
* True when the {@link CSPDirectiveSrcValue#NONCE} is used in one of the directives.
*
* @return When any of the directives contains a nonce.
@@ -168,7 +222,7 @@ public class CSPHeaderConfiguration
public CSPHeaderConfiguration remove(CSPDirective directive)
{
directives.remove(directive);
- return this;
+ return recalculateState();
}
/**
@@ -186,7 +240,7 @@ public class CSPHeaderConfiguration
{
doAddDirective(directive, value);
}
- return this;
+ return recalculateState();
}
/**
@@ -203,9 +257,9 @@ public class CSPHeaderConfiguration
{
for (String value : values)
{
- doAddDirective(directive, new FixedCSPDirective(value));
+ doAddDirective(directive, new FixedCSPValue(value));
}
- return this;
+ return recalculateState();
}
/**
@@ -224,7 +278,26 @@ public class CSPHeaderConfiguration
public CSPHeaderConfiguration clear()
{
directives.clear();
- nonceEnabled = false;
+ return recalculateState();
+ }
+
+ private CSPHeaderConfiguration recalculateState()
+ {
+ nonceEnabled = directives.values()
+ .stream()
+ .flatMap(List::stream)
+ .anyMatch(value -> value == CSPDirectiveSrcValue.NONCE);
+
+ reportUriMountPath = null;
+ List<CSPRenderable> reportValues = directives.get(CSPDirective.REPORT_URI);
+ if (reportValues != null && !reportValues.isEmpty())
+ {
+ CSPRenderable reportUri = reportValues.get(0);
+ if (reportUri instanceof RelativeURICSPValue)
+ {
+ reportUriMountPath = reportUri.toString();
+ }
+ }
return this;
}
@@ -240,7 +313,6 @@ public class CSPHeaderConfiguration
List<CSPRenderable> values = directives.computeIfAbsent(directive, x -> new ArrayList<>());
directive.checkValueForDirective(value, values);
values.add(value);
- nonceEnabled |= CSPDirectiveSrcValue.NONCE == value;
return this;
}
@@ -252,16 +324,19 @@ public class CSPHeaderConfiguration
* The {@link ContentSecurityPolicyEnforcer} that renders the header.
* @param cycle
* The current {@link RequestCycle}.
+ * @param currentHandler
+ * The handler that is currently being evaluated or executed.
* @return the rendered header.
*/
- public String renderHeaderValue(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
+ public String renderHeaderValue(ContentSecurityPolicyEnforcer listener, RequestCycle cycle,
+ IRequestHandler currentHandler)
{
return directives.entrySet()
.stream()
.map(e -> e.getKey().getValue() + " "
+ e.getValue()
.stream()
- .map(r -> r.render(listener, cycle))
+ .map(r -> r.render(listener, cycle, currentHandler))
.collect(Collectors.joining(" ")))
.collect(Collectors.joining("; "));
}
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CspNonceHeaderResponseDecorator.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPNonceHeaderResponseDecorator.java
similarity index 90%
rename from wicket-core/src/main/java/org/apache/wicket/csp/CspNonceHeaderResponseDecorator.java
rename to wicket-core/src/main/java/org/apache/wicket/csp/CSPNonceHeaderResponseDecorator.java
index bb0d6ee..8424182 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CspNonceHeaderResponseDecorator.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPNonceHeaderResponseDecorator.java
@@ -27,11 +27,11 @@ import org.apache.wicket.request.cycle.RequestCycle;
* Add a <em>Content Security Policy<em> (CSP) nonce to all {@link AbstractCspHeaderItem}s when that
* is required by the configuration of the CSP.
*/
-public class CspNonceHeaderResponseDecorator extends DecoratingHeaderResponse
+public class CSPNonceHeaderResponseDecorator extends DecoratingHeaderResponse
{
private ContentSecurityPolicyEnforcer listener;
- public CspNonceHeaderResponseDecorator(IHeaderResponse real, ContentSecurityPolicyEnforcer listener)
+ public CSPNonceHeaderResponseDecorator(IHeaderResponse real, ContentSecurityPolicyEnforcer listener)
{
super(real);
@@ -51,7 +51,8 @@ public class CspNonceHeaderResponseDecorator extends DecoratingHeaderResponse
if (checkitem instanceof AbstractCspHeaderItem)
{
- ((AbstractCspHeaderItem) checkitem).setNonce(listener.getNonce(RequestCycle.get()));
+ ((AbstractCspHeaderItem) checkitem).setNonce(listener.getNonce(RequestCycle.get(),
+ RequestCycle.get().getActiveRequestHandler()));
}
}
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPRenderable.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPRenderable.java
index 1b67e92..0663490 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPRenderable.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPRenderable.java
@@ -16,6 +16,7 @@
*/
package org.apache.wicket.csp;
+import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.RequestCycle;
/**
@@ -25,7 +26,7 @@ import org.apache.wicket.request.cycle.RequestCycle;
* @author papegaaij
* @see CSPDirectiveSrcValue
* @see CSPDirectiveSandboxValue
- * @see FixedCSPDirective
+ * @see FixedCSPValue
*/
public interface CSPRenderable
{
@@ -36,9 +37,12 @@ public interface CSPRenderable
* The {@link ContentSecurityPolicyEnforcer} that renders this value.
* @param cycle
* The current {@link RequestCycle}.
+ * @param currentHandler
+ * The handler that is currently being evaluated or executed.
* @return The rendered value.
*/
- public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle);
+ public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle,
+ IRequestHandler currentHandler);
/**
* Checks if the {@code CSPRenderable} represents a valid value for a {@code -src} directive. By
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicyEnforcer.java b/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicyEnforcer.java
index ca6c173..54ca9e5 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicyEnforcer.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicyEnforcer.java
@@ -16,14 +16,17 @@
*/
package org.apache.wicket.csp;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import org.apache.wicket.Application;
import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.Page;
import org.apache.wicket.core.request.handler.BufferedResponseRequestHandler;
import org.apache.wicket.core.request.handler.IPageClassRequestHandler;
+import org.apache.wicket.core.request.handler.IPageRequestHandler;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.IRequestHandlerDelegate;
import org.apache.wicket.request.cycle.IRequestCycleListener;
@@ -141,7 +144,7 @@ public class ContentSecurityPolicyEnforcer implements IRequestCycleListener
configs.entrySet().stream().filter(entry -> entry.getValue().isSet()).forEach(entry -> {
CSPHeaderMode mode = entry.getKey();
CSPHeaderConfiguration config = entry.getValue();
- String headerValue = config.renderHeaderValue(this, cycle);
+ String headerValue = config.renderHeaderValue(this, cycle, handler);
webResponse.setHeader(mode.getHeader(), headerValue);
if (config.isAddLegacyHeaders())
{
@@ -160,19 +163,65 @@ public class ContentSecurityPolicyEnforcer implements IRequestCycleListener
return configs.values().stream().anyMatch(CSPHeaderConfiguration::isNonceEnabled);
}
- public String getNonce(RequestCycle cycle)
+ public String getNonce(RequestCycle cycle, IRequestHandler handler)
{
String nonce = cycle.getMetaData(NONCE_KEY);
+ Page currentPage = getPage(handler);
if (nonce == null)
{
- nonce = getSecuritySettings().getRandomSupplier().getRandomBase64(NONCE_LENGTH);
+ if (currentPage != null)
+ {
+ nonce = currentPage.getMetaData(NONCE_KEY);
+ }
+ if (nonce == null)
+ {
+ nonce = getSecuritySettings().getRandomSupplier().getRandomBase64(NONCE_LENGTH);
+ }
cycle.setMetaData(NONCE_KEY, nonce);
}
+ if (currentPage != null)
+ {
+ currentPage.setMetaData(NONCE_KEY, nonce);
+ }
return nonce;
}
+ /**
+ * Resolves a page instance from the request handler iff the page instance is already created
+ *
+ * @param handler
+ * @return page or {@code null} if none
+ */
+ public static Page getPage(IRequestHandler handler)
+ {
+ while (handler instanceof IRequestHandlerDelegate)
+ {
+ handler = ((IRequestHandlerDelegate) handler).getDelegateHandler();
+ }
+
+ if (handler instanceof IPageRequestHandler)
+ {
+ IPageRequestHandler pageHandler = (IPageRequestHandler) handler;
+ if (pageHandler.isPageInstanceCreated())
+ {
+ return (Page) pageHandler.getPage();
+ }
+ }
+ return null;
+ }
+
private SecuritySettings getSecuritySettings()
{
return application.getSecuritySettings();
}
+
+ /**
+ * Returns the CSP configuration per {@link CSPHeaderMode}.
+ *
+ * @return the CSP configuration per {@link CSPHeaderMode}.
+ */
+ public Map<CSPHeaderMode, CSPHeaderConfiguration> getConfiguration()
+ {
+ return Collections.unmodifiableMap(configs);
+ }
}
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java b/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPValue.java
similarity index 86%
copy from wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java
copy to wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPValue.java
index b0f56b3..2b73f3d 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPValue.java
@@ -19,25 +19,26 @@ package org.apache.wicket.csp;
import java.net.URI;
import java.net.URISyntaxException;
+import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.util.string.Strings;
/**
- * A simple CSP directive that renders the string specified.
+ * A simple CSP value that renders the string specified.
*
* @author papegaaij
*/
-public class FixedCSPDirective implements CSPRenderable
+public class FixedCSPValue implements CSPRenderable
{
private String value;
/**
- * Creates a new {@code FixedCSPDirective} for the given value.
+ * Creates a new {@code FixedCSPValue} for the given value.
*
* @param value
* the value to render;
*/
- public FixedCSPDirective(String value)
+ public FixedCSPValue(String value)
{
if (Strings.isEmpty(value))
{
@@ -47,7 +48,8 @@ public class FixedCSPDirective implements CSPRenderable
}
@Override
- public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
+ public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle,
+ IRequestHandler currentHandler)
{
return value;
}
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java b/wicket-core/src/main/java/org/apache/wicket/csp/RelativeURICSPValue.java
similarity index 56%
rename from wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java
rename to wicket-core/src/main/java/org/apache/wicket/csp/RelativeURICSPValue.java
index b0f56b3..a5feb10 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/RelativeURICSPValue.java
@@ -19,68 +19,54 @@ package org.apache.wicket.csp;
import java.net.URI;
import java.net.URISyntaxException;
+import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.RequestCycle;
-import org.apache.wicket.util.string.Strings;
+import org.apache.wicket.util.lang.Args;
/**
- * A simple CSP directive that renders the string specified.
+ * A CSP value that renders an URI relative to the context root of the Wicket application.
*
* @author papegaaij
*/
-public class FixedCSPDirective implements CSPRenderable
+public class RelativeURICSPValue implements CSPRenderable
{
- private String value;
+ private String relativeUri;
/**
- * Creates a new {@code FixedCSPDirective} for the given value.
+ * Creates a new {@code RelativeURICSPValue} for the given relative URI.
*
- * @param value
- * the value to render;
+ * @param relativeUri
+ * The part of the URI relative to the context root of the Wicket application.
*/
- public FixedCSPDirective(String value)
+ public RelativeURICSPValue(String relativeUri)
{
- if (Strings.isEmpty(value))
- {
- throw new IllegalArgumentException("CSP directive cannot have empty or null values");
- }
- this.value = value;
+ Args.notEmpty(relativeUri, "relativeUri");
+ this.relativeUri = relativeUri;
}
@Override
- public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
+ public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle,
+ IRequestHandler currentHandler)
{
- return value;
+ return cycle.getUrlRenderer().renderContextRelativeUrl(relativeUri);
}
-
+
@Override
public void checkValidityForSrc()
{
- String strValue = value;
- if ("data:".equals(strValue) || "https:".equals(strValue))
- {
- return;
- }
-
- // strip off "*." so "*.example.com" becomes "example.com" and we can check if
- // it is a valid uri
- if (strValue.startsWith("*."))
- {
- strValue = strValue.substring(2);
- }
-
try
{
- new URI(strValue);
+ new URI("https://example.com/" + relativeUri);
}
catch (URISyntaxException urise)
{
- throw new IllegalArgumentException("Illegal URI for -src directive", urise);
+ throw new IllegalArgumentException("Illegal relative URI", urise);
}
}
-
+
@Override
public String toString()
{
- return value;
+ return relativeUri;
}
}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/ReportCSPViolationMapper.java b/wicket-core/src/main/java/org/apache/wicket/csp/ReportCSPViolationMapper.java
new file mode 100644
index 0000000..6179060
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/ReportCSPViolationMapper.java
@@ -0,0 +1,110 @@
+package org.apache.wicket.csp;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+import org.apache.wicket.request.IRequestCycle;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.IRequestMapper;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.mapper.AbstractMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A simple {@link IRequestMapper} that logs the content of a CSP violation.
+ *
+ * @author papegaaij
+ * @see CSPHeaderConfiguration#reportBack()
+ */
+public class ReportCSPViolationMapper extends AbstractMapper
+{
+ private static final int MAX_LOG_SIZE = 4 * 1024;
+
+ private static final Logger log = LoggerFactory.getLogger(ReportCSPViolationMapper.class);
+
+ private ContentSecurityPolicyEnforcer csp;
+
+ public ReportCSPViolationMapper(ContentSecurityPolicyEnforcer csp)
+ {
+ this.csp = csp;
+ }
+
+ @Override
+ public IRequestHandler mapRequest(Request request)
+ {
+ if (requestMatches(request))
+ {
+ return new IRequestHandler()
+ {
+ @Override
+ public void respond(IRequestCycle requestCycle)
+ {
+ try
+ {
+ HttpServletRequest httpRequest =
+ ((ServletWebRequest) requestCycle.getRequest()).getContainerRequest();
+ log.error(reportToString(httpRequest));
+ }
+ catch (IOException e)
+ {
+ throw new WicketRuntimeException(e);
+ }
+ }
+
+ private String reportToString(HttpServletRequest httpRequest) throws IOException
+ {
+ try (StringWriter sw = new StringWriter())
+ {
+ char[] buffer = new char[MAX_LOG_SIZE];
+ int n = 0;
+ if (-1 != (n = httpRequest.getReader().read(buffer)))
+ {
+ sw.write(buffer, 0, n);
+ }
+ return sw.toString();
+ }
+ }
+ };
+ }
+ return null;
+ }
+
+ @Override
+ public int getCompatibilityScore(Request request)
+ {
+ return requestMatches(request) ? 1000 : 0;
+ }
+
+ private boolean requestMatches(Request request)
+ {
+ if (request instanceof ServletWebRequest)
+ {
+ if (!((ServletWebRequest) request).getContainerRequest().getMethod().equals("POST"))
+ {
+ return false;
+ }
+ for (CSPHeaderConfiguration curConfig : csp.getConfiguration().values())
+ {
+ String mountPath = curConfig.getReportUriMountPath();
+ if (mountPath != null
+ && urlStartsWith(request.getUrl(), getMountSegments(mountPath)))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Url mapHandler(IRequestHandler requestHandler)
+ {
+ return null;
+ }
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
index 1d72fff..0c6dc68 100644
--- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
+++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
@@ -42,7 +42,8 @@ import org.apache.wicket.core.util.file.WebApplicationPath;
import org.apache.wicket.core.util.resource.ClassPathResourceFinder;
import org.apache.wicket.csp.CSPHeaderConfiguration;
import org.apache.wicket.csp.ContentSecurityPolicyEnforcer;
-import org.apache.wicket.csp.CspNonceHeaderResponseDecorator;
+import org.apache.wicket.csp.ReportCSPViolationMapper;
+import org.apache.wicket.csp.CSPNonceHeaderResponseDecorator;
import org.apache.wicket.markup.MarkupType;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
@@ -749,6 +750,13 @@ public abstract class WebApplication extends Application
getResourceSettings().setFileCleaner(new FileCleaner());
+ cspEnforcer = newCspEnforcer();
+ getRequestCycleListeners().add(getCsp());
+ getHeaderResponseDecorators()
+ .add(response -> new CSPNonceHeaderResponseDecorator(response, getCsp()));
+ mount(new ReportCSPViolationMapper(getCsp()));
+ getCsp().blocking().unsafeInline();
+
if (getConfigurationType() == RuntimeConfigurationType.DEVELOPMENT)
{
// Add optional sourceFolder for resources.
@@ -757,6 +765,7 @@ public abstract class WebApplication extends Application
{
getResourceSettings().getResourceFinders().add(new Path(resourceFolder));
}
+ getCsp().blocking().reportBack();
}
setPageRendererProvider(WebPageRenderer::new);
setSessionStoreProvider(HttpSessionStore::new);
@@ -764,12 +773,6 @@ public abstract class WebApplication extends Application
getAjaxRequestTargetListeners().add(new AjaxEnclosureListener());
- cspEnforcer = newCspEnforcer();
- getRequestCycleListeners().add(getCsp());
- getHeaderResponseDecorators()
- .add(response -> new CspNonceHeaderResponseDecorator(response, getCsp()));
- getCsp().blocking().unsafeInline();
-
// Configure the app.
configure();
}
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponseTest.java b/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponseTest.java
index 2d5c467..0463fab 100644
--- a/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponseTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/FilteringHeaderResponseTest.java
@@ -20,15 +20,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Collections;
-import org.apache.wicket.csp.CSPDirective;
import org.apache.wicket.csp.ContentSecurityPolicyEnforcer;
-import org.apache.wicket.markup.head.IHeaderResponse;
-import org.apache.wicket.markup.head.ResourceAggregator;
import org.apache.wicket.markup.head.StringHeaderItem;
import org.apache.wicket.markup.head.internal.HeaderResponse;
-import org.apache.wicket.markup.html.IHeaderResponseDecorator;
import org.apache.wicket.mock.MockApplication;
import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.response.StringResponse;
@@ -53,7 +50,7 @@ class FilteringHeaderResponseTest extends WicketTestCase
return new ContentSecurityPolicyEnforcer(this)
{
@Override
- public String getNonce(RequestCycle cycle)
+ public String getNonce(RequestCycle cycle, IRequestHandler currentHandler)
{
return "NONCE";
}