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/19 20:36:24 UTC

[wicket] branch csp updated: WICKET-6727: Updated documentation, fixed websocket, fixed examples

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

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


The following commit(s) were added to refs/heads/csp by this push:
     new ea1e129  WICKET-6727: Updated documentation, fixed websocket, fixed examples
ea1e129 is described below

commit ea1e129b94b1327c35a0c31ae7631e27d8c540d4
Author: Emond Papegaaij <em...@topicus.nl>
AuthorDate: Sun Jan 19 21:36:04 2020 +0100

    WICKET-6727: Updated documentation, fixed websocket, fixed examples
---
 .../java/org/apache/wicket/csp/CSPDirective.java   | 12 +++--
 .../apache/wicket/csp/CSPHeaderConfiguration.java  | 60 ++++++++++++++++++----
 .../wicket/csp/ContentSecurityPolicyEnforcer.java  | 44 ++++++++--------
 .../org/apache/wicket/csp/FixedCSPDirective.java   |  5 +-
 .../org/apache/wicket/mock/MockWebResponse.java    |  6 +++
 .../org/apache/wicket/page/PartialPageUpdate.java  |  6 +++
 .../wicket/protocol/http/BufferedWebResponse.java  |  6 +++
 .../protocol/http/HeaderBufferingWebResponse.java  |  6 +++
 .../protocol/http/servlet/ServletWebResponse.java  |  6 +++
 .../wicket/examples/WicketExampleApplication.java  |  3 ++
 .../apache/wicket/examples/WicketExamplePage.html  |  4 +-
 .../CustomLoadedTemplate.html                      |  4 +-
 .../wicket/protocol/ws/api/WebSocketResponse.java  |  5 ++
 .../apache/wicket/request/http/WebResponse.java    |  9 ++++
 14 files changed, 135 insertions(+), 41 deletions(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirective.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirective.java
index 1ea52c1..0edf6b9 100644
--- a/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirective.java
+++ b/wicket-core/src/main/java/org/apache/wicket/csp/CSPDirective.java
@@ -40,8 +40,11 @@ public enum CSPDirective
 	MEDIA_SRC("media-src"),
 	CHILD_SRC("child-src"),
 	FRAME_ANCESTORS("frame-ancestors"),
+	/**
+	 * @deprecated Use CHILD-SRC, this will also add FRAME-SRC automatically for compatibility with
+	 *             older browsers.
+	 */
 	@Deprecated
-	/** @deprecated Gebruik CHILD-SRC, deze zet ook automatisch FRAME-SRC. */
 	FRAME_SRC("frame-src"),
 	SANDBOX("sandbox")
 	{
@@ -156,8 +159,7 @@ public enum CSPDirective
 		}
 
 		// strip off "*." so "*.example.com" becomes "example.com" and we can check if
-		// it
-		// is a valid uri
+		// it is a valid uri
 		if (strValue.startsWith("*."))
 		{
 			strValue = strValue.substring(2);
@@ -180,11 +182,15 @@ public enum CSPDirective
 	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/CSPHeaderConfiguration.java b/wicket-core/src/main/java/org/apache/wicket/csp/CSPHeaderConfiguration.java
index 91521ba..6bee9fd 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
@@ -40,6 +40,10 @@ import java.util.stream.Collectors;
 import org.apache.wicket.request.cycle.RequestCycle;
 
 /**
+ * {@code CSPHeaderConfiguration} contains the configuration for a Content-Security-Policy header.
+ * This configuration is constructed using the available {@link CSPDirective}s. An number of default
+ * profiles is provided. These profiles can be used as a basis for a specific CSP. Extra directives
+ * can be added or exising directives modified.
  * 
  * @author papegaaij
  */
@@ -54,33 +58,69 @@ public class CSPHeaderConfiguration
 	public CSPHeaderConfiguration()
 	{
 	}
-	
-	public CSPHeaderConfiguration disabled() {
+
+	/**
+	 * Removes all directives from the CSP, returning an empty configuration.
+	 * 
+	 * @return {@code this} for chaining.
+	 */
+	public CSPHeaderConfiguration disabled()
+	{
 		return clear();
 	}
-
+	
+	/**
+	 * Builds a CSP configuration with the following directives: {@code default-src 'none';}
+	 * {@code script-src 'self' 'unsafe-inline' 'unsafe-eval';}
+	 * {@code style-src 'self' 'unsafe-inline';} {@code img-src 'self';} {@code connect-src 'self';}
+	 * {@code font-src 'self';} {@code manifest-src 'self';} {@code child-src 'self';}
+	 * {@code frame-src 'self'}. This will allow resources to be loaded from {@code 'self'} (the
+	 * current host). In addition, unsafe inline Javascript, {@code eval()} and inline CSS is
+	 * allowed.
+	 * 
+	 * It is recommended to not allow {@code unsafe-inline} or {@code unsafe-eval}, because those
+	 * can be used to trigger XSS attacks in your application (often in combination with another
+	 * bug). Because older application often rely on inline scripting and styling, this CSP can be
+	 * used as a stepping stone for older Wicket applications, before switching to {@link #strict}.
+	 * Using a CSP with unsafe directives is still more secure than using no CSP at all.
+	 * 
+	 * @return {@code this} for chaining.
+	 */
 	public CSPHeaderConfiguration unsafeInline()
 	{
 		return clear().addDirective(DEFAULT_SRC, NONE)
-			.addDirective(STYLE_SRC, SELF, UNSAFE_INLINE)
 			.addDirective(SCRIPT_SRC, SELF, UNSAFE_INLINE, UNSAFE_EVAL)
+			.addDirective(STYLE_SRC, SELF, UNSAFE_INLINE)
 			.addDirective(IMG_SRC, SELF)
+			.addDirective(CONNECT_SRC, SELF)
 			.addDirective(FONT_SRC, SELF)
-			.addDirective(CHILD_SRC, SELF)
 			.addDirective(MANIFEST_SRC, SELF)
-			.addDirective(CONNECT_SRC, SELF);
+			.addDirective(CHILD_SRC, SELF);
 	}
-	
+
+	/**
+	 * Builds a strict, very secure CSP configuration with the following directives:
+	 * {@code default-src 'none';} {@code script-src 'strict-dynamic' 'nonce-XYZ';}
+	 * {@code style-src 'nonce-XYZ';} {@code img-src 'self';} {@code connect-src 'self';}
+	 * {@code font-src 'self';} {@code manifest-src 'self';} {@code child-src 'self';}
+	 * {@code frame-src 'self'}. This will allow most resources to be loaded from {@code 'self'}
+	 * (the current host). Scripts and styles are only allowed when rendered with the correct nonce.
+	 * Wicket will automatically add the nonces to the {@code script} and {@code link} (CSS)
+	 * elements and to the headers.
+	 * 
+	 * @return {@code this} for chaining.
+	 */
 	public CSPHeaderConfiguration strict()
 	{
 		return clear().addDirective(DEFAULT_SRC, NONE)
+				.addDirective(SCRIPT_SRC, STRICT_DYNAMIC, NONCE)
 			.addDirective(STYLE_SRC, NONCE)
-			.addDirective(SCRIPT_SRC, STRICT_DYNAMIC, NONCE)
 			.addDirective(IMG_SRC, SELF)
+			.addDirective(CONNECT_SRC, SELF)
 			.addDirective(FONT_SRC, SELF)
-			.addDirective(CHILD_SRC, SELF)
 			.addDirective(MANIFEST_SRC, SELF)
-			.addDirective(CONNECT_SRC, SELF);
+			.addDirective(CHILD_SRC, SELF)
+			;
 	}
 
 	/**
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 5fb65e7..dad360c 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
@@ -36,32 +36,21 @@ import org.apache.wicket.util.lang.Args;
  * 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:
- *
+ * Build the CSP configuration like this:
+ * 
  * <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.getCSP().blocking().clear()
+ *      .addDirective(CSPDirective.DEFAULT_SRC, CSPDirectiveSrcValue.NONE)
+ *      .addDirective(CSPDirective.SCRIPT_SRC, CSPDirectiveSrcValue.SELF)
+ *      .addDirective(CSPDirective.IMG_SRC, CSPDirectiveSrcValue.SELF)
+ *      .addDirective(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));
+ *  myApplication.getCSP().reporting().strict();
  * 	}
  * </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 {@link CSPHeaderConfiguration} for more details on specifying the configuration.
  *
  * @see "http://www.w3.org/TR/CSP2/"
  * @see "https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives"
@@ -114,7 +103,9 @@ public class ContentSecurityPolicyEnforcer implements IRequestCycleListener
 	protected boolean mustProtect(IRequestHandler handler)
 	{
 		if (handler instanceof IRequestHandlerDelegate)
+		{
 			return mustProtect(((IRequestHandlerDelegate) handler).getDelegateHandler());
+		}
 		if (handler instanceof IPageClassRequestHandler)
 		{
 			return mustProtectPageRequest((IPageClassRequestHandler) handler);
@@ -131,16 +122,25 @@ public class ContentSecurityPolicyEnforcer implements IRequestCycleListener
 	public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
 	{
 		if (!mustProtect(handler) || !(cycle.getResponse() instanceof WebResponse))
+		{
 			return;
-		
+		}
+
 		WebResponse webResponse = (WebResponse) cycle.getResponse();
+		if (!webResponse.isHeaderSupported())
+		{
+			return;
+		}
+
 		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(mode.getLegacyHeader(), headerValue);
+			}
 		});
 	}
 	
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 a091b6a..dc9865c 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
@@ -37,8 +37,9 @@ public class FixedCSPDirective implements CSPRenderable
 	public FixedCSPDirective(String value)
 	{
 		if (Strings.isEmpty(value))
-			throw new IllegalArgumentException(
-				"CSP directive cannot have empty or null values");
+		{
+			throw new IllegalArgumentException("CSP directive cannot have empty or null values");
+		}
 		this.value = value;
 	}
 
diff --git a/wicket-core/src/main/java/org/apache/wicket/mock/MockWebResponse.java b/wicket-core/src/main/java/org/apache/wicket/mock/MockWebResponse.java
index ba65975..6021e92 100644
--- a/wicket-core/src/main/java/org/apache/wicket/mock/MockWebResponse.java
+++ b/wicket-core/src/main/java/org/apache/wicket/mock/MockWebResponse.java
@@ -166,6 +166,12 @@ public class MockWebResponse extends WebResponse
 	}
 
 	@Override
+	public boolean isHeaderSupported()
+	{
+		return true;
+	}
+
+	@Override
 	public void setHeader(String name, String value)
 	{
 		internalSetContentType(name, value);
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java b/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java
index 34f77ef..cd777ac 100644
--- a/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java
+++ b/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java
@@ -806,6 +806,12 @@ public abstract class PartialPageUpdate
 		}
 
 		@Override
+		public boolean isHeaderSupported()
+		{
+			return originalResponse.isHeaderSupported();
+		}
+
+		@Override
 		public void setHeader(String name, String value)
 		{
 			originalResponse.setHeader(name, value);
diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/BufferedWebResponse.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/BufferedWebResponse.java
index 23de6ea..43458e0 100644
--- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/BufferedWebResponse.java
+++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/BufferedWebResponse.java
@@ -193,6 +193,12 @@ public class BufferedWebResponse extends WebResponse implements IMetaDataBufferi
 	}
 
 	@Override
+	public boolean isHeaderSupported()
+	{
+		return originalResponse.isHeaderSupported();
+	}
+
+	@Override
 	public void setHeader(String name, String value)
 	{
 		actions.add(ActionType.HEADER.action(res -> res.setHeader(name, value)));
diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/HeaderBufferingWebResponse.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/HeaderBufferingWebResponse.java
index 993f959..8e31f4e 100644
--- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/HeaderBufferingWebResponse.java
+++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/HeaderBufferingWebResponse.java
@@ -136,6 +136,12 @@ class HeaderBufferingWebResponse extends WebResponse implements IMetaDataBufferi
 	}
 
 	@Override
+	public boolean isHeaderSupported()
+	{
+		return getMetaResponse().isHeaderSupported();
+	}
+
+	@Override
 	public void setHeader(String name, String value)
 	{
 		getMetaResponse().setHeader(name, value);
diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/servlet/ServletWebResponse.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/servlet/ServletWebResponse.java
index 8f11fa0..fce9056 100644
--- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/servlet/ServletWebResponse.java
+++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/servlet/ServletWebResponse.java
@@ -89,6 +89,12 @@ public class ServletWebResponse extends WebResponse
 	}
 
 	@Override
+	public boolean isHeaderSupported()
+	{
+		return true;
+	}
+	
+	@Override
 	public void setHeader(String name, String value)
 	{
 		httpServletResponse.setHeader(name, value);
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java
index 3794cbf..ee1180b 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java
@@ -16,6 +16,7 @@
  */
 package org.apache.wicket.examples;
 
+import org.apache.wicket.csp.CSPDirective;
 import org.apache.wicket.protocol.http.WebApplication;
 import org.apache.wicket.resource.CssUrlReplacer;
 import org.apache.wicket.settings.SecuritySettings;
@@ -57,5 +58,7 @@ public abstract class WicketExampleApplication extends WebApplication
 		getDebugSettings().setDevelopmentUtilitiesEnabled(true);
 		
 		getResourceSettings().setCssCompressor(new CssUrlReplacer());
+		getCsp().blocking().addDirective(CSPDirective.STYLE_SRC, "https://maxcdn.bootstrapcdn.com")
+				.addDirective(CSPDirective.FONT_SRC, "https://maxcdn.bootstrapcdn.com");
 	}
 }
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExamplePage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExamplePage.html
index b07d0a3..d57c44d 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExamplePage.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExamplePage.html
@@ -8,7 +8,7 @@
 <link rel="shortcut icon" href="favicon.ico"
 	type="image/vnd.microsoft.icon" />
 <link
-	href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
+	href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
 	rel="stylesheet" />
 </head>
 
@@ -28,4 +28,4 @@
 		</div>
 	</main>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/customresourceloading/CustomLoadedTemplate.html b/wicket-examples/src/main/java/org/apache/wicket/examples/customresourceloading/CustomLoadedTemplate.html
index af4d41d..7d119c1 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/customresourceloading/CustomLoadedTemplate.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/customresourceloading/CustomLoadedTemplate.html
@@ -9,7 +9,7 @@
 <link rel="shortcut icon" href="favicon.ico"
     type="image/vnd.microsoft.icon" />
 <link
-    href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
+    href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
     rel="stylesheet" />
 
 <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
@@ -42,4 +42,4 @@
         </div> 
     </main>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketResponse.java b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketResponse.java
index 325eeab..af1c938 100644
--- a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketResponse.java
+++ b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketResponse.java
@@ -150,6 +150,11 @@ public class WebSocketResponse extends WebResponse
 	}
 
 	@Override
+	public boolean isHeaderSupported() {
+		return false;
+	}
+	
+	@Override
 	public void setHeader(String name, String value)
 	{
 		throw new UnsupportedOperationException();
diff --git a/wicket-request/src/main/java/org/apache/wicket/request/http/WebResponse.java b/wicket-request/src/main/java/org/apache/wicket/request/http/WebResponse.java
index 322f4ff..89a1b3e 100644
--- a/wicket-request/src/main/java/org/apache/wicket/request/http/WebResponse.java
+++ b/wicket-request/src/main/java/org/apache/wicket/request/http/WebResponse.java
@@ -53,6 +53,15 @@ public abstract class WebResponse extends Response
 	public abstract void clearCookie(final Cookie cookie);
 
 	/**
+	 * Indicates if the response supports setting headers. When this method returns
+	 * false, {@link #setHeader(String, String)} and its variations will thrown an
+	 * {@code UnsupportedOperationException}.
+	 * 
+	 * @return True when this {@code WebResponse} supports setting headers.
+	 */
+	public abstract boolean isHeaderSupported();
+	
+	/**
 	 * Set a header to the string value in the servlet response stream.
 	 * 
 	 * @param name