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/17 21:26:55 UTC

[wicket] branch csp-configurable updated (11a2a6e -> 1e509dd)

This is an automated email from the ASF dual-hosted git repository.

papegaaij pushed a change to branch csp-configurable
in repository https://gitbox.apache.org/repos/asf/wicket.git.


 discard 11a2a6e  WICKET-6727: enable CSP in WebApplication and provide default profiles
    omit 9dddf2e  WICKET-6727: do not extend WicketTester but WicketTestCase
    omit a1c9bd7  WICKET-6727: more refactoring of the CSP API
    omit ed5c301  WICKET-6727: fix license headers
    omit 4a0bab6  WICKET-6727: refactoring of CSP API
    omit 1125c3d  WICKET-6727: first code drop for configurable CSP
     add bf9732d  WICKET-6730: central SecureRandom setting
     add e44f720  WICKET-6730: Minor code-style improvements
     add 95e5c4d  WICKET-6724: replace empty inline JS in href by # and event.preventDefault
     add 1ece296  WICKET-6730: remove public from methods in interface, in line with other interfaces
     new 0a5cdf1  WICKET-6727: first code drop for configurable CSP
     new 3bed1e9  WICKET-6727: refactoring of CSP API
     new 3afb562  WICKET-6727: fix license headers
     new d1a59a8  WICKET-6727: more refactoring of the CSP API
     new 6392eda  WICKET-6727: do not extend WicketTester but WicketTestCase
     new 8dd6557  WICKET-6727: enable CSP in WebApplication and provide default profiles
     new cfc4615  WICKET-6727: Remove old CspNonceHeaderResponse and update example
     new 1e509dd  WICKET-6727: ContentSecurityPolicyEnforcer must not protect non-webresponses

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (11a2a6e)
            \
             N -- N -- N   refs/heads/csp-configurable (1e509dd)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/wicket/DefaultPageManagerProvider.java  |  2 +-
 .../apache/wicket/ajax/markup/html/AjaxLink.java   |  3 +-
 .../ajax/markup/html/form/AjaxSubmitLink.java      |  3 +-
 .../core/random/DefaultSecureRandomSupplier.java}  | 35 +++++---
 .../wicket/core/random/ISecureRandomSupplier.java  | 62 +++++++++++++++
 .../wicket/csp/ContentSecurityPolicyEnforcer.java  |  2 +-
 .../markup/head/filter/CspNonceHeaderResponse.java | 93 ----------------------
 .../apache/wicket/pageStore/CryptingPageStore.java | 23 +++---
 .../apache/wicket/settings/SecuritySettings.java   | 36 +++++++++
 .../html/ajaxLink/AjaxLinkPageExpectedResult.html  |  4 +-
 .../ajax/markup/html/ajaxLink/AjaxLinkTest.java    |  2 +-
 .../AjaxLinkWithBorderPageExpectedResult.html      |  4 +-
 .../html/ajaxLink/AjaxPage2_ExpectedResult.html    |  4 +-
 .../markup/head/filter/CspNoncePageExpected.html   |  3 +-
 .../head/filter/FilteringHeaderResponseTest.java   | 28 ++++++-
 .../html/basic/SimplePageExpectedResult_13.html    |  2 +-
 .../border/HideableBorderPage_ExpectedResult.html  |  4 +-
 .../panel/InlinePanelPageExpectedResult_7.html     |  4 +-
 .../panel/InlinePanelPageExpectedResult_8.html     |  4 +-
 .../wicket/pageStore/CryptingPageStoreTest.java    | 15 ++--
 .../apache/wicket/examples/csp/CspApplication.java | 35 +-------
 21 files changed, 185 insertions(+), 183 deletions(-)
 copy wicket-core/src/{test/java/org/apache/wicket/resource/InvalidResourceSpecificationExceptionTest.java => main/java/org/apache/wicket/core/random/DefaultSecureRandomSupplier.java} (60%)
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/core/random/ISecureRandomSupplier.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/markup/head/filter/CspNonceHeaderResponse.java


[wicket] 02/08: WICKET-6727: refactoring of CSP API

Posted by pa...@apache.org.
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

commit 3bed1e9e4cd489588f00562557f5332390601340
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Wed Jan 15 11:20:47 2020 +0100

    WICKET-6727: refactoring of CSP API
---
 .../wicket/csp/CSPSettingRequestCycleListener.java | 300 +++++++++------------
 .../csp/CSPSettingRequestCycleListenerTest.java    |  58 ++--
 2 files changed, 160 insertions(+), 198 deletions(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
index a32af03..47c6cdd 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
@@ -5,6 +5,7 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.EnumMap;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
@@ -311,201 +312,176 @@ public class CSPSettingRequestCycleListener implements IRequestCycleListener
 
 	private enum CSPHeaderMode
 	{
-		BLOCKING,
-		REPORT_ONLY;
-	}
+		BLOCKING("Content-Security-Policy"),
+		REPORT_ONLY("Content-Security-Policy-Report-Only");
 
-	private static String HEADER_CSP = "Content-Security-Policy";
+		private final String header;
 
-	private static String HEADER_CSP_REPORT = "Content-Security-Policy-Report-Only";
+		private CSPHeaderMode(String header)
+		{
+			this.header = header;
+		}
 
-	private static String HEADER_CSP_IE = "X-Content-Security-Policy";
+		public String getHeader()
+		{
+			return header;
+		}
 
-	private static String HEADER_CSP_REPORT_IE = "X-Content-Security-Policy-Report-Only";
+		public String getLegacyHeader()
+		{
+			return "X-" + getHeader();
+		}
+	}
 
-	// Directives for the 'Content-Security-Policy' header
-	private Map<CSPDirective, List<CSPRenderable>> blockingDirectives =
-		new EnumMap<>(CSPDirective.class);
+	public class CSPConfiguration
+	{
+		private CSPHeaderMode mode;
 
-	// Directives for the 'Content-Security-Policy-Report-Only' header
-	private Map<CSPDirective, List<CSPRenderable>> reportingDirectives =
-		new EnumMap<>(CSPDirective.class);
+		private Map<CSPDirective, List<CSPRenderable>> directives =
+			new EnumMap<>(CSPDirective.class);
 
-	private Function<Integer, byte[]> randomSupplier;
+		private boolean addLegacyHeaders = false;
 
-	private boolean addLegacyHeaders = false;
+		private CSPConfiguration(CSPHeaderMode mode)
+		{
+			this.mode = mode;
+		}
 
-	public CSPSettingRequestCycleListener()
-	{
-	}
+		public CSPHeaderMode getMode()
+		{
+			return mode;
+		}
 
-	public CSPSettingRequestCycleListener(Function<Integer, byte[]> randomSupplier)
-	{
-		this.randomSupplier = randomSupplier;
-	}
+		/**
+		 * True when legacy headers should be added.
+		 * 
+		 * @return True when legacy headers should be added.
+		 */
+		public boolean isAddLegacyHeaders()
+		{
+			return addLegacyHeaders;
+		}
 
-	/**
-	 * True when legacy headers should be added.
-	 * 
-	 * @return True when legacy headers should be added.
-	 */
-	public boolean isAddLegacyHeaders()
-	{
-		return addLegacyHeaders;
-	}
+		/**
+		 * Enable legacy {@code X-Content-Security-Policy} headers for older browsers, such as IE.
+		 * 
+		 * @param addLegacyHeaders
+		 *            True when the legacy headers should be added.
+		 * @return {@code this} for chaining
+		 */
+		public CSPConfiguration setAddLegacyHeaders(boolean addLegacyHeaders)
+		{
+			this.addLegacyHeaders = addLegacyHeaders;
+			return this;
+		}
 
-	/**
-	 * Enable legacy {@code X-Content-Security-Policy} headers for older browsers, such as IE.
-	 * 
-	 * @param addLegacyHeaders True when the legacy headers should be added.
-	 * @return {@code this} for chaining
-	 */
-	public CSPSettingRequestCycleListener setAddLegacyHeaders(boolean addLegacyHeaders)
-	{
-		this.addLegacyHeaders = addLegacyHeaders;
-		return this;
-	}
+		public CSPConfiguration addDirective(CSPDirective directive, CSPDirectiveSrcValue... values)
+		{
+			for (CSPDirectiveSrcValue value : values)
+			{
+				doAddDirective(directive, value);
+			}
+			return this;
+		}
 
-	/**
-	 * Adds any of the default values to a -src directive for the 'blocking' CSP header
-	 */
-	public CSPSettingRequestCycleListener addBlockingDirective(CSPDirective directive,
-			CSPDirectiveSrcValue... values)
-	{
-		for (CSPDirectiveSrcValue value : values)
+		/**
+		 * Adds any of the default values to the sandbox directive for the 'blocking' CSP header.
+		 * Use {@link #addBlockingDirective(CSPDirective, String...)} with the sandbox
+		 * {@link CSPDirective} and a single empty string (<em>not</em> {@code null}) to add the
+		 * empty sandbox directive.
+		 */
+		public CSPConfiguration addDirective(CSPDirective sandboxDirective,
+				CSPDirectiveSandboxValue... values)
 		{
-			addDirective(directive, value, CSPHeaderMode.BLOCKING);
+			for (CSPDirectiveSandboxValue value : values)
+			{
+				doAddDirective(sandboxDirective, value);
+			}
+			return this;
 		}
-		return this;
-	}
 
-	/**
-	 * Adds any of the default values to the sandbox directive for the 'blocking' CSP header. Use
-	 * {@link #addBlockingDirective(CSPDirective, String...)} with the sandbox {@link CSPDirective}
-	 * and a single empty string (<em>not</em> {@code null}) to add the empty sandbox directive.
-	 */
-	public CSPSettingRequestCycleListener addBlockingDirective(CSPDirective sandboxDirective,
-			CSPDirectiveSandboxValue... values)
-	{
-		for (CSPDirectiveSandboxValue value : values)
+		/**
+		 * Adds any value to a directive for the 'blocking' CSP header. Use
+		 * {@link #addBlockingDirective(CSPDirective, CSPDirectiveSandboxValue...)} and
+		 * {@link #addBlockingDirective(CSPDirective, CSPDirectiveSrcValue...)} for the default
+		 * values for the sandbox and -src directives.
+		 */
+		public CSPConfiguration addDirective(CSPDirective directive, String... values)
 		{
-			addDirective(sandboxDirective, value, CSPHeaderMode.BLOCKING);
+			for (String value : values)
+			{
+				doAddDirective(directive, new FixedCSPDirective(value));
+			}
+			return this;
 		}
-		return this;
-	}
 
-	/**
-	 * Adds any value to a directive for the 'blocking' CSP header. Use
-	 * {@link #addBlockingDirective(CSPDirective, CSPDirectiveSandboxValue...)} and
-	 * {@link #addBlockingDirective(CSPDirective, CSPDirectiveSrcValue...)} for the default values
-	 * for the sandbox and -src directives.
-	 */
-	public CSPSettingRequestCycleListener addBlockingDirective(CSPDirective directive,
-			String... values)
-	{
-		for (String value : values)
+		public boolean isSet()
 		{
-			addDirective(directive, new FixedCSPDirective(value), CSPHeaderMode.BLOCKING);
+			return !directives.isEmpty();
 		}
-		return this;
-	}
 
-	/**
-	 * Adds any of the default values to a -src directive for the 'reporting-only' CSP header
-	 */
-	public CSPSettingRequestCycleListener addReportingDirective(CSPDirective directive,
-			CSPDirectiveSrcValue... values)
-	{
-		for (CSPDirectiveSrcValue value : values)
+		private CSPConfiguration doAddDirective(CSPDirective directive, CSPRenderable value)
 		{
-			addDirective(directive, value, CSPHeaderMode.REPORT_ONLY);
+			// Add backwards compatible frame-src
+			// see http://caniuse.com/#feat=contentsecuritypolicy2
+			if (CSPDirective.CHILD_SRC.equals(directive))
+			{
+				doAddDirective(CSPDirective.FRAME_SRC, value);
+			}
+			List<CSPRenderable> values =
+				directives.computeIfAbsent(directive, x -> new ArrayList<>());
+			directive.checkValueForDirective(value, values);
+			values.add(value);
+			return this;
 		}
-		return this;
-	}
 
-	/**
-	 * Adds any of the default values to the sandbox directive for the 'reporting-only' CSP header.
-	 * Use {@link #addReportingDirective(CSPDirective, String...)} with the sandbox
-	 * {@link CSPDirective} and a single empty string (<em>not</em> {@code null}) to add the empty
-	 * sandbox directive.
-	 */
-	public CSPSettingRequestCycleListener addReportingDirective(CSPDirective sandboxDirective,
-			CSPDirectiveSandboxValue... values)
-	{
-		for (CSPDirectiveSandboxValue value : values)
+		// @returns "key1 value1a value1b; key2 value2a; key3 value3a value3b value3c"
+		public String renderHeaderValue(RequestCycle cycle)
 		{
-			addDirective(sandboxDirective, value, CSPHeaderMode.REPORT_ONLY);
+			return directives.entrySet()
+				.stream()
+				.map(e -> e.getKey().getValue() + " "
+					+ e.getValue()
+						.stream()
+						.map(r -> r.render(CSPSettingRequestCycleListener.this, cycle))
+						.collect(Collectors.joining(" ")))
+				.collect(Collectors.joining("; "));
 		}
-		return this;
 	}
 
-	/**
-	 * Adds any value to a directive for the 'reporting-only' CSP header. Use
-	 * {@link #addReportingDirective(CSPDirective, CSPDirectiveSandboxValue...)} and
-	 * {@link #addReportingDirective(CSPDirective, CSPDirectiveSrcValue...)} for the default values
-	 * for the sandbox and -src directives.
-	 */
-	public CSPSettingRequestCycleListener addReportingDirective(CSPDirective directive,
-			String... values)
+	private Function<Integer, byte[]> randomSupplier;
+
+	private Map<CSPHeaderMode, CSPConfiguration> configs = new HashMap<>();
+
+	public CSPSettingRequestCycleListener()
 	{
-		for (String value : values)
-		{
-			addDirective(directive, new FixedCSPDirective(value), CSPHeaderMode.REPORT_ONLY);
-		}
-		return this;
 	}
 
-	private CSPSettingRequestCycleListener addDirective(CSPDirective directive, CSPRenderable value,
-			CSPHeaderMode mode)
+	public CSPSettingRequestCycleListener(Function<Integer, byte[]> randomSupplier)
 	{
-		// Add backwards compatible frame-src
-		// see http://caniuse.com/#feat=contentsecuritypolicy2
-		if (CSPDirective.CHILD_SRC.equals(directive))
-		{
-			addDirective(CSPDirective.FRAME_SRC, value, mode);
-		}
-		switch (mode)
-		{
-			case BLOCKING:
-				if (blockingDirectives.get(directive) == null)
-				{
-					blockingDirectives.put(directive, new ArrayList<>());
-				}
-				directive.checkValueForDirective(value, blockingDirectives.get(directive));
-				blockingDirectives.get(directive).add(value);
-				return this;
-			case REPORT_ONLY:
-				if (reportingDirectives.get(directive) == null)
-				{
-					reportingDirectives.put(directive, new ArrayList<>());
-				}
-				directive.checkValueForDirective(value, reportingDirectives.get(directive));
-				reportingDirectives.get(directive).add(value);
-				return this;
-			default:
-				throw new IllegalArgumentException("Incorrect CSPHeaderMode!");
-		}
+		this.randomSupplier = randomSupplier;
+	}
+
+	public CSPConfiguration blocking()
+	{
+		return configs.computeIfAbsent(CSPHeaderMode.BLOCKING, CSPConfiguration::new);
+	}
 
+	public CSPConfiguration reporting()
+	{
+		return configs.computeIfAbsent(CSPHeaderMode.REPORT_ONLY, CSPConfiguration::new);
 	}
 
 	@Override
 	public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
 	{
 		WebResponse webResponse = (WebResponse) cycle.getResponse();
-		if (!reportingDirectives.isEmpty())
-		{
-			String reportHeaderValue = getCSPHeaderValue(reportingDirectives, cycle);
-			webResponse.setHeader(HEADER_CSP_REPORT, reportHeaderValue);
-			if (addLegacyHeaders)
-				webResponse.setHeader(HEADER_CSP_REPORT_IE, reportHeaderValue);
-		}
-		if (!blockingDirectives.isEmpty())
-		{
-			String blockHeaderValue = getCSPHeaderValue(blockingDirectives, cycle);
-			webResponse.setHeader(HEADER_CSP, blockHeaderValue);
-			if (addLegacyHeaders)
-				webResponse.setHeader(HEADER_CSP_IE, blockHeaderValue);
-		}
+		configs.values().stream().filter(CSPConfiguration::isSet).forEach(config -> {
+			String headerValue = config.renderHeaderValue(cycle);
+			webResponse.setHeader(config.getMode().getHeader(), headerValue);
+			if (config.isAddLegacyHeaders())
+				webResponse.setHeader(config.getMode().getLegacyHeader(), headerValue);
+		});
 	}
 
 	public String getNonce(RequestCycle cycle)
@@ -518,18 +494,4 @@ public class CSPSettingRequestCycleListener implements IRequestCycleListener
 		}
 		return nonce;
 	}
-
-	// @returns "key1 value1a value1b; key2 value2a; key3 value3a value3b value3c"
-	private String getCSPHeaderValue(Map<CSPDirective, List<CSPRenderable>> directiveValuesMap,
-			RequestCycle cycle)
-	{
-		return directiveValuesMap.entrySet()
-			.stream()
-			.map(e -> e.getKey().getValue() + " "
-				+ e.getValue()
-					.stream()
-					.map(r -> r.render(this, cycle))
-					.collect(Collectors.joining(" ")))
-			.collect(Collectors.joining("; "));
-	}
 }
diff --git a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
index 8e00759..f347f8a 100644
--- a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
@@ -50,7 +50,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(DEFAULT_SRC, (String) null);
+			cspListener.blocking().addDirective(DEFAULT_SRC, (String) null);
 		});
 	}
 
@@ -59,7 +59,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(DEFAULT_SRC, "");
+			cspListener.blocking().addDirective(DEFAULT_SRC, "");
 		});
 	}
 
@@ -72,7 +72,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(DEFAULT_SRC, "abc?^()-_\'xyz");
+			cspListener.blocking().addDirective(DEFAULT_SRC, "abc?^()-_\'xyz");
 		});
 	}
 
@@ -85,7 +85,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(DEFAULT_SRC, SELF, NONE);
+			cspListener.blocking().addDirective(DEFAULT_SRC, SELF, NONE);
 		});
 	}
 
@@ -98,7 +98,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(DEFAULT_SRC, NONE, SELF);
+			cspListener.blocking().addDirective(DEFAULT_SRC, NONE, SELF);
 		});
 	}
 
@@ -110,9 +110,9 @@ public class CSPSettingRequestCycleListenerTest
 	public void testMultipleSrcInputWithStarIsRejected1()
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
-		cspListener.addBlockingDirective(DEFAULT_SRC, SELF);
+		cspListener.blocking().addDirective(DEFAULT_SRC, SELF);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(DEFAULT_SRC, WILDCARD);
+			cspListener.blocking().addDirective(DEFAULT_SRC, WILDCARD);
 		});
 	}
 
@@ -124,9 +124,9 @@ public class CSPSettingRequestCycleListenerTest
 	public void testMultipleSrcInputWithStarIsRejected2()
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
-		cspListener.addBlockingDirective(DEFAULT_SRC, WILDCARD);
+		cspListener.blocking().addDirective(DEFAULT_SRC, WILDCARD);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(DEFAULT_SRC, SELF);
+			cspListener.blocking().addDirective(DEFAULT_SRC, SELF);
 		});
 	}
 
@@ -135,7 +135,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(DEFAULT_SRC, ALLOW_FORMS);
+			cspListener.blocking().addDirective(DEFAULT_SRC, ALLOW_FORMS);
 		});
 	}
 
@@ -144,7 +144,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(SANDBOX, SELF);
+			cspListener.blocking().addDirective(SANDBOX, SELF);
 		});
 	}
 
@@ -153,7 +153,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(SANDBOX, (String) null);
+			cspListener.blocking().addDirective(SANDBOX, (String) null);
 		});
 	}
 
@@ -161,7 +161,7 @@ public class CSPSettingRequestCycleListenerTest
 	public void testEmptySandboxInputIsAccepted()
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
-		cspListener.addBlockingDirective(SANDBOX, CSPDirectiveSandboxValue.EMPTY);
+		cspListener.blocking().addDirective(SANDBOX, CSPDirectiveSandboxValue.EMPTY);
 	}
 
 	@Test
@@ -169,7 +169,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(SANDBOX, "abcxyz");
+			cspListener.blocking().addDirective(SANDBOX, "abcxyz");
 		});
 	}
 
@@ -177,9 +177,9 @@ public class CSPSettingRequestCycleListenerTest
 	public void testMultipleSandboxInputWithEmptyStringIsRejected1()
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
-		cspListener.addBlockingDirective(SANDBOX, ALLOW_FORMS);
+		cspListener.blocking().addDirective(SANDBOX, ALLOW_FORMS);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(SANDBOX, EMPTY);
+			cspListener.blocking().addDirective(SANDBOX, EMPTY);
 		});
 	}
 
@@ -187,9 +187,9 @@ public class CSPSettingRequestCycleListenerTest
 	public void testMultipleSandboxInputWithEmptyStringIsRejected2()
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
-		cspListener.addBlockingDirective(SANDBOX, EMPTY);
+		cspListener.blocking().addDirective(SANDBOX, EMPTY);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(SANDBOX, ALLOW_FORMS);
+			cspListener.blocking().addDirective(SANDBOX, ALLOW_FORMS);
 		});
 	}
 
@@ -198,7 +198,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(REPORT_URI, (String) null);
+			cspListener.blocking().addDirective(REPORT_URI, (String) null);
 		});
 	}
 
@@ -207,7 +207,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(REPORT_URI, "");
+			cspListener.blocking().addDirective(REPORT_URI, "");
 		});
 	}
 
@@ -216,7 +216,7 @@ public class CSPSettingRequestCycleListenerTest
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
-			cspListener.addBlockingDirective(REPORT_URI, "abc?^()-_\'xyz");
+			cspListener.blocking().addDirective(REPORT_URI, "abc?^()-_\'xyz");
 		});
 	}
 
@@ -240,9 +240,9 @@ public class CSPSettingRequestCycleListenerTest
 			{
 				final CSPDirectiveSrcValue cspDirectiveValue =
 					CSPDirectiveSrcValue.values()[i % cspDirectiveSrcValueCount];
-				cspListener.addBlockingDirective(cspDirective, cspDirectiveValue);
+				cspListener.blocking().addDirective(cspDirective, cspDirectiveValue);
 
-				cspListener.addReportingDirective(cspDirective, cspDirectiveValue);
+				cspListener.reporting().addDirective(cspDirective, cspDirectiveValue);
 			}
 		}
 
@@ -258,8 +258,8 @@ public class CSPSettingRequestCycleListenerTest
 	public void testCSPReportUriDirectiveSetCorrectly()
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
-		cspListener.addBlockingDirective(REPORT_URI, "http://report.example.com");
-		cspListener.addReportingDirective(REPORT_URI, "/example-report-uri");
+		cspListener.blocking().addDirective(REPORT_URI, "http://report.example.com");
+		cspListener.reporting().addDirective(REPORT_URI, "/example-report-uri");
 
 		StringBuffer headerErrors = checkHeaders(cspListener);
 
@@ -280,8 +280,8 @@ public class CSPSettingRequestCycleListenerTest
 			if (cspDirectiveValue.equals(CSPDirectiveSandboxValue.EMPTY))
 				continue;
 			
-			cspListener.addBlockingDirective(SANDBOX, cspDirectiveValue);
-			cspListener.addReportingDirective(SANDBOX, cspDirectiveValue);
+			cspListener.blocking().addDirective(SANDBOX, cspDirectiveValue);
+			cspListener.reporting().addDirective(SANDBOX, cspDirectiveValue);
 		}
 
 		StringBuffer headerErrors = checkHeaders(cspListener);
@@ -301,8 +301,8 @@ public class CSPSettingRequestCycleListenerTest
 	public void testChildSrcDirectiveAlsoSetsFrameSrcDirective()
 	{
 		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
-		cspListener.addBlockingDirective(CHILD_SRC, SELF);
-		cspListener.addReportingDirective(CHILD_SRC, SELF);
+		cspListener.blocking().addDirective(CHILD_SRC, SELF);
+		cspListener.reporting().addDirective(CHILD_SRC, SELF);
 		StringBuffer headerErrors = checkHeaders(cspListener);
 
 		if (headerErrors.length() > 0)


[wicket] 06/08: WICKET-6727: enable CSP in WebApplication and provide default profiles

Posted by pa...@apache.org.
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

commit 8dd65578d8e3bbf6d0cf7ccd9e11b2865811190a
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Fri Jan 17 21:52:50 2020 +0100

    WICKET-6727: enable CSP in WebApplication and provide default profiles
---
 .../wicket/csp/CSPDirectiveSandboxValue.java       |  2 +-
 .../apache/wicket/csp/CSPDirectiveSrcValue.java    |  4 +-
 .../apache/wicket/csp/CSPHeaderConfiguration.java  | 72 +++++++++++++++++-
 .../java/org/apache/wicket/csp/CSPRenderable.java  |  4 +-
 ...ner.java => ContentSecurityPolicyEnforcer.java} | 46 ++++++++++--
 .../csp/CspNonceHeaderResponseDecorator.java       | 29 ++++----
 .../org/apache/wicket/csp/FixedCSPDirective.java   |  2 +-
 .../wicket/protocol/http/WebApplication.java       | 36 +++++++++
 .../csp/CSPSettingRequestCycleListenerTest.java    | 86 +++++++++++-----------
 9 files changed, 208 insertions(+), 73 deletions(-)

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 8164b31..8236ee6 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
@@ -42,7 +42,7 @@ public enum CSPDirectiveSandboxValue implements CSPRenderable
 	}
 
 	@Override
-	public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+	public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
 	{
 		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 b4a06db..0bb4a4e 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
@@ -32,7 +32,7 @@ public enum CSPDirectiveSrcValue implements CSPRenderable
 	NONCE("'nonce-%1$s'")
 	{
 		@Override
-		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+		public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
 		{
 			return String.format(getValue(), listener.getNonce(cycle));
 		}
@@ -46,7 +46,7 @@ public enum CSPDirectiveSrcValue implements CSPRenderable
 	}
 
 	@Override
-	public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+	public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
 	{
 		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 1abd1a5..91521ba 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
@@ -16,6 +16,21 @@
  */
 package org.apache.wicket.csp;
 
+import static org.apache.wicket.csp.CSPDirective.CHILD_SRC;
+import static org.apache.wicket.csp.CSPDirective.CONNECT_SRC;
+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.SCRIPT_SRC;
+import static org.apache.wicket.csp.CSPDirective.STYLE_SRC;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.NONCE;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.NONE;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.SELF;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.STRICT_DYNAMIC;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.UNSAFE_EVAL;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.UNSAFE_INLINE;
+
 import java.util.ArrayList;
 import java.util.EnumMap;
 import java.util.List;
@@ -33,10 +48,50 @@ public class CSPHeaderConfiguration
 	private Map<CSPDirective, List<CSPRenderable>> directives = new EnumMap<>(CSPDirective.class);
 
 	private boolean addLegacyHeaders = false;
+	
+	private boolean nonceEnabled = false;
 
 	public CSPHeaderConfiguration()
 	{
 	}
+	
+	public CSPHeaderConfiguration disabled() {
+		return clear();
+	}
+
+	public CSPHeaderConfiguration unsafeInline()
+	{
+		return clear().addDirective(DEFAULT_SRC, NONE)
+			.addDirective(STYLE_SRC, SELF, UNSAFE_INLINE)
+			.addDirective(SCRIPT_SRC, SELF, UNSAFE_INLINE, UNSAFE_EVAL)
+			.addDirective(IMG_SRC, SELF)
+			.addDirective(FONT_SRC, SELF)
+			.addDirective(CHILD_SRC, SELF)
+			.addDirective(MANIFEST_SRC, SELF)
+			.addDirective(CONNECT_SRC, SELF);
+	}
+	
+	public CSPHeaderConfiguration strict()
+	{
+		return clear().addDirective(DEFAULT_SRC, NONE)
+			.addDirective(STYLE_SRC, NONCE)
+			.addDirective(SCRIPT_SRC, STRICT_DYNAMIC, NONCE)
+			.addDirective(IMG_SRC, SELF)
+			.addDirective(FONT_SRC, SELF)
+			.addDirective(CHILD_SRC, SELF)
+			.addDirective(MANIFEST_SRC, SELF)
+			.addDirective(CONNECT_SRC, SELF);
+	}
+
+	/**
+	 * True when the {@link CSPDirectiveSrcValue#NONCE} is used in one of the directives.
+	 * 
+	 * @return When any of the directives contains a nonce.
+	 */
+	public boolean isNonceEnabled()
+	{
+		return nonceEnabled;
+	}
 
 	/**
 	 * True when legacy headers should be added.
@@ -104,6 +159,18 @@ public class CSPHeaderConfiguration
 		return !directives.isEmpty();
 	}
 
+	/**
+	 * Removes all CSP directives from the configuration.
+	 * 
+	 * @return {@code this} for chaining.
+	 */
+	public CSPHeaderConfiguration clear()
+	{
+		directives.clear();
+		nonceEnabled = false;
+		return this;
+	}
+
 	@SuppressWarnings("deprecation")
 	private CSPHeaderConfiguration doAddDirective(CSPDirective directive, CSPRenderable value)
 	{
@@ -116,6 +183,7 @@ 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;
 	}
 
@@ -124,12 +192,12 @@ public class CSPHeaderConfiguration
 	 * in the form {@code "key1 value1a value1b; key2 value2a; key3 value3a value3b value3c"}.
 	 * 
 	 * @param listener
-	 *            The {@link CSPSettingRequestCycleListener} that renders the header.
+	 *            The {@link ContentSecurityPolicyEnforcer} that renders the header.
 	 * @param cycle
 	 *            The current {@link RequestCycle}.
 	 * @return the rendered header.
 	 */
-	public String renderHeaderValue(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+	public String renderHeaderValue(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
 	{
 		return directives.entrySet()
 			.stream()
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 05badc9..0b57567 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
@@ -33,10 +33,10 @@ public interface CSPRenderable
 	 * Renders the value that should be put in the CSP header.
 	 * 
 	 * @param listener
-	 *            The {@link CSPSettingRequestCycleListener} that renders this value.
+	 *            The {@link ContentSecurityPolicyEnforcer} that renders this value.
 	 * @param cycle
 	 *            The current {@link RequestCycle}.
 	 * @return The rendered value.
 	 */
-	public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle);
+	public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle);
 }
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java b/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicyEnforcer.java
similarity index 77%
rename from wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
rename to wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicyEnforcer.java
index 85cff77..4d2677c 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/ContentSecurityPolicyEnforcer.java
@@ -18,6 +18,7 @@ package org.apache.wicket.csp;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Predicate;
 
 import org.apache.wicket.Application;
 import org.apache.wicket.MetaDataKey;
@@ -28,6 +29,7 @@ import org.apache.wicket.request.IRequestHandlerDelegate;
 import org.apache.wicket.request.cycle.IRequestCycleListener;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.request.http.WebResponse;
+import org.apache.wicket.settings.SecuritySettings;
 import org.apache.wicket.util.lang.Args;
 
 /**
@@ -67,7 +69,7 @@ import org.apache.wicket.util.lang.Args;
  * @author Sven Haster
  * @author Emond Papegaaij
  */
-public class CSPSettingRequestCycleListener implements IRequestCycleListener
+public class ContentSecurityPolicyEnforcer implements IRequestCycleListener
 {
 	public static MetaDataKey<String> NONCE_KEY = new MetaDataKey<>()
 	{
@@ -77,8 +79,10 @@ public class CSPSettingRequestCycleListener implements IRequestCycleListener
 	private final Application application;
 	
 	private Map<CSPHeaderMode, CSPHeaderConfiguration> configs = new HashMap<>();
+	
+	private Predicate<IPageClassRequestHandler> protectedPageFilter = handler -> true;
 
-	public CSPSettingRequestCycleListener(Application application)
+	public ContentSecurityPolicyEnforcer(Application application)
 	{
 		this.application = Args.notNull(application, "application");
 	}
@@ -92,6 +96,20 @@ public class CSPSettingRequestCycleListener implements IRequestCycleListener
 	{
 		return configs.computeIfAbsent(CSPHeaderMode.REPORT_ONLY, x -> new CSPHeaderConfiguration());
 	}
+	
+	/**
+	 * Sets the predicate that determines which requests must be protected by the CSP. When the
+	 * predicate evaluates to false, the request for the page will not be protected.
+	 * 
+	 * @param protectedPageFilter
+	 * @return {@code this} for chaining.
+	 */
+	public ContentSecurityPolicyEnforcer
+			setProtectedPageFilter(Predicate<IPageClassRequestHandler> protectedPageFilter)
+	{
+		this.protectedPageFilter = protectedPageFilter;
+		return this;
+	}
 
 	protected boolean mustProtect(IRequestHandler handler)
 	{
@@ -103,9 +121,10 @@ public class CSPSettingRequestCycleListener implements IRequestCycleListener
 		}
 		return !(handler instanceof BufferedResponseRequestHandler);
 	}
-	
-	protected boolean mustProtectPageRequest(IPageClassRequestHandler handler) {
-		return true;
+
+	protected boolean mustProtectPageRequest(IPageClassRequestHandler handler)
+	{
+		return protectedPageFilter.test(handler);
 	}
 
 	@Override
@@ -124,15 +143,30 @@ public class CSPSettingRequestCycleListener implements IRequestCycleListener
 				webResponse.setHeader(mode.getLegacyHeader(), headerValue);
 		});
 	}
+	
+	/**
+	 * Returns true if any of the headers includes a directive with a nonce.
+	 * 
+	 * @return If a nonce is used in the CSP.
+	 */
+	public boolean isNonceEnabled()
+	{
+		return configs.values().stream().anyMatch(CSPHeaderConfiguration::isNonceEnabled);
+	}
 
 	public String getNonce(RequestCycle cycle)
 	{
 		String nonce = cycle.getMetaData(NONCE_KEY);
 		if (nonce == null)
 		{
-			nonce = application.getSecuritySettings().getRandomSupplier().getRandomBase64(12);
+			nonce = getSecuritySettings().getRandomSupplier().getRandomBase64(12);
 			cycle.setMetaData(NONCE_KEY, nonce);
 		}
 		return nonce;
 	}
+
+	private SecuritySettings getSecuritySettings()
+	{
+		return application.getSecuritySettings();
+	}
 }
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
index da87059..425c9db 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
@@ -16,28 +16,22 @@
  */
 package org.apache.wicket.csp;
 
-import org.apache.wicket.Application;
 import org.apache.wicket.markup.head.AbstractCspHeaderItem;
 import org.apache.wicket.markup.head.HeaderItem;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.IWrappedHeaderItem;
-import org.apache.wicket.markup.head.ResourceAggregator;
 import org.apache.wicket.markup.html.DecoratingHeaderResponse;
 import org.apache.wicket.request.cycle.RequestCycle;
 
 /**
- * Add a <em>Content Security Policy<em> (CSP) nonce to all {@link AbstractCspHeaderItem}s.
- * <p>
- * Note: please don't forget to wrap with {@link ResourceAggregator} when setting it up with
- * {@link Application#setHeaderResponseDecorator}, otherwise dependencies will not be rendered.
- *
- * @see AbstractCspHeaderItem
+ * 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
 {
-	private CSPSettingRequestCycleListener listener;
+	private ContentSecurityPolicyEnforcer listener;
 
-	public CspNonceHeaderResponseDecorator(IHeaderResponse real, CSPSettingRequestCycleListener listener)
+	public CspNonceHeaderResponseDecorator(IHeaderResponse real, ContentSecurityPolicyEnforcer listener)
 	{
 		super(real);
 
@@ -47,14 +41,17 @@ public class CspNonceHeaderResponseDecorator extends DecoratingHeaderResponse
 	@Override
 	public void render(HeaderItem item)
 	{
-		while (item instanceof IWrappedHeaderItem)
+		if (listener.isNonceEnabled())
 		{
-			item = ((IWrappedHeaderItem) item).getWrapped();
-		}
+			while (item instanceof IWrappedHeaderItem)
+			{
+				item = ((IWrappedHeaderItem) item).getWrapped();
+			}
 
-		if (item instanceof AbstractCspHeaderItem)
-		{
-			((AbstractCspHeaderItem) item).setNonce(listener.getNonce(RequestCycle.get()));
+			if (item instanceof AbstractCspHeaderItem)
+			{
+				((AbstractCspHeaderItem) item).setNonce(listener.getNonce(RequestCycle.get()));
+			}
 		}
 
 		super.render(item);
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java b/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java
index 25886d6..a091b6a 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java
@@ -43,7 +43,7 @@ public class FixedCSPDirective implements CSPRenderable
 	}
 
 	@Override
-	public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+	public String render(ContentSecurityPolicyEnforcer listener, RequestCycle cycle)
 	{
 		return value;
 	}
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 0f3177f..1d72fff 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
@@ -40,6 +40,9 @@ import org.apache.wicket.core.request.mapper.PackageMapper;
 import org.apache.wicket.core.request.mapper.ResourceMapper;
 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.markup.MarkupType;
 import org.apache.wicket.markup.head.CssHeaderItem;
 import org.apache.wicket.markup.head.JavaScriptHeaderItem;
@@ -144,6 +147,8 @@ public abstract class WebApplication extends Application
 	 * runtime.
 	 */
 	private RuntimeConfigurationType configurationType;
+	
+	private ContentSecurityPolicyEnforcer cspEnforcer;
 
 	/**
 	 * Covariant override for easy getting the current {@link WebApplication} without having to cast
@@ -759,6 +764,12 @@ 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();
 	}
@@ -1070,6 +1081,31 @@ public abstract class WebApplication extends Application
 		}
 		return filterFactoryManager;
 	}
+	
+	/**
+	 * Builds the {@link ContentSecurityPolicyEnforcer} to be used for this application. Override
+	 * this method to provider your own implementation.
+	 * 
+	 * @return The newly created CSP enforcer.
+	 */
+	protected ContentSecurityPolicyEnforcer newCspEnforcer()
+	{
+		return new ContentSecurityPolicyEnforcer(this);
+	}
+
+	/**
+	 * Returns the {@link ContentSecurityPolicyEnforcer} for this application. See
+	 * {@link ContentSecurityPolicyEnforcer} and {@link CSPHeaderConfiguration} for instructions on
+	 * configuring the CSP for your specific needs.
+	 * 
+	 * @return The {@link ContentSecurityPolicyEnforcer} for this application.
+	 * @see ContentSecurityPolicyEnforcer
+	 * @see CSPHeaderConfiguration
+	 */
+	public ContentSecurityPolicyEnforcer getCsp()
+	{
+		return cspEnforcer;
+	}
 
 	/**
 	 * If true, auto label css classes such as {@code error} and {@code required} will be updated
diff --git a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
index d4893cd..5a7be31 100644
--- a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
@@ -62,8 +62,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testNullSrcInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, (String) null);
 		});
@@ -72,8 +72,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testEmptySrcInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, "");
 		});
@@ -86,8 +86,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testInvalidSrcInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, "abc?^()-_\'xyz");
 		});
@@ -100,8 +100,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testMultipleSrcInputWithNoneIsRejected1()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, SELF, NONE);
 		});
@@ -114,8 +114,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testMultipleSrcInputWithNoneIsRejected2()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, NONE, SELF);
 		});
@@ -128,8 +128,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testMultipleSrcInputWithStarIsRejected1()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		cspListener.blocking().addDirective(DEFAULT_SRC, SELF);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, WILDCARD);
@@ -143,8 +143,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testMultipleSrcInputWithStarIsRejected2()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		cspListener.blocking().addDirective(DEFAULT_SRC, WILDCARD);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, SELF);
@@ -154,8 +154,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testWrongSrcInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, ALLOW_FORMS);
 		});
@@ -164,8 +164,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testWrongSandboxInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, SELF);
 		});
@@ -174,8 +174,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testNullSandboxInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, (String) null);
 		});
@@ -184,16 +184,16 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testEmptySandboxInputIsAccepted()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		cspListener.blocking().addDirective(SANDBOX, CSPDirectiveSandboxValue.EMPTY);
 	}
 
 	@Test
 	public void testInvalidSandboxInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, "abcxyz");
 		});
@@ -202,8 +202,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testMultipleSandboxInputWithEmptyStringIsRejected1()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		cspListener.blocking().addDirective(SANDBOX, ALLOW_FORMS);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, EMPTY);
@@ -213,8 +213,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testMultipleSandboxInputWithEmptyStringIsRejected2()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		cspListener.blocking().addDirective(SANDBOX, EMPTY);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, ALLOW_FORMS);
@@ -224,8 +224,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testNullReportUriInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(REPORT_URI, (String) null);
 		});
@@ -234,8 +234,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testEmptyReportUriInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(REPORT_URI, "");
 		});
@@ -244,8 +244,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testInvalidReportUriInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(REPORT_URI, "abc?^()-_\'xyz");
 		});
@@ -254,8 +254,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testAllCSPSrcDefaultEnumsAreSetCorrectly() throws NoSuchAlgorithmException
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 
 		final int cspDirectiveCount = CSPDirective.values().length;
 		final int cspDirectiveSrcValueCount = CSPDirectiveSrcValue.values().length;
@@ -284,8 +284,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testCSPReportUriDirectiveSetCorrectly()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		cspListener.blocking().addDirective(REPORT_URI, "http://report.example.com");
 		cspListener.reporting().addDirective(REPORT_URI, "/example-report-uri");
 
@@ -300,8 +300,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testCSPSandboxDirectiveSetCorrectly()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		final int cspSandboxDirectiveValueCount = CSPDirectiveSandboxValue.values().length;
 		for (int i = 0; i < cspSandboxDirectiveValueCount; i++)
 		{
@@ -329,8 +329,8 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 	@Test
 	public void testChildSrcDirectiveAlsoSetsFrameSrcDirective()
 	{
-		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(tester.getApplication());
+		ContentSecurityPolicyEnforcer cspListener =
+			new ContentSecurityPolicyEnforcer(tester.getApplication());
 		cspListener.blocking().addDirective(CHILD_SRC, SELF);
 		cspListener.reporting().addDirective(CHILD_SRC, SELF);
 		StringBuffer headerErrors = checkHeaders(cspListener);
@@ -341,7 +341,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 		}
 	}
 
-	private StringBuffer checkHeaders(CSPSettingRequestCycleListener cspListener)
+	private StringBuffer checkHeaders(ContentSecurityPolicyEnforcer cspListener)
 	{
 		StringBuffer headerErrors = new StringBuffer();
 		wicketTester.getRequestCycle().getListeners().add(cspListener);


[wicket] 07/08: WICKET-6727: Remove old CspNonceHeaderResponse and update example

Posted by pa...@apache.org.
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

commit cfc46152d08d89d259e6666d3f0895e7b63266a0
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Fri Jan 17 22:14:48 2020 +0100

    WICKET-6727: Remove old CspNonceHeaderResponse and update example
---
 .../markup/head/filter/CspNonceHeaderResponse.java | 93 ----------------------
 .../markup/head/filter/CspNoncePageExpected.html   |  3 +-
 .../head/filter/FilteringHeaderResponseTest.java   | 28 ++++++-
 .../apache/wicket/examples/csp/CspApplication.java | 35 +-------
 4 files changed, 27 insertions(+), 132 deletions(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/CspNonceHeaderResponse.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/CspNonceHeaderResponse.java
deleted file mode 100644
index f2a9ce1..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/filter/CspNonceHeaderResponse.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.markup.head.filter;
-
-import org.apache.wicket.Application;
-import org.apache.wicket.markup.head.AbstractCspHeaderItem;
-import org.apache.wicket.markup.head.HeaderItem;
-import org.apache.wicket.markup.head.IHeaderResponse;
-import org.apache.wicket.markup.head.IWrappedHeaderItem;
-import org.apache.wicket.markup.head.MetaDataHeaderItem;
-import org.apache.wicket.markup.head.ResourceAggregator;
-import org.apache.wicket.markup.html.DecoratingHeaderResponse;
-
-/**
- * Add a <em>Content Security Policy<em> (CSP) nonce to all {@link AbstractCspHeaderItem}s.
- * <p>
- * Note: please don't forget to wrap with {@link ResourceAggregator} when setting it up with
- * {@link Application#setHeaderResponseDecorator}, otherwise dependencies will not be rendered.
- * 
- * @see AbstractCspHeaderItem
- */
-public class CspNonceHeaderResponse extends DecoratingHeaderResponse
-{
-	private static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy";
-
-	/**
-	 * Has the <em>Content-Security-Policy</em> header been rendered once.
-	 */
-	private boolean policyRendered = false;
-
-	private String nonce;
-
-	public CspNonceHeaderResponse(IHeaderResponse real, String nonce)
-	{
-		super(real);
-
-		this.nonce = nonce;
-	}
-
-	@Override
-	public void render(HeaderItem item)
-	{
-		while (item instanceof IWrappedHeaderItem)
-		{
-			item = ((IWrappedHeaderItem)item).getWrapped();
-		}
-
-		if (item instanceof AbstractCspHeaderItem)
-		{
-			if (policyRendered == false)
-			{
-				policyRendered = true;
-
-				String policy = getContentSecurityPolicy(nonce);
-
-				super.render(MetaDataHeaderItem.forHttpEquiv(CONTENT_SECURITY_POLICY, policy));
-			}
-
-			((AbstractCspHeaderItem)item).setNonce(nonce);
-		}
-		
-		super.render(item);
-	}
-
-	/**
-	 * Get the <em>Content-Security-Policy</em> (CSP).
-	 * <p>
-	 * There is a variety of CSP configurations, this default implementation uses the nonce for scripts and styles
-	 * and allows <code>strict-dynamic</code>s (needed for Wicket Ajax).
-	 * 
-	 * @param nonce
-	 *            the nonce
-	 * @return content security policy
-	 */
-	protected String getContentSecurityPolicy(String nonce)
-	{
-		return String.format("script-src 'strict-dynamic' 'nonce-%1$s'; style-src 'nonce-%1$s';", nonce);
-	}
-}
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/CspNoncePageExpected.html b/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/CspNoncePageExpected.html
index 2c44fc9..6cf3902 100644
--- a/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/CspNoncePageExpected.html
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/head/filter/CspNoncePageExpected.html
@@ -1,6 +1,5 @@
 <html xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd" >
-    <head><meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' 'nonce-NONCE'; style-src 'nonce-NONCE';" />
-<script type="text/javascript" src="../resource/org.apache.wicket.resource.JQueryResourceReference/jquery/jquery-3.4.1.js" nonce="NONCE"></script>
+    <head><script type="text/javascript" src="../resource/org.apache.wicket.resource.JQueryResourceReference/jquery/jquery-3.4.1.js" nonce="NONCE"></script>
 <script type="text/javascript" src="../resource/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/res/js/wicket-ajax-jquery.js" nonce="NONCE"></script>
 <script type="text/javascript" id="wicket-ajax-debug-enable" nonce="NONCE">
 /*<![CDATA[*/
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 63bbadf..2d5c467 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,12 +20,17 @@ 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.Response;
+import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.response.StringResponse;
 import org.apache.wicket.util.tester.WicketTestCase;
 import org.junit.jupiter.api.Test;
@@ -37,6 +42,25 @@ import org.junit.jupiter.api.Test;
  */
 class FilteringHeaderResponseTest extends WicketTestCase
 {
+	@Override
+	protected WebApplication newApplication()
+	{
+		return new MockApplication()
+		{
+			@Override
+			protected ContentSecurityPolicyEnforcer newCspEnforcer()
+			{
+				return new ContentSecurityPolicyEnforcer(this)
+				{
+					@Override
+					public String getNonce(RequestCycle cycle)
+					{
+						return "NONCE";
+					}
+				};
+			}
+		};
+	}
 
 	@Test
 	void footerDependsOnHeadItem() throws Exception
@@ -96,9 +120,7 @@ class FilteringHeaderResponseTest extends WicketTestCase
 	@Test
 	void nonce() throws Exception
 	{
-		tester.getApplication()
-			.getHeaderResponseDecorators()
-			.add(response -> new CspNonceHeaderResponse(response, "NONCE"));
+		tester.getApplication().getCsp().blocking().strict();
 		executeTest(CspNoncePage.class, "CspNoncePageExpected.html");
 	}
 }
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/csp/CspApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/csp/CspApplication.java
index e8eff7e..ae9187d 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/csp/CspApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/csp/CspApplication.java
@@ -16,24 +16,11 @@
  */
 package org.apache.wicket.examples.csp;
 
-import java.util.Base64;
-import java.util.concurrent.ThreadLocalRandom;
-
-import org.apache.wicket.MetaDataKey;
 import org.apache.wicket.Page;
-import org.apache.wicket.Session;
 import org.apache.wicket.examples.WicketExampleApplication;
-import org.apache.wicket.markup.head.ResourceAggregator;
-import org.apache.wicket.markup.head.filter.CspNonceHeaderResponse;
 
 public class CspApplication extends WicketExampleApplication
 {
-	private static final int NONCE_LENGTH = 24;
-	
-	public static MetaDataKey<String> NONCE_KEY = new MetaDataKey<String>()
-	{
-	};
-
 	@Override
 	public Class<? extends Page> getHomePage()
 	{
@@ -45,28 +32,8 @@ public class CspApplication extends WicketExampleApplication
 	{
 		super.init();
 
-		getHeaderResponseDecorators().add(response -> new CspNonceHeaderResponse(response, getNonce()));
+		getCsp().blocking().strict();
 		
 		mountPage("noncedemo", NonceDemoPage.class);
 	}
-
-	protected static String generateNonce()
-	{
-		byte[] randomBytes = new byte[NONCE_LENGTH];
-		ThreadLocalRandom.current().nextBytes(randomBytes);
-		return Base64.getUrlEncoder().encodeToString(randomBytes);
-	}
-
-	public static String getNonce()
-	{
-		Session session = Session.get();
-		session.bind();
-		String nonce = session.getMetaData(NONCE_KEY);
-		if (nonce == null)
-		{
-			nonce = generateNonce();
-			session.setMetaData(NONCE_KEY, nonce);
-		}
-		return nonce;
-	}
 }


[wicket] 01/08: WICKET-6727: first code drop for configurable CSP

Posted by pa...@apache.org.
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

commit 0a5cdf17b06b231074788608196c0c7b768f986f
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Mon Jan 13 22:04:00 2020 +0100

    WICKET-6727: first code drop for configurable CSP
---
 .../wicket/csp/CSPSettingRequestCycleListener.java | 535 +++++++++++++++++++++
 .../csp/CspNonceHeaderResponseDecorator.java       |  56 +++
 .../csp/CSPSettingRequestCycleListenerTest.java    | 448 +++++++++++++++++
 3 files changed, 1039 insertions(+)

diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
new file mode 100644
index 0000000..a32af03
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
@@ -0,0 +1,535 @@
+package org.apache.wicket.csp;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.cycle.IRequestCycleListener;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebResponse;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * An {@link IRequestCycleListener} that adds {@code Content-Security-Policy} and/or
+ * {@code Content-Security-Policy-Report-Only} headers based on the supplied configuration.
+ *
+ * See also the {@code CSPSettingRequestCycleListenerTest}.
+ *
+ * Example usage:
+ *
+ * <pre>
+ * {@code
+ *      myApplication.getRequestCycleListeners().add(
+ * 			new CSPSettingRequestCycleListener()
+ * 				.addBlockingDirective(CSPDirective.DEFAULT_SRC, CSPDirectiveSrcValue.NONE)
+ * 				.addBlockingDirective(CSPDirective.SCRIPT_SRC, CSPDirectiveSrcValue.SELF)
+ * 				.addBlockingDirective(CSPDirective.IMG_SRC, CSPDirectiveSrcValue.SELF)
+ * 				.addBlockingDirective(CSPDirective.FONT_SRC, CSPDirectiveSrcValue.SELF));
+ *
+ * 		 myApplication.getRequestCycleListeners().add(
+ * 			new CSPSettingRequestCycleListener()
+ * 				.addReportingDirective(CSPDirective.DEFAULT_SRC, CSPDirectiveSrcValue.NONE)
+ * 				.addReportingDirective(CSPDirective.IMG_SRC, CSPDirectiveSrcValue.SELF)
+ * 				.addReportingDirective(CSPDirective.FONT_SRC, CSPDirectiveSrcValue.SELF)
+ * 				.addReportingDirective(CSPDirective.SCRIPT_SRC, CSPDirectiveSrcValue.SELF));
+ * 	}
+ * </pre>
+ *
+ * {@code frame-src} has been deprecated since CSP 2.0 and replaced by {@code child-src}. Some
+ * browsers do not yet support {@code child-src} and expect {@code frame-src} instead. When
+ * {@code child-src} is added, a matching {@code frame-src} is added automatically for
+ * compatibility.
+ *
+ * @see "http://www.w3.org/TR/CSP2/"
+ * @see "https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives"
+ *
+ * @author Sven Haster
+ * @author Emond Papegaaij
+ */
+public class CSPSettingRequestCycleListener implements IRequestCycleListener
+{
+	public static MetaDataKey<String> NONCE_KEY = new MetaDataKey<>()
+	{
+		private static final long serialVersionUID = 1L;
+	};
+
+	public static interface CSPRenderable
+	{
+		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle);
+	}
+
+	private static final class FixedCSPDirective implements CSPRenderable
+	{
+		private String value;
+
+		public FixedCSPDirective(String value)
+		{
+			if (Strings.isEmpty(value))
+				throw new IllegalArgumentException(
+					"CSP directive cannot have empty or null values");
+			this.value = value;
+		}
+
+		@Override
+		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+		{
+			return value;
+		}
+	}
+
+	/**
+	 * An enum holding the default values for -src directives including the mandatory single quotes
+	 */
+	public enum CSPDirectiveSrcValue implements CSPRenderable
+	{
+		NONE("'none'"),
+		WILDCARD("*"),
+		SELF("'self'"),
+		UNSAFE_INLINE("'unsafe-inline'"),
+		UNSAFE_EVAL("'unsafe-eval'"),
+		STRICT_DYNAMIC("'strict-dynamic'"),
+		NONCE("'nonce-%1$s'")
+		{
+			@Override
+			public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+			{
+				return String.format(getValue(), listener.getNonce(cycle));
+			}
+		};
+
+		private String value;
+
+		private CSPDirectiveSrcValue(String value)
+		{
+			this.value = value;
+		}
+
+		@Override
+		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+		{
+			return value;
+		}
+
+		public String getValue()
+		{
+			return value;
+		}
+	}
+
+	/**
+	 * An enum representing the only possible values for the sandbox directive
+	 */
+	public enum CSPDirectiveSandboxValue implements CSPRenderable
+	{
+		ALLOW_FORMS("allow-forms"),
+		ALLOW_SAME_ORIGIN("allow-same-origin"),
+		ALLOW_SCRIPTS("allow-scripts"),
+		ALLOW_TOP_NAVIGATION("allow-top-navigation"),
+		EMPTY("");
+
+		private String value;
+
+		private CSPDirectiveSandboxValue(String value)
+		{
+			this.value = value;
+		}
+
+		public String getValue()
+		{
+			return value;
+		}
+
+		@Override
+		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+		{
+			return value;
+		}
+	}
+
+	/** An enum holding the possible CSP Directives */
+	public enum CSPDirective
+	{
+		DEFAULT_SRC("default-src"),
+		SCRIPT_SRC("script-src"),
+		STYLE_SRC("style-src"),
+		IMG_SRC("img-src"),
+		CONNECT_SRC("connect-src"),
+		FONT_SRC("font-src"),
+		OBJECT_SRC("object-src"),
+		MANIFEST_SRC("manifest-src"),
+		MEDIA_SRC("media-src"),
+		CHILD_SRC("child-src"),
+		FRAME_ANCESTORS("frame-ancestors"),
+		@Deprecated
+		/** @deprecated Gebruik CHILD-SRC, deze zet ook automatisch FRAME-SRC. */
+		FRAME_SRC("frame-src"),
+		SANDBOX("sandbox")
+		{
+			@Override
+			protected void checkValueForDirective(CSPRenderable value,
+					List<CSPRenderable> existingDirectiveValues)
+			{
+				if (!existingDirectiveValues.isEmpty())
+				{
+					if (CSPDirectiveSandboxValue.EMPTY.equals(value))
+					{
+						throw new IllegalArgumentException(
+							"A sandbox directive can't contain an empty string if it already contains other values ");
+					}
+					if (existingDirectiveValues.contains(CSPDirectiveSandboxValue.EMPTY))
+					{
+						throw new IllegalArgumentException(
+							"A sandbox directive can't contain other values if it already contains an empty string");
+					}
+				}
+
+				if (!(value instanceof CSPDirectiveSandboxValue))
+				{
+					throw new IllegalArgumentException(
+						"A sandbox directive can only contain values from CSPDirectiveSandboxValue or be empty");
+				}
+			}
+		},
+		REPORT_URI("report-uri")
+		{
+			@Override
+			protected void checkValueForDirective(CSPRenderable value,
+					List<CSPRenderable> existingDirectiveValues)
+			{
+				if (!existingDirectiveValues.isEmpty())
+				{
+					throw new IllegalArgumentException(
+						"A report-uri directive can only contain one uri");
+				}
+				if (!(value instanceof FixedCSPDirective))
+				{
+					throw new IllegalArgumentException(
+						"A report-uri directive can only contain an URI");
+				}
+				try
+				{
+					new URI(value.render(null, null));
+				}
+				catch (URISyntaxException urise)
+				{
+					throw new IllegalArgumentException("Illegal URI for report-uri directive",
+						urise);
+				}
+			}
+		};
+
+		private String value;
+
+		private CSPDirective(String value)
+		{
+			this.value = value;
+		}
+
+		public String getValue()
+		{
+			return value;
+		}
+
+		protected void checkValueForDirective(CSPRenderable value,
+				List<CSPRenderable> existingDirectiveValues)
+		{
+			if (!existingDirectiveValues.isEmpty())
+			{
+				if (CSPDirectiveSrcValue.WILDCARD.equals(value)
+					|| CSPDirectiveSrcValue.NONE.equals(value))
+				{
+					throw new IllegalArgumentException(
+						"A -src directive can't contain an * or a 'none' if it already contains other values ");
+				}
+				if (existingDirectiveValues.contains(CSPDirectiveSrcValue.WILDCARD)
+					|| existingDirectiveValues.contains(CSPDirectiveSrcValue.NONE))
+				{
+					throw new IllegalArgumentException(
+						"A -src directive can't contain other values if it already contains an * or a 'none'");
+				}
+			}
+
+			if (value instanceof CSPDirectiveSrcValue)
+			{
+				return;
+			}
+
+			if (value instanceof CSPDirectiveSandboxValue)
+			{
+				throw new IllegalArgumentException(
+					"A -src directive can't contain any of the sandbox directive values");
+			}
+
+			String strValue = value.render(null, null);
+			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);
+			}
+			catch (URISyntaxException urise)
+			{
+				throw new IllegalArgumentException("Illegal URI for -src directive", urise);
+			}
+		}
+
+		/**
+		 * @return The CSPDirective constant whose value-parameter equals the input-parameter or
+		 *         {@code null} if none can be found.
+		 */
+		public static CSPDirective fromValue(String value)
+		{
+			if (Strings.isEmpty(value))
+				return null;
+			for (int i = 0; i < values().length; i++)
+			{
+				if (value.equals(values()[i].getValue()))
+					return values()[i];
+			}
+			return null;
+		}
+	}
+
+	private enum CSPHeaderMode
+	{
+		BLOCKING,
+		REPORT_ONLY;
+	}
+
+	private static String HEADER_CSP = "Content-Security-Policy";
+
+	private static String HEADER_CSP_REPORT = "Content-Security-Policy-Report-Only";
+
+	private static String HEADER_CSP_IE = "X-Content-Security-Policy";
+
+	private static String HEADER_CSP_REPORT_IE = "X-Content-Security-Policy-Report-Only";
+
+	// Directives for the 'Content-Security-Policy' header
+	private Map<CSPDirective, List<CSPRenderable>> blockingDirectives =
+		new EnumMap<>(CSPDirective.class);
+
+	// Directives for the 'Content-Security-Policy-Report-Only' header
+	private Map<CSPDirective, List<CSPRenderable>> reportingDirectives =
+		new EnumMap<>(CSPDirective.class);
+
+	private Function<Integer, byte[]> randomSupplier;
+
+	private boolean addLegacyHeaders = false;
+
+	public CSPSettingRequestCycleListener()
+	{
+	}
+
+	public CSPSettingRequestCycleListener(Function<Integer, byte[]> randomSupplier)
+	{
+		this.randomSupplier = randomSupplier;
+	}
+
+	/**
+	 * True when legacy headers should be added.
+	 * 
+	 * @return True when legacy headers should be added.
+	 */
+	public boolean isAddLegacyHeaders()
+	{
+		return addLegacyHeaders;
+	}
+
+	/**
+	 * Enable legacy {@code X-Content-Security-Policy} headers for older browsers, such as IE.
+	 * 
+	 * @param addLegacyHeaders True when the legacy headers should be added.
+	 * @return {@code this} for chaining
+	 */
+	public CSPSettingRequestCycleListener setAddLegacyHeaders(boolean addLegacyHeaders)
+	{
+		this.addLegacyHeaders = addLegacyHeaders;
+		return this;
+	}
+
+	/**
+	 * Adds any of the default values to a -src directive for the 'blocking' CSP header
+	 */
+	public CSPSettingRequestCycleListener addBlockingDirective(CSPDirective directive,
+			CSPDirectiveSrcValue... values)
+	{
+		for (CSPDirectiveSrcValue value : values)
+		{
+			addDirective(directive, value, CSPHeaderMode.BLOCKING);
+		}
+		return this;
+	}
+
+	/**
+	 * Adds any of the default values to the sandbox directive for the 'blocking' CSP header. Use
+	 * {@link #addBlockingDirective(CSPDirective, String...)} with the sandbox {@link CSPDirective}
+	 * and a single empty string (<em>not</em> {@code null}) to add the empty sandbox directive.
+	 */
+	public CSPSettingRequestCycleListener addBlockingDirective(CSPDirective sandboxDirective,
+			CSPDirectiveSandboxValue... values)
+	{
+		for (CSPDirectiveSandboxValue value : values)
+		{
+			addDirective(sandboxDirective, value, CSPHeaderMode.BLOCKING);
+		}
+		return this;
+	}
+
+	/**
+	 * Adds any value to a directive for the 'blocking' CSP header. Use
+	 * {@link #addBlockingDirective(CSPDirective, CSPDirectiveSandboxValue...)} and
+	 * {@link #addBlockingDirective(CSPDirective, CSPDirectiveSrcValue...)} for the default values
+	 * for the sandbox and -src directives.
+	 */
+	public CSPSettingRequestCycleListener addBlockingDirective(CSPDirective directive,
+			String... values)
+	{
+		for (String value : values)
+		{
+			addDirective(directive, new FixedCSPDirective(value), CSPHeaderMode.BLOCKING);
+		}
+		return this;
+	}
+
+	/**
+	 * Adds any of the default values to a -src directive for the 'reporting-only' CSP header
+	 */
+	public CSPSettingRequestCycleListener addReportingDirective(CSPDirective directive,
+			CSPDirectiveSrcValue... values)
+	{
+		for (CSPDirectiveSrcValue value : values)
+		{
+			addDirective(directive, value, CSPHeaderMode.REPORT_ONLY);
+		}
+		return this;
+	}
+
+	/**
+	 * Adds any of the default values to the sandbox directive for the 'reporting-only' CSP header.
+	 * Use {@link #addReportingDirective(CSPDirective, String...)} with the sandbox
+	 * {@link CSPDirective} and a single empty string (<em>not</em> {@code null}) to add the empty
+	 * sandbox directive.
+	 */
+	public CSPSettingRequestCycleListener addReportingDirective(CSPDirective sandboxDirective,
+			CSPDirectiveSandboxValue... values)
+	{
+		for (CSPDirectiveSandboxValue value : values)
+		{
+			addDirective(sandboxDirective, value, CSPHeaderMode.REPORT_ONLY);
+		}
+		return this;
+	}
+
+	/**
+	 * Adds any value to a directive for the 'reporting-only' CSP header. Use
+	 * {@link #addReportingDirective(CSPDirective, CSPDirectiveSandboxValue...)} and
+	 * {@link #addReportingDirective(CSPDirective, CSPDirectiveSrcValue...)} for the default values
+	 * for the sandbox and -src directives.
+	 */
+	public CSPSettingRequestCycleListener addReportingDirective(CSPDirective directive,
+			String... values)
+	{
+		for (String value : values)
+		{
+			addDirective(directive, new FixedCSPDirective(value), CSPHeaderMode.REPORT_ONLY);
+		}
+		return this;
+	}
+
+	private CSPSettingRequestCycleListener addDirective(CSPDirective directive, CSPRenderable value,
+			CSPHeaderMode mode)
+	{
+		// Add backwards compatible frame-src
+		// see http://caniuse.com/#feat=contentsecuritypolicy2
+		if (CSPDirective.CHILD_SRC.equals(directive))
+		{
+			addDirective(CSPDirective.FRAME_SRC, value, mode);
+		}
+		switch (mode)
+		{
+			case BLOCKING:
+				if (blockingDirectives.get(directive) == null)
+				{
+					blockingDirectives.put(directive, new ArrayList<>());
+				}
+				directive.checkValueForDirective(value, blockingDirectives.get(directive));
+				blockingDirectives.get(directive).add(value);
+				return this;
+			case REPORT_ONLY:
+				if (reportingDirectives.get(directive) == null)
+				{
+					reportingDirectives.put(directive, new ArrayList<>());
+				}
+				directive.checkValueForDirective(value, reportingDirectives.get(directive));
+				reportingDirectives.get(directive).add(value);
+				return this;
+			default:
+				throw new IllegalArgumentException("Incorrect CSPHeaderMode!");
+		}
+
+	}
+
+	@Override
+	public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
+	{
+		WebResponse webResponse = (WebResponse) cycle.getResponse();
+		if (!reportingDirectives.isEmpty())
+		{
+			String reportHeaderValue = getCSPHeaderValue(reportingDirectives, cycle);
+			webResponse.setHeader(HEADER_CSP_REPORT, reportHeaderValue);
+			if (addLegacyHeaders)
+				webResponse.setHeader(HEADER_CSP_REPORT_IE, reportHeaderValue);
+		}
+		if (!blockingDirectives.isEmpty())
+		{
+			String blockHeaderValue = getCSPHeaderValue(blockingDirectives, cycle);
+			webResponse.setHeader(HEADER_CSP, blockHeaderValue);
+			if (addLegacyHeaders)
+				webResponse.setHeader(HEADER_CSP_IE, blockHeaderValue);
+		}
+	}
+
+	public String getNonce(RequestCycle cycle)
+	{
+		String nonce = cycle.getMetaData(NONCE_KEY);
+		if (nonce == null)
+		{
+			nonce = Base64.getEncoder().encodeToString(randomSupplier.apply(12));
+			cycle.setMetaData(NONCE_KEY, nonce);
+		}
+		return nonce;
+	}
+
+	// @returns "key1 value1a value1b; key2 value2a; key3 value3a value3b value3c"
+	private String getCSPHeaderValue(Map<CSPDirective, List<CSPRenderable>> directiveValuesMap,
+			RequestCycle cycle)
+	{
+		return directiveValuesMap.entrySet()
+			.stream()
+			.map(e -> e.getKey().getValue() + " "
+				+ e.getValue()
+					.stream()
+					.map(r -> r.render(this, cycle))
+					.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
new file mode 100644
index 0000000..5aabcda
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CspNonceHeaderResponseDecorator.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
+ * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
+ * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
+ * for the specific language governing permissions and limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.markup.head.AbstractCspHeaderItem;
+import org.apache.wicket.markup.head.HeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.IWrappedHeaderItem;
+import org.apache.wicket.markup.head.ResourceAggregator;
+import org.apache.wicket.markup.html.DecoratingHeaderResponse;
+import org.apache.wicket.request.cycle.RequestCycle;
+
+/**
+ * Add a <em>Content Security Policy<em> (CSP) nonce to all {@link AbstractCspHeaderItem}s.
+ * <p>
+ * Note: please don't forget to wrap with {@link ResourceAggregator} when setting it up with
+ * {@link Application#setHeaderResponseDecorator}, otherwise dependencies will not be rendered.
+ *
+ * @see AbstractCspHeaderItem
+ */
+public class CspNonceHeaderResponseDecorator extends DecoratingHeaderResponse
+{
+	private CSPSettingRequestCycleListener listener;
+
+	public CspNonceHeaderResponseDecorator(IHeaderResponse real, CSPSettingRequestCycleListener listener)
+	{
+		super(real);
+
+		this.listener = listener;
+	}
+
+	@Override
+	public void render(HeaderItem item)
+	{
+		while (item instanceof IWrappedHeaderItem)
+		{
+			item = ((IWrappedHeaderItem) item).getWrapped();
+		}
+
+		if (item instanceof AbstractCspHeaderItem)
+		{
+			((AbstractCspHeaderItem) item).setNonce(listener.getNonce(RequestCycle.get()));
+		}
+
+		super.render(item);
+	}
+}
diff --git a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
new file mode 100644
index 0000000..8e00759
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
@@ -0,0 +1,448 @@
+package org.apache.wicket.csp;
+
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.CHILD_SRC;
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.DEFAULT_SRC;
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.FRAME_SRC;
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.REPORT_URI;
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.SANDBOX;
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSandboxValue.ALLOW_FORMS;
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSandboxValue.EMPTY;
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSrcValue.NONE;
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSrcValue.SELF;
+import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSrcValue.WILDCARD;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective;
+import org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSandboxValue;
+import org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSrcValue;
+import org.apache.wicket.mock.MockHomePage;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings("deprecation")
+public class CSPSettingRequestCycleListenerTest
+{
+	private static String HEADER_CSP = "Content-Security-Policy";
+
+	private static String HEADER_CSP_REPORT = "Content-Security-Policy-Report-Only";
+
+	private WicketTester wicketTester;
+
+	@BeforeEach
+	public void setUp()
+	{
+		wicketTester = new WicketTester(MockHomePage.class);
+	}
+
+	@Test
+	public void testNullSrcInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(DEFAULT_SRC, (String) null);
+		});
+	}
+
+	@Test
+	public void testEmptySrcInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(DEFAULT_SRC, "");
+		});
+	}
+
+	/**
+	 * A value for any of the -src directives can be a number of predefined values (for most of them
+	 * you can use {@link CSPDirectiveSrcValue}) or a correct URI.
+	 */
+	@Test
+	public void testInvalidSrcInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(DEFAULT_SRC, "abc?^()-_\'xyz");
+		});
+	}
+
+	/**
+	 * If {@code 'none'} is used for any of the -src directives, it must be the only value for that
+	 * directive.
+	 */
+	@Test
+	public void testMultipleSrcInputWithNoneIsRejected1()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(DEFAULT_SRC, SELF, NONE);
+		});
+	}
+
+	/**
+	 * If {@code 'none'} is used for any of the -src directives, it must be the only value for that
+	 * directive.
+	 */
+	@Test
+	public void testMultipleSrcInputWithNoneIsRejected2()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(DEFAULT_SRC, NONE, SELF);
+		});
+	}
+
+	/**
+	 * If {@code *} (asterisk) is used for any of the -src directives, it must be the only value for
+	 * that directive.
+	 */
+	@Test
+	public void testMultipleSrcInputWithStarIsRejected1()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		cspListener.addBlockingDirective(DEFAULT_SRC, SELF);
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(DEFAULT_SRC, WILDCARD);
+		});
+	}
+
+	/**
+	 * If {@code *} (asterisk) is used for any of the -src directives, it must be the only value for
+	 * that directive.
+	 */
+	@Test
+	public void testMultipleSrcInputWithStarIsRejected2()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		cspListener.addBlockingDirective(DEFAULT_SRC, WILDCARD);
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(DEFAULT_SRC, SELF);
+		});
+	}
+
+	@Test
+	public void testWrongSrcInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(DEFAULT_SRC, ALLOW_FORMS);
+		});
+	}
+
+	@Test
+	public void testWrongSandboxInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(SANDBOX, SELF);
+		});
+	}
+
+	@Test
+	public void testNullSandboxInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(SANDBOX, (String) null);
+		});
+	}
+
+	@Test
+	public void testEmptySandboxInputIsAccepted()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		cspListener.addBlockingDirective(SANDBOX, CSPDirectiveSandboxValue.EMPTY);
+	}
+
+	@Test
+	public void testInvalidSandboxInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(SANDBOX, "abcxyz");
+		});
+	}
+
+	@Test
+	public void testMultipleSandboxInputWithEmptyStringIsRejected1()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		cspListener.addBlockingDirective(SANDBOX, ALLOW_FORMS);
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(SANDBOX, EMPTY);
+		});
+	}
+
+	@Test
+	public void testMultipleSandboxInputWithEmptyStringIsRejected2()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		cspListener.addBlockingDirective(SANDBOX, EMPTY);
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(SANDBOX, ALLOW_FORMS);
+		});
+	}
+
+	@Test
+	public void testNullReportUriInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(REPORT_URI, (String) null);
+		});
+	}
+
+	@Test
+	public void testEmptyReportUriInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(REPORT_URI, "");
+		});
+	}
+
+	@Test
+	public void testInvalidReportUriInputIsRejected()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		Assertions.assertThrows(IllegalArgumentException.class, () -> {
+			cspListener.addBlockingDirective(REPORT_URI, "abc?^()-_\'xyz");
+		});
+	}
+
+	@Test
+	public void testAllCSPSrcDefaultEnumsAreSetCorrectly() throws NoSuchAlgorithmException
+	{
+		SecureRandom random = SecureRandom.getInstanceStrong();
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener(length -> {
+			byte[] ret = new byte[length];
+			random.nextBytes(ret);
+			return ret;
+		});
+
+		final int cspDirectiveCount = CSPDirective.values().length;
+		final int cspDirectiveSrcValueCount = CSPDirectiveSrcValue.values().length;
+		for (int i = 0; i < Math.max(cspDirectiveCount, cspDirectiveSrcValueCount); i++)
+		{
+			final CSPDirective cspDirective = CSPDirective.values()[i % cspDirectiveCount];
+			// FRAME-SRC wordt al gezet door de aanroep voor CHILD-SRC
+			if (!FRAME_SRC.equals(cspDirective) && cspDirective.getValue().endsWith("-src"))
+			{
+				final CSPDirectiveSrcValue cspDirectiveValue =
+					CSPDirectiveSrcValue.values()[i % cspDirectiveSrcValueCount];
+				cspListener.addBlockingDirective(cspDirective, cspDirectiveValue);
+
+				cspListener.addReportingDirective(cspDirective, cspDirectiveValue);
+			}
+		}
+
+		StringBuffer headerErrors = checkHeaders(cspListener);
+
+		if (headerErrors.length() > 0)
+		{
+			Assertions.fail(headerErrors.toString());
+		}
+	}
+
+	@Test
+	public void testCSPReportUriDirectiveSetCorrectly()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		cspListener.addBlockingDirective(REPORT_URI, "http://report.example.com");
+		cspListener.addReportingDirective(REPORT_URI, "/example-report-uri");
+
+		StringBuffer headerErrors = checkHeaders(cspListener);
+
+		if (headerErrors.length() > 0)
+		{
+			Assertions.fail(headerErrors.toString());
+		}
+	}
+
+	@Test
+	public void testCSPSandboxDirectiveSetCorrectly()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		final int cspSandboxDirectiveValueCount = CSPDirectiveSandboxValue.values().length;
+		for (int i = 0; i < cspSandboxDirectiveValueCount; i++)
+		{
+			final CSPDirectiveSandboxValue cspDirectiveValue = CSPDirectiveSandboxValue.values()[i];
+			if (cspDirectiveValue.equals(CSPDirectiveSandboxValue.EMPTY))
+				continue;
+			
+			cspListener.addBlockingDirective(SANDBOX, cspDirectiveValue);
+			cspListener.addReportingDirective(SANDBOX, cspDirectiveValue);
+		}
+
+		StringBuffer headerErrors = checkHeaders(cspListener);
+
+		if (headerErrors.length() > 0)
+		{
+			Assertions.fail(headerErrors.toString());
+		}
+	}
+
+	// FF 36+, IE (incl. Edge), Safari en Opera Mini hebben nog geen (volledige)
+	// support voor CSP, wat betekent dat ze CHILD-SRC niet kennen en FRAME-SRC
+	// verwachten. Daarom in de CSPSettingRCL een hack om alle CHILD-SRC's die geset
+	// worden ook als FRAME-SRC te setten.
+	// Zie http://caniuse.com/#feat=contentsecuritypolicy2
+	@Test
+	public void testChildSrcDirectiveAlsoSetsFrameSrcDirective()
+	{
+		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		cspListener.addBlockingDirective(CHILD_SRC, SELF);
+		cspListener.addReportingDirective(CHILD_SRC, SELF);
+		StringBuffer headerErrors = checkHeaders(cspListener);
+
+		if (headerErrors.length() > 0)
+		{
+			Assertions.fail(headerErrors.toString());
+		}
+	}
+
+	private StringBuffer checkHeaders(CSPSettingRequestCycleListener cspListener)
+	{
+		StringBuffer headerErrors = new StringBuffer();
+		wicketTester.getRequestCycle().getListeners().add(cspListener);
+		wicketTester.executeUrl("/");
+		String cspHeaderValue = wicketTester.getLastResponse().getHeader(HEADER_CSP);
+		String cspReportingHeaderValue =
+			wicketTester.getLastResponse().getHeader(HEADER_CSP_REPORT);
+
+		if (cspHeaderValue == null)
+		{
+			headerErrors.append(
+				String.format("Header %s expected but either not present or empty", HEADER_CSP));
+		}
+		if (cspReportingHeaderValue == null)
+		{
+			headerErrors.append(String.format("Header %s expected but either not present or empty",
+				HEADER_CSP_REPORT));
+		}
+
+		if (headerErrors.length() > 0)
+		{
+			return headerErrors;
+		}
+
+		StringBuffer headerValueErrors = new StringBuffer();
+		List<String> blockingHeaderValueErrors = checkCSPHeaderValues(cspHeaderValue);
+		List<String> reportingHeaderValueErrors = checkCSPHeaderValues(cspReportingHeaderValue);
+
+		if (!blockingHeaderValueErrors.isEmpty())
+		{
+			headerValueErrors.append("Blocking-mode CSP header value issues: ");
+			headerValueErrors
+				.append(blockingHeaderValueErrors.stream().collect(Collectors.joining("; ")));
+			headerValueErrors.append(". ");
+		}
+		if (!reportingHeaderValueErrors.isEmpty())
+		{
+			headerValueErrors.append("Reporting-mode CSP header value issues: ");
+			headerValueErrors
+				.append(reportingHeaderValueErrors.stream().collect(Collectors.joining("; ")));
+			headerValueErrors.append(". ");
+		}
+		return headerValueErrors;
+	}
+
+	private List<String> checkCSPHeaderValues(String cspHeaderValue)
+	{
+		Set<String> directiveValues = Stream.of(CSPDirective.values())
+			.map(CSPDirective::getValue)
+			.collect(Collectors.toSet());
+		Set<String> directiveSrcValues = Stream.of(CSPDirectiveSrcValue.values())
+			.map(CSPDirectiveSrcValue::getValue)
+			.collect(Collectors.toSet());
+		Set<String> directiveSandboxValues = Stream.of(CSPDirectiveSandboxValue.values())
+			.map(CSPDirectiveSandboxValue::getValue)
+			.collect(Collectors.toSet());
+
+		final List<String> errors = new ArrayList<>();
+		String[] directives = cspHeaderValue.split(";");
+		boolean hasChildSrc = false, hasFrameSrc = false;
+		for (String directive : directives)
+		{
+			directive = directive.trim();
+			String[] values = directive.split("\\s");
+			String directiveName = values[0];
+			if (!directiveValues.contains(directiveName))
+			{
+				errors.add(
+					String.format("Directive %s is not a valid directive name", directiveName));
+			}
+			else
+			{
+				if (CSPDirective.fromValue(directiveName).equals(FRAME_SRC))
+				{
+					hasFrameSrc = true;
+				}
+				if (CSPDirective.fromValue(directiveName).equals(CHILD_SRC))
+				{
+					hasChildSrc = true;
+				}
+				for (int i = 1; i < values.length; i++)
+				{
+					final String trimmedValue = values[i].trim();
+					final boolean isValidDefaultSrcValue =
+						directiveSrcValues.contains(trimmedValue);
+					final boolean isValidDefaultSandboxValue =
+						directiveSandboxValues.contains(trimmedValue);
+					if (!(isValidDefaultSrcValue || isValidDefaultSandboxValue
+						|| isValidDirectiveValue(trimmedValue)))
+					{
+						errors.add(
+							String.format("Value %s is not a valid directive value", trimmedValue));
+					}
+				}
+			}
+		}
+
+		if (hasFrameSrc != hasChildSrc)
+		{
+			String presentDirective = hasFrameSrc ? FRAME_SRC.getValue() : CHILD_SRC.getValue();
+			String notPresentDirective = !hasFrameSrc ? FRAME_SRC.getValue() : CHILD_SRC.getValue();
+			errors.add(String.format("Directive %s present without directive %s for fallback",
+				presentDirective, notPresentDirective));
+		}
+
+		return errors;
+	}
+
+	// @see: http://content-security-policy.com/#source_list
+	private boolean isValidDirectiveValue(String directiveValue)
+	{
+		if ("*".equals(directiveValue))
+			return true;
+		else if ("data:".equals(directiveValue) || "https:".equals(directiveValue))
+			return true;
+
+		// strip off "*." for "*.example.com" so we can check "example.com" to be a valid
+		// URI.
+		if (directiveValue.startsWith("*."))
+			directiveValue = directiveValue.substring(2);
+		try
+		{
+			new URI(directiveValue);
+			return true;
+		}
+		catch (URISyntaxException ignored)
+		{
+			// fall through
+		}
+
+		return false;
+	}
+
+}


[wicket] 04/08: WICKET-6727: more refactoring of the CSP API

Posted by pa...@apache.org.
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

commit d1a59a848e1095ffc3f09b14cd5cb786d89a4a63
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Thu Jan 16 22:10:44 2020 +0100

    WICKET-6727: more refactoring of the CSP API
---
 .../java/org/apache/wicket/csp/CSPDirective.java   | 191 +++++++++
 .../wicket/csp/CSPDirectiveSandboxValue.java       |  49 +++
 .../apache/wicket/csp/CSPDirectiveSrcValue.java    |  58 +++
 .../apache/wicket/csp/CSPHeaderConfiguration.java  | 143 +++++++
 .../java/org/apache/wicket/csp/CSPHeaderMode.java  |  45 +++
 .../java/org/apache/wicket/csp/CSPRenderable.java  |  42 ++
 .../wicket/csp/CSPSettingRequestCycleListener.java | 441 ++-------------------
 .../org/apache/wicket/csp/FixedCSPDirective.java   |  50 +++
 .../csp/CSPSettingRequestCycleListenerTest.java    |  96 +++--
 9 files changed, 665 insertions(+), 450 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
new file mode 100644
index 0000000..1ea52c1
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirective.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * An enum holding the possible CSP Directives. Via the
+ * {@link #checkValueForDirective(CSPRenderable, List)}-method, new values can be verified before
+ * being added to the list of values for a directive.
+ */
+public enum CSPDirective
+{
+	DEFAULT_SRC("default-src"),
+	SCRIPT_SRC("script-src"),
+	STYLE_SRC("style-src"),
+	IMG_SRC("img-src"),
+	CONNECT_SRC("connect-src"),
+	FONT_SRC("font-src"),
+	OBJECT_SRC("object-src"),
+	MANIFEST_SRC("manifest-src"),
+	MEDIA_SRC("media-src"),
+	CHILD_SRC("child-src"),
+	FRAME_ANCESTORS("frame-ancestors"),
+	@Deprecated
+	/** @deprecated Gebruik CHILD-SRC, deze zet ook automatisch FRAME-SRC. */
+	FRAME_SRC("frame-src"),
+	SANDBOX("sandbox")
+	{
+		@Override
+		public void checkValueForDirective(CSPRenderable value,
+				List<CSPRenderable> existingDirectiveValues)
+		{
+			if (!existingDirectiveValues.isEmpty())
+			{
+				if (CSPDirectiveSandboxValue.EMPTY.equals(value))
+				{
+					throw new IllegalArgumentException(
+						"A sandbox directive can't contain an empty string if it already contains other values ");
+				}
+				if (existingDirectiveValues.contains(CSPDirectiveSandboxValue.EMPTY))
+				{
+					throw new IllegalArgumentException(
+						"A sandbox directive can't contain other values if it already contains an empty string");
+				}
+			}
+
+			if (!(value instanceof CSPDirectiveSandboxValue))
+			{
+				throw new IllegalArgumentException(
+					"A sandbox directive can only contain values from CSPDirectiveSandboxValue or be empty");
+			}
+		}
+	},
+	REPORT_URI("report-uri")
+	{
+		@Override
+		public void checkValueForDirective(CSPRenderable value,
+				List<CSPRenderable> existingDirectiveValues)
+		{
+			if (!existingDirectiveValues.isEmpty())
+			{
+				throw new IllegalArgumentException(
+					"A report-uri directive can only contain one uri");
+			}
+			if (!(value instanceof FixedCSPDirective))
+			{
+				throw new IllegalArgumentException(
+					"A report-uri directive can only contain an URI");
+			}
+			try
+			{
+				new URI(value.render(null, null));
+			}
+			catch (URISyntaxException urise)
+			{
+				throw new IllegalArgumentException("Illegal URI for report-uri directive", urise);
+			}
+		}
+	};
+
+	private String value;
+
+	private CSPDirective(String value)
+	{
+		this.value = value;
+	}
+
+	public String getValue()
+	{
+		return value;
+	}
+
+	/**
+	 * Check if {@code value} can be added to the list of other values.
+	 * 
+	 * @param value
+	 *            The value to add.
+	 * @param existingDirectiveValues
+	 *            The other values.
+	 * @throws IllegalArgumentException
+	 *             if the given value is invalid.
+	 */
+	public void checkValueForDirective(CSPRenderable value,
+			List<CSPRenderable> existingDirectiveValues)
+	{
+		if (!existingDirectiveValues.isEmpty())
+		{
+			if (CSPDirectiveSrcValue.WILDCARD.equals(value)
+				|| CSPDirectiveSrcValue.NONE.equals(value))
+			{
+				throw new IllegalArgumentException(
+					"A -src directive can't contain an * or a 'none' if it already contains other values ");
+			}
+			if (existingDirectiveValues.contains(CSPDirectiveSrcValue.WILDCARD)
+				|| existingDirectiveValues.contains(CSPDirectiveSrcValue.NONE))
+			{
+				throw new IllegalArgumentException(
+					"A -src directive can't contain other values if it already contains an * or a 'none'");
+			}
+		}
+
+		if (value instanceof CSPDirectiveSrcValue)
+		{
+			return;
+		}
+
+		if (value instanceof CSPDirectiveSandboxValue)
+		{
+			throw new IllegalArgumentException(
+				"A -src directive can't contain any of the sandbox directive values");
+		}
+
+		String strValue = value.render(null, null);
+		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);
+		}
+		catch (URISyntaxException urise)
+		{
+			throw new IllegalArgumentException("Illegal URI for -src directive", urise);
+		}
+	}
+
+	/**
+	 * @return The CSPDirective constant whose value-parameter equals the input-parameter or
+	 *         {@code null} if none can be found.
+	 */
+	public static CSPDirective fromValue(String value)
+	{
+		if (Strings.isEmpty(value))
+			return null;
+		for (int i = 0; i < values().length; i++)
+		{
+			if (value.equals(values()[i].getValue()))
+				return values()[i];
+		}
+		return null;
+	}
+}
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
new file mode 100644
index 0000000..8164b31
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSandboxValue.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+import org.apache.wicket.request.cycle.RequestCycle;
+
+/**
+ * An enum representing the only possible values for the sandbox directive
+ */
+public enum CSPDirectiveSandboxValue implements CSPRenderable
+{
+	ALLOW_FORMS("allow-forms"),
+	ALLOW_SAME_ORIGIN("allow-same-origin"),
+	ALLOW_SCRIPTS("allow-scripts"),
+	ALLOW_TOP_NAVIGATION("allow-top-navigation"),
+	EMPTY("");
+
+	private String value;
+
+	private CSPDirectiveSandboxValue(String value)
+	{
+		this.value = value;
+	}
+
+	public String getValue()
+	{
+		return value;
+	}
+
+	@Override
+	public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+	{
+		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
new file mode 100644
index 0000000..b4a06db
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirectiveSrcValue.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+import org.apache.wicket.request.cycle.RequestCycle;
+
+/**
+ * An enum holding the default values for -src directives including the mandatory single quotes
+ */
+public enum CSPDirectiveSrcValue implements CSPRenderable
+{
+	NONE("'none'"),
+	WILDCARD("*"),
+	SELF("'self'"),
+	UNSAFE_INLINE("'unsafe-inline'"),
+	UNSAFE_EVAL("'unsafe-eval'"),
+	STRICT_DYNAMIC("'strict-dynamic'"),
+	NONCE("'nonce-%1$s'")
+	{
+		@Override
+		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+		{
+			return String.format(getValue(), listener.getNonce(cycle));
+		}
+	};
+
+	private String value;
+
+	private CSPDirectiveSrcValue(String value)
+	{
+		this.value = value;
+	}
+
+	@Override
+	public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+	{
+		return value;
+	}
+
+	public String getValue()
+	{
+		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
new file mode 100644
index 0000000..1abd1a5
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderConfiguration.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.wicket.request.cycle.RequestCycle;
+
+/**
+ * 
+ * @author papegaaij
+ */
+public class CSPHeaderConfiguration
+{
+	private Map<CSPDirective, List<CSPRenderable>> directives = new EnumMap<>(CSPDirective.class);
+
+	private boolean addLegacyHeaders = false;
+
+	public CSPHeaderConfiguration()
+	{
+	}
+
+	/**
+	 * True when legacy headers should be added.
+	 * 
+	 * @return True when legacy headers should be added.
+	 */
+	public boolean isAddLegacyHeaders()
+	{
+		return addLegacyHeaders;
+	}
+
+	/**
+	 * Enable legacy {@code X-Content-Security-Policy} headers for older browsers, such as IE.
+	 * 
+	 * @param addLegacyHeaders
+	 *            True when the legacy headers should be added.
+	 * @return {@code this} for chaining
+	 */
+	public CSPHeaderConfiguration setAddLegacyHeaders(boolean addLegacyHeaders)
+	{
+		this.addLegacyHeaders = addLegacyHeaders;
+		return this;
+	}
+
+	/**
+	 * Adds the given values to the CSP directive on this configuraiton.
+	 * 
+	 * @param directive
+	 *            The directive to add the values to.
+	 * @param values
+	 *            The values to add.
+	 */
+	public CSPHeaderConfiguration addDirective(CSPDirective directive, CSPRenderable... values)
+	{
+		for (CSPRenderable value : values)
+		{
+			doAddDirective(directive, value);
+		}
+		return this;
+	}
+
+	/**
+	 * Adds a free-form value to a directive for the CSP header. This is primarily meant to used for
+	 * URIs.
+	 * 
+	 * @param directive
+	 *            The directive to add the values to.
+	 * @param values
+	 *            The values to add.
+	 */
+	public CSPHeaderConfiguration addDirective(CSPDirective directive, String... values)
+	{
+		for (String value : values)
+		{
+			doAddDirective(directive, new FixedCSPDirective(value));
+		}
+		return this;
+	}
+
+	/**
+	 * @return true if this {@code CSPHeaderConfiguration} has any directives configured.
+	 */
+	public boolean isSet()
+	{
+		return !directives.isEmpty();
+	}
+
+	@SuppressWarnings("deprecation")
+	private CSPHeaderConfiguration doAddDirective(CSPDirective directive, CSPRenderable value)
+	{
+		// Add backwards compatible frame-src
+		// see http://caniuse.com/#feat=contentsecuritypolicy2
+		if (CSPDirective.CHILD_SRC.equals(directive))
+		{
+			doAddDirective(CSPDirective.FRAME_SRC, value);
+		}
+		List<CSPRenderable> values = directives.computeIfAbsent(directive, x -> new ArrayList<>());
+		directive.checkValueForDirective(value, values);
+		values.add(value);
+		return this;
+	}
+
+	/**
+	 * Renders this {@code CSPHeaderConfiguration} into an HTTP header. The returned String will be
+	 * in the form {@code "key1 value1a value1b; key2 value2a; key3 value3a value3b value3c"}.
+	 * 
+	 * @param listener
+	 *            The {@link CSPSettingRequestCycleListener} that renders the header.
+	 * @param cycle
+	 *            The current {@link RequestCycle}.
+	 * @return the rendered header.
+	 */
+	public String renderHeaderValue(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+	{
+		return directives.entrySet()
+			.stream()
+			.map(e -> e.getKey().getValue() + " "
+				+ e.getValue()
+					.stream()
+					.map(r -> r.render(listener, cycle))
+					.collect(Collectors.joining(" ")))
+			.collect(Collectors.joining("; "));
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderMode.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderMode.java
new file mode 100644
index 0000000..48a3a95
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderMode.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+/**
+ * Defines the possible headers for a CSP directive.
+ * 
+ * @author papegaaij
+ */
+public enum CSPHeaderMode
+{
+	BLOCKING("Content-Security-Policy"),
+	REPORT_ONLY("Content-Security-Policy-Report-Only");
+
+	private final String header;
+
+	private CSPHeaderMode(String header)
+	{
+		this.header = header;
+	}
+
+	public String getHeader()
+	{
+		return header;
+	}
+
+	public String getLegacyHeader()
+	{
+		return "X-" + getHeader();
+	}
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..05badc9
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPRenderable.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+import org.apache.wicket.request.cycle.RequestCycle;
+
+/**
+ * {@code CSPRenderable} describes a directive that is part of a Content-Security-Policy (CSP in
+ * short). Most directives are predefined in enums.
+ * 
+ * @author papegaaij
+ * @see CSPDirectiveSrcValue
+ * @see CSPDirectiveSandboxValue
+ * @see FixedCSPDirective
+ */
+public interface CSPRenderable
+{
+	/**
+	 * Renders the value that should be put in the CSP header.
+	 * 
+	 * @param listener
+	 *            The {@link CSPSettingRequestCycleListener} that renders this value.
+	 * @param cycle
+	 *            The current {@link RequestCycle}.
+	 * @return The rendered value.
+	 */
+	public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle);
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
index d688990..85cff77 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
@@ -16,23 +16,19 @@
  */
 package org.apache.wicket.csp;
 
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.EnumMap;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 
+import org.apache.wicket.Application;
 import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.core.request.handler.BufferedResponseRequestHandler;
+import org.apache.wicket.core.request.handler.IPageClassRequestHandler;
 import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.IRequestHandlerDelegate;
 import org.apache.wicket.request.cycle.IRequestCycleListener;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.request.http.WebResponse;
-import org.apache.wicket.util.string.Strings;
+import org.apache.wicket.util.lang.Args;
 
 /**
  * An {@link IRequestCycleListener} that adds {@code Content-Security-Policy} and/or
@@ -78,425 +74,54 @@ public class CSPSettingRequestCycleListener implements IRequestCycleListener
 		private static final long serialVersionUID = 1L;
 	};
 
-	public static interface CSPRenderable
-	{
-		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle);
-	}
+	private final Application application;
+	
+	private Map<CSPHeaderMode, CSPHeaderConfiguration> configs = new HashMap<>();
 
-	private static final class FixedCSPDirective implements CSPRenderable
+	public CSPSettingRequestCycleListener(Application application)
 	{
-		private String value;
-
-		public FixedCSPDirective(String value)
-		{
-			if (Strings.isEmpty(value))
-				throw new IllegalArgumentException(
-					"CSP directive cannot have empty or null values");
-			this.value = value;
-		}
-
-		@Override
-		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
-		{
-			return value;
-		}
+		this.application = Args.notNull(application, "application");
 	}
 
-	/**
-	 * An enum holding the default values for -src directives including the mandatory single quotes
-	 */
-	public enum CSPDirectiveSrcValue implements CSPRenderable
+	public CSPHeaderConfiguration blocking()
 	{
-		NONE("'none'"),
-		WILDCARD("*"),
-		SELF("'self'"),
-		UNSAFE_INLINE("'unsafe-inline'"),
-		UNSAFE_EVAL("'unsafe-eval'"),
-		STRICT_DYNAMIC("'strict-dynamic'"),
-		NONCE("'nonce-%1$s'")
-		{
-			@Override
-			public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
-			{
-				return String.format(getValue(), listener.getNonce(cycle));
-			}
-		};
-
-		private String value;
-
-		private CSPDirectiveSrcValue(String value)
-		{
-			this.value = value;
-		}
-
-		@Override
-		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
-		{
-			return value;
-		}
-
-		public String getValue()
-		{
-			return value;
-		}
+		return configs.computeIfAbsent(CSPHeaderMode.BLOCKING, x -> new CSPHeaderConfiguration());
 	}
 
-	/**
-	 * An enum representing the only possible values for the sandbox directive
-	 */
-	public enum CSPDirectiveSandboxValue implements CSPRenderable
+	public CSPHeaderConfiguration reporting()
 	{
-		ALLOW_FORMS("allow-forms"),
-		ALLOW_SAME_ORIGIN("allow-same-origin"),
-		ALLOW_SCRIPTS("allow-scripts"),
-		ALLOW_TOP_NAVIGATION("allow-top-navigation"),
-		EMPTY("");
-
-		private String value;
-
-		private CSPDirectiveSandboxValue(String value)
-		{
-			this.value = value;
-		}
-
-		public String getValue()
-		{
-			return value;
-		}
-
-		@Override
-		public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
-		{
-			return value;
-		}
+		return configs.computeIfAbsent(CSPHeaderMode.REPORT_ONLY, x -> new CSPHeaderConfiguration());
 	}
 
-	/** An enum holding the possible CSP Directives */
-	public enum CSPDirective
+	protected boolean mustProtect(IRequestHandler handler)
 	{
-		DEFAULT_SRC("default-src"),
-		SCRIPT_SRC("script-src"),
-		STYLE_SRC("style-src"),
-		IMG_SRC("img-src"),
-		CONNECT_SRC("connect-src"),
-		FONT_SRC("font-src"),
-		OBJECT_SRC("object-src"),
-		MANIFEST_SRC("manifest-src"),
-		MEDIA_SRC("media-src"),
-		CHILD_SRC("child-src"),
-		FRAME_ANCESTORS("frame-ancestors"),
-		@Deprecated
-		/** @deprecated Gebruik CHILD-SRC, deze zet ook automatisch FRAME-SRC. */
-		FRAME_SRC("frame-src"),
-		SANDBOX("sandbox")
-		{
-			@Override
-			protected void checkValueForDirective(CSPRenderable value,
-					List<CSPRenderable> existingDirectiveValues)
-			{
-				if (!existingDirectiveValues.isEmpty())
-				{
-					if (CSPDirectiveSandboxValue.EMPTY.equals(value))
-					{
-						throw new IllegalArgumentException(
-							"A sandbox directive can't contain an empty string if it already contains other values ");
-					}
-					if (existingDirectiveValues.contains(CSPDirectiveSandboxValue.EMPTY))
-					{
-						throw new IllegalArgumentException(
-							"A sandbox directive can't contain other values if it already contains an empty string");
-					}
-				}
-
-				if (!(value instanceof CSPDirectiveSandboxValue))
-				{
-					throw new IllegalArgumentException(
-						"A sandbox directive can only contain values from CSPDirectiveSandboxValue or be empty");
-				}
-			}
-		},
-		REPORT_URI("report-uri")
-		{
-			@Override
-			protected void checkValueForDirective(CSPRenderable value,
-					List<CSPRenderable> existingDirectiveValues)
-			{
-				if (!existingDirectiveValues.isEmpty())
-				{
-					throw new IllegalArgumentException(
-						"A report-uri directive can only contain one uri");
-				}
-				if (!(value instanceof FixedCSPDirective))
-				{
-					throw new IllegalArgumentException(
-						"A report-uri directive can only contain an URI");
-				}
-				try
-				{
-					new URI(value.render(null, null));
-				}
-				catch (URISyntaxException urise)
-				{
-					throw new IllegalArgumentException("Illegal URI for report-uri directive",
-						urise);
-				}
-			}
-		};
-
-		private String value;
-
-		private CSPDirective(String value)
-		{
-			this.value = value;
-		}
-
-		public String getValue()
+		if (handler instanceof IRequestHandlerDelegate)
+			return mustProtect(((IRequestHandlerDelegate) handler).getDelegateHandler());
+		if (handler instanceof IPageClassRequestHandler)
 		{
-			return value;
-		}
-
-		protected void checkValueForDirective(CSPRenderable value,
-				List<CSPRenderable> existingDirectiveValues)
-		{
-			if (!existingDirectiveValues.isEmpty())
-			{
-				if (CSPDirectiveSrcValue.WILDCARD.equals(value)
-					|| CSPDirectiveSrcValue.NONE.equals(value))
-				{
-					throw new IllegalArgumentException(
-						"A -src directive can't contain an * or a 'none' if it already contains other values ");
-				}
-				if (existingDirectiveValues.contains(CSPDirectiveSrcValue.WILDCARD)
-					|| existingDirectiveValues.contains(CSPDirectiveSrcValue.NONE))
-				{
-					throw new IllegalArgumentException(
-						"A -src directive can't contain other values if it already contains an * or a 'none'");
-				}
-			}
-
-			if (value instanceof CSPDirectiveSrcValue)
-			{
-				return;
-			}
-
-			if (value instanceof CSPDirectiveSandboxValue)
-			{
-				throw new IllegalArgumentException(
-					"A -src directive can't contain any of the sandbox directive values");
-			}
-
-			String strValue = value.render(null, null);
-			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);
-			}
-			catch (URISyntaxException urise)
-			{
-				throw new IllegalArgumentException("Illegal URI for -src directive", urise);
-			}
-		}
-
-		/**
-		 * @return The CSPDirective constant whose value-parameter equals the input-parameter or
-		 *         {@code null} if none can be found.
-		 */
-		public static CSPDirective fromValue(String value)
-		{
-			if (Strings.isEmpty(value))
-				return null;
-			for (int i = 0; i < values().length; i++)
-			{
-				if (value.equals(values()[i].getValue()))
-					return values()[i];
-			}
-			return null;
+			return mustProtectPageRequest((IPageClassRequestHandler) handler);
 		}
+		return !(handler instanceof BufferedResponseRequestHandler);
 	}
-
-	private enum CSPHeaderMode
-	{
-		BLOCKING("Content-Security-Policy"),
-		REPORT_ONLY("Content-Security-Policy-Report-Only");
-
-		private final String header;
-
-		private CSPHeaderMode(String header)
-		{
-			this.header = header;
-		}
-
-		public String getHeader()
-		{
-			return header;
-		}
-
-		public String getLegacyHeader()
-		{
-			return "X-" + getHeader();
-		}
-	}
-
-	public class CSPConfiguration
-	{
-		private CSPHeaderMode mode;
-
-		private Map<CSPDirective, List<CSPRenderable>> directives =
-			new EnumMap<>(CSPDirective.class);
-
-		private boolean addLegacyHeaders = false;
-
-		private CSPConfiguration(CSPHeaderMode mode)
-		{
-			this.mode = mode;
-		}
-
-		public CSPHeaderMode getMode()
-		{
-			return mode;
-		}
-
-		/**
-		 * True when legacy headers should be added.
-		 * 
-		 * @return True when legacy headers should be added.
-		 */
-		public boolean isAddLegacyHeaders()
-		{
-			return addLegacyHeaders;
-		}
-
-		/**
-		 * Enable legacy {@code X-Content-Security-Policy} headers for older browsers, such as IE.
-		 * 
-		 * @param addLegacyHeaders
-		 *            True when the legacy headers should be added.
-		 * @return {@code this} for chaining
-		 */
-		public CSPConfiguration setAddLegacyHeaders(boolean addLegacyHeaders)
-		{
-			this.addLegacyHeaders = addLegacyHeaders;
-			return this;
-		}
-
-		public CSPConfiguration addDirective(CSPDirective directive, CSPDirectiveSrcValue... values)
-		{
-			for (CSPDirectiveSrcValue value : values)
-			{
-				doAddDirective(directive, value);
-			}
-			return this;
-		}
-
-		/**
-		 * Adds any of the default values to the sandbox directive for the 'blocking' CSP header.
-		 * Use {@link #addBlockingDirective(CSPDirective, String...)} with the sandbox
-		 * {@link CSPDirective} and a single empty string (<em>not</em> {@code null}) to add the
-		 * empty sandbox directive.
-		 */
-		public CSPConfiguration addDirective(CSPDirective sandboxDirective,
-				CSPDirectiveSandboxValue... values)
-		{
-			for (CSPDirectiveSandboxValue value : values)
-			{
-				doAddDirective(sandboxDirective, value);
-			}
-			return this;
-		}
-
-		/**
-		 * Adds any value to a directive for the 'blocking' CSP header. Use
-		 * {@link #addBlockingDirective(CSPDirective, CSPDirectiveSandboxValue...)} and
-		 * {@link #addBlockingDirective(CSPDirective, CSPDirectiveSrcValue...)} for the default
-		 * values for the sandbox and -src directives.
-		 */
-		public CSPConfiguration addDirective(CSPDirective directive, String... values)
-		{
-			for (String value : values)
-			{
-				doAddDirective(directive, new FixedCSPDirective(value));
-			}
-			return this;
-		}
-
-		public boolean isSet()
-		{
-			return !directives.isEmpty();
-		}
-
-		private CSPConfiguration doAddDirective(CSPDirective directive, CSPRenderable value)
-		{
-			// Add backwards compatible frame-src
-			// see http://caniuse.com/#feat=contentsecuritypolicy2
-			if (CSPDirective.CHILD_SRC.equals(directive))
-			{
-				doAddDirective(CSPDirective.FRAME_SRC, value);
-			}
-			List<CSPRenderable> values =
-				directives.computeIfAbsent(directive, x -> new ArrayList<>());
-			directive.checkValueForDirective(value, values);
-			values.add(value);
-			return this;
-		}
-
-		// @returns "key1 value1a value1b; key2 value2a; key3 value3a value3b value3c"
-		public String renderHeaderValue(RequestCycle cycle)
-		{
-			return directives.entrySet()
-				.stream()
-				.map(e -> e.getKey().getValue() + " "
-					+ e.getValue()
-						.stream()
-						.map(r -> r.render(CSPSettingRequestCycleListener.this, cycle))
-						.collect(Collectors.joining(" ")))
-				.collect(Collectors.joining("; "));
-		}
-	}
-
-	private Function<Integer, byte[]> randomSupplier;
-
-	private Map<CSPHeaderMode, CSPConfiguration> configs = new HashMap<>();
-
-	public CSPSettingRequestCycleListener()
-	{
-	}
-
-	public CSPSettingRequestCycleListener(Function<Integer, byte[]> randomSupplier)
-	{
-		this.randomSupplier = randomSupplier;
-	}
-
-	public CSPConfiguration blocking()
-	{
-		return configs.computeIfAbsent(CSPHeaderMode.BLOCKING, CSPConfiguration::new);
-	}
-
-	public CSPConfiguration reporting()
-	{
-		return configs.computeIfAbsent(CSPHeaderMode.REPORT_ONLY, CSPConfiguration::new);
+	
+	protected boolean mustProtectPageRequest(IPageClassRequestHandler handler) {
+		return true;
 	}
 
 	@Override
 	public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
 	{
+		if (!mustProtect(handler))
+			return;
+		
 		WebResponse webResponse = (WebResponse) cycle.getResponse();
-		configs.values().stream().filter(CSPConfiguration::isSet).forEach(config -> {
-			String headerValue = config.renderHeaderValue(cycle);
-			webResponse.setHeader(config.getMode().getHeader(), headerValue);
+		configs.entrySet().stream().filter(entry -> entry.getValue().isSet()).forEach(entry -> {
+			CSPHeaderMode mode = entry.getKey();
+			CSPHeaderConfiguration config = entry.getValue();
+			String headerValue = config.renderHeaderValue(this, cycle);
+			webResponse.setHeader(mode.getHeader(), headerValue);
 			if (config.isAddLegacyHeaders())
-				webResponse.setHeader(config.getMode().getLegacyHeader(), headerValue);
+				webResponse.setHeader(mode.getLegacyHeader(), headerValue);
 		});
 	}
 
@@ -505,7 +130,7 @@ public class CSPSettingRequestCycleListener implements IRequestCycleListener
 		String nonce = cycle.getMetaData(NONCE_KEY);
 		if (nonce == null)
 		{
-			nonce = Base64.getEncoder().encodeToString(randomSupplier.apply(12));
+			nonce = application.getSecuritySettings().getRandomSupplier().getRandomBase64(12);
 			cycle.setMetaData(NONCE_KEY, nonce);
 		}
 		return nonce;
diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java b/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java
new file mode 100644
index 0000000..25886d6
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/FixedCSPDirective.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.csp;
+
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * A simpel CSP directive that renders the string specified.
+ * 
+ * @author papegaaij
+ */
+public class FixedCSPDirective implements CSPRenderable
+{
+	private String value;
+
+	/**
+	 * Creates a new {@code FixedCSPDirective} for the given value.
+	 * 
+	 * @param value
+	 *            the value to render;
+	 */
+	public FixedCSPDirective(String value)
+	{
+		if (Strings.isEmpty(value))
+			throw new IllegalArgumentException(
+				"CSP directive cannot have empty or null values");
+		this.value = value;
+	}
+
+	@Override
+	public String render(CSPSettingRequestCycleListener listener, RequestCycle cycle)
+	{
+		return value;
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
index 14ba223..28b292c 100644
--- a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
@@ -16,16 +16,16 @@
  */
 package org.apache.wicket.csp;
 
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.CHILD_SRC;
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.DEFAULT_SRC;
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.FRAME_SRC;
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.REPORT_URI;
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.SANDBOX;
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSandboxValue.ALLOW_FORMS;
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSandboxValue.EMPTY;
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSrcValue.NONE;
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSrcValue.SELF;
-import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSrcValue.WILDCARD;
+import static org.apache.wicket.csp.CSPDirective.CHILD_SRC;
+import static org.apache.wicket.csp.CSPDirective.DEFAULT_SRC;
+import static org.apache.wicket.csp.CSPDirective.FRAME_SRC;
+import static org.apache.wicket.csp.CSPDirective.REPORT_URI;
+import static org.apache.wicket.csp.CSPDirective.SANDBOX;
+import static org.apache.wicket.csp.CSPDirectiveSandboxValue.ALLOW_FORMS;
+import static org.apache.wicket.csp.CSPDirectiveSandboxValue.EMPTY;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.NONE;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.SELF;
+import static org.apache.wicket.csp.CSPDirectiveSrcValue.WILDCARD;
 
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -37,9 +37,6 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective;
-import org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSandboxValue;
-import org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirectiveSrcValue;
 import org.apache.wicket.mock.MockHomePage;
 import org.apache.wicket.util.tester.WicketTester;
 import org.junit.jupiter.api.Assertions;
@@ -47,7 +44,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 @SuppressWarnings("deprecation")
-public class CSPSettingRequestCycleListenerTest
+public class CSPSettingRequestCycleListenerTest extends WicketTester
 {
 	private static String HEADER_CSP = "Content-Security-Policy";
 
@@ -64,7 +61,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testNullSrcInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, (String) null);
 		});
@@ -73,7 +71,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testEmptySrcInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, "");
 		});
@@ -86,7 +85,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testInvalidSrcInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, "abc?^()-_\'xyz");
 		});
@@ -99,7 +99,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testMultipleSrcInputWithNoneIsRejected1()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, SELF, NONE);
 		});
@@ -112,7 +113,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testMultipleSrcInputWithNoneIsRejected2()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, NONE, SELF);
 		});
@@ -125,7 +127,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testMultipleSrcInputWithStarIsRejected1()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		cspListener.blocking().addDirective(DEFAULT_SRC, SELF);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, WILDCARD);
@@ -139,7 +142,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testMultipleSrcInputWithStarIsRejected2()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		cspListener.blocking().addDirective(DEFAULT_SRC, WILDCARD);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, SELF);
@@ -149,7 +153,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testWrongSrcInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, ALLOW_FORMS);
 		});
@@ -158,7 +163,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testWrongSandboxInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, SELF);
 		});
@@ -167,7 +173,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testNullSandboxInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, (String) null);
 		});
@@ -176,14 +183,16 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testEmptySandboxInputIsAccepted()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		cspListener.blocking().addDirective(SANDBOX, CSPDirectiveSandboxValue.EMPTY);
 	}
 
 	@Test
 	public void testInvalidSandboxInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, "abcxyz");
 		});
@@ -192,7 +201,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testMultipleSandboxInputWithEmptyStringIsRejected1()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		cspListener.blocking().addDirective(SANDBOX, ALLOW_FORMS);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, EMPTY);
@@ -202,7 +212,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testMultipleSandboxInputWithEmptyStringIsRejected2()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		cspListener.blocking().addDirective(SANDBOX, EMPTY);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, ALLOW_FORMS);
@@ -212,7 +223,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testNullReportUriInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(REPORT_URI, (String) null);
 		});
@@ -221,7 +233,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testEmptyReportUriInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(REPORT_URI, "");
 		});
@@ -230,7 +243,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testInvalidReportUriInputIsRejected()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(REPORT_URI, "abc?^()-_\'xyz");
 		});
@@ -239,12 +253,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testAllCSPSrcDefaultEnumsAreSetCorrectly() throws NoSuchAlgorithmException
 	{
-		SecureRandom random = SecureRandom.getInstanceStrong();
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener(length -> {
-			byte[] ret = new byte[length];
-			random.nextBytes(ret);
-			return ret;
-		});
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 
 		final int cspDirectiveCount = CSPDirective.values().length;
 		final int cspDirectiveSrcValueCount = CSPDirectiveSrcValue.values().length;
@@ -273,7 +283,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testCSPReportUriDirectiveSetCorrectly()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		cspListener.blocking().addDirective(REPORT_URI, "http://report.example.com");
 		cspListener.reporting().addDirective(REPORT_URI, "/example-report-uri");
 
@@ -288,14 +299,15 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testCSPSandboxDirectiveSetCorrectly()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		final int cspSandboxDirectiveValueCount = CSPDirectiveSandboxValue.values().length;
 		for (int i = 0; i < cspSandboxDirectiveValueCount; i++)
 		{
 			final CSPDirectiveSandboxValue cspDirectiveValue = CSPDirectiveSandboxValue.values()[i];
 			if (cspDirectiveValue.equals(CSPDirectiveSandboxValue.EMPTY))
 				continue;
-			
+
 			cspListener.blocking().addDirective(SANDBOX, cspDirectiveValue);
 			cspListener.reporting().addDirective(SANDBOX, cspDirectiveValue);
 		}
@@ -316,7 +328,8 @@ public class CSPSettingRequestCycleListenerTest
 	@Test
 	public void testChildSrcDirectiveAlsoSetsFrameSrcDirective()
 	{
-		CSPSettingRequestCycleListener cspListener = new CSPSettingRequestCycleListener();
+		CSPSettingRequestCycleListener cspListener =
+			new CSPSettingRequestCycleListener(getApplication());
 		cspListener.blocking().addDirective(CHILD_SRC, SELF);
 		cspListener.reporting().addDirective(CHILD_SRC, SELF);
 		StringBuffer headerErrors = checkHeaders(cspListener);
@@ -460,5 +473,4 @@ public class CSPSettingRequestCycleListenerTest
 
 		return false;
 	}
-
 }


[wicket] 08/08: WICKET-6727: ContentSecurityPolicyEnforcer must not protect non-webresponses

Posted by pa...@apache.org.
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

commit 1e509dda61b228449b7e42692ae1f546d25c4791
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Fri Jan 17 22:23:42 2020 +0100

    WICKET-6727: ContentSecurityPolicyEnforcer must not protect non-webresponses
---
 .../main/java/org/apache/wicket/csp/ContentSecurityPolicyEnforcer.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 4d2677c..5fb65e7 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
@@ -130,7 +130,7 @@ public class ContentSecurityPolicyEnforcer implements IRequestCycleListener
 	@Override
 	public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
 	{
-		if (!mustProtect(handler))
+		if (!mustProtect(handler) || !(cycle.getResponse() instanceof WebResponse))
 			return;
 		
 		WebResponse webResponse = (WebResponse) cycle.getResponse();


[wicket] 03/08: WICKET-6727: fix license headers

Posted by pa...@apache.org.
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

commit 3afb562ddb73e73d792a9298ae3d8037371d196c
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Thu Jan 16 09:27:23 2020 +0100

    WICKET-6727: fix license headers
---
 .../wicket/csp/CSPSettingRequestCycleListener.java | 16 ++++++++++++++++
 .../csp/CspNonceHeaderResponseDecorator.java       | 22 ++++++++++++++--------
 .../csp/CSPSettingRequestCycleListenerTest.java    | 16 ++++++++++++++++
 3 files changed, 46 insertions(+), 8 deletions(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
index 47c6cdd..d688990 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPSettingRequestCycleListener.java
@@ -1,3 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.apache.wicket.csp;
 
 import java.net.URI;
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
index 5aabcda..da87059 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
@@ -1,12 +1,18 @@
 /*
- * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License. You may obtain a
- * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
- * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
- * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
- * for the specific language governing permissions and limitations under the License.
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 package org.apache.wicket.csp;
 
diff --git a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
index f347f8a..14ba223 100644
--- a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
@@ -1,3 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.apache.wicket.csp;
 
 import static org.apache.wicket.csp.CSPSettingRequestCycleListener.CSPDirective.CHILD_SRC;


[wicket] 05/08: WICKET-6727: do not extend WicketTester but WicketTestCase

Posted by pa...@apache.org.
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

commit 6392edaecf144fb5000ed6fe4a55ddb7ad3e1d9e
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Fri Jan 17 09:24:21 2020 +0100

    WICKET-6727: do not extend WicketTester but WicketTestCase
---
 .../csp/CSPSettingRequestCycleListenerTest.java    | 45 +++++++++++-----------
 1 file changed, 23 insertions(+), 22 deletions(-)

diff --git a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
index 28b292c..d4893cd 100644
--- a/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/csp/CSPSettingRequestCycleListenerTest.java
@@ -38,13 +38,14 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.apache.wicket.mock.MockHomePage;
+import org.apache.wicket.util.tester.WicketTestCase;
 import org.apache.wicket.util.tester.WicketTester;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 @SuppressWarnings("deprecation")
-public class CSPSettingRequestCycleListenerTest extends WicketTester
+public class CSPSettingRequestCycleListenerTest extends WicketTestCase
 {
 	private static String HEADER_CSP = "Content-Security-Policy";
 
@@ -62,7 +63,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testNullSrcInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, (String) null);
 		});
@@ -72,7 +73,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testEmptySrcInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, "");
 		});
@@ -86,7 +87,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testInvalidSrcInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, "abc?^()-_\'xyz");
 		});
@@ -100,7 +101,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testMultipleSrcInputWithNoneIsRejected1()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, SELF, NONE);
 		});
@@ -114,7 +115,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testMultipleSrcInputWithNoneIsRejected2()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, NONE, SELF);
 		});
@@ -128,7 +129,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testMultipleSrcInputWithStarIsRejected1()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		cspListener.blocking().addDirective(DEFAULT_SRC, SELF);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, WILDCARD);
@@ -143,7 +144,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testMultipleSrcInputWithStarIsRejected2()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		cspListener.blocking().addDirective(DEFAULT_SRC, WILDCARD);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, SELF);
@@ -154,7 +155,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testWrongSrcInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(DEFAULT_SRC, ALLOW_FORMS);
 		});
@@ -164,7 +165,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testWrongSandboxInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, SELF);
 		});
@@ -174,7 +175,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testNullSandboxInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, (String) null);
 		});
@@ -184,7 +185,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testEmptySandboxInputIsAccepted()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		cspListener.blocking().addDirective(SANDBOX, CSPDirectiveSandboxValue.EMPTY);
 	}
 
@@ -192,7 +193,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testInvalidSandboxInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, "abcxyz");
 		});
@@ -202,7 +203,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testMultipleSandboxInputWithEmptyStringIsRejected1()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		cspListener.blocking().addDirective(SANDBOX, ALLOW_FORMS);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, EMPTY);
@@ -213,7 +214,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testMultipleSandboxInputWithEmptyStringIsRejected2()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		cspListener.blocking().addDirective(SANDBOX, EMPTY);
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(SANDBOX, ALLOW_FORMS);
@@ -224,7 +225,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testNullReportUriInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(REPORT_URI, (String) null);
 		});
@@ -234,7 +235,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testEmptyReportUriInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(REPORT_URI, "");
 		});
@@ -244,7 +245,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testInvalidReportUriInputIsRejected()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		Assertions.assertThrows(IllegalArgumentException.class, () -> {
 			cspListener.blocking().addDirective(REPORT_URI, "abc?^()-_\'xyz");
 		});
@@ -254,7 +255,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testAllCSPSrcDefaultEnumsAreSetCorrectly() throws NoSuchAlgorithmException
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 
 		final int cspDirectiveCount = CSPDirective.values().length;
 		final int cspDirectiveSrcValueCount = CSPDirectiveSrcValue.values().length;
@@ -284,7 +285,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testCSPReportUriDirectiveSetCorrectly()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		cspListener.blocking().addDirective(REPORT_URI, "http://report.example.com");
 		cspListener.reporting().addDirective(REPORT_URI, "/example-report-uri");
 
@@ -300,7 +301,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testCSPSandboxDirectiveSetCorrectly()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		final int cspSandboxDirectiveValueCount = CSPDirectiveSandboxValue.values().length;
 		for (int i = 0; i < cspSandboxDirectiveValueCount; i++)
 		{
@@ -329,7 +330,7 @@ public class CSPSettingRequestCycleListenerTest extends WicketTester
 	public void testChildSrcDirectiveAlsoSetsFrameSrcDirective()
 	{
 		CSPSettingRequestCycleListener cspListener =
-			new CSPSettingRequestCycleListener(getApplication());
+			new CSPSettingRequestCycleListener(tester.getApplication());
 		cspListener.blocking().addDirective(CHILD_SRC, SELF);
 		cspListener.reporting().addDirective(CHILD_SRC, SELF);
 		StringBuffer headerErrors = checkHeaders(cspListener);