Add support for detecting CSP violations during unit tests in GWT.
This CL defines and implements a config property
"strictCspTestingEnabled" that can be set in .gwt.xml
config files. When running unit tests using a module where
this option is enabled, the GWT test runner enforce a strict
(https://csp.withgoogle.com/docs/strict-csp.html) CSP policy on the
running page.
Change-Id: I387de41fb2019086c741d9c81077f54b0e8595ea
Review-Link: https://gwt-review.googlesource.com/#/c/19080/
diff --git a/user/src/com/google/gwt/junit/JUnit.gwt.xml b/user/src/com/google/gwt/junit/JUnit.gwt.xml
index e7fc0f5..54f1ba4 100644
--- a/user/src/com/google/gwt/junit/JUnit.gwt.xml
+++ b/user/src/com/google/gwt/junit/JUnit.gwt.xml
@@ -55,4 +55,12 @@
<servlet path="/junithost/*" class="com.google.gwt.junit.server.JUnitHostImpl"/>
<servlet path="/remote_logging" class="com.google.gwt.junit.server.JUnitHostImpl"/>
+ <!--
+ gwt.cspTestingEnabled enables checking for CSP violations during unit tests.
+ When this is turned on, a nonce-based policy employing 'strict-dynamic' will
+ be enforced on the hosting page. If code run during a unit test violates
+ this policy, an error will be signaled.
+ -->
+ <define-property name="gwt.strictCspTestingEnabled" values="true, false"/>
+ <set-property name="gwt.strictCspTestingEnabled" value="false"/>
</module>
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index e8838c0..0a161fb 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -81,9 +81,11 @@
import junit.framework.TestResult;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.webapp.WebAppContext;
import java.io.File;
+import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -100,7 +102,15 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
/**
* This class is responsible for hosting JUnit test case execution. There are
@@ -1129,6 +1139,13 @@
}
}
}
+ BindingProperty strictCspTestingEnabledProperty =
+ module.getProperties().findBindingProp("gwt.strictCspTestingEnabled");
+
+ if (strictCspTestingEnabledProperty != null &&
+ "true".equals(strictCspTestingEnabledProperty.getConstrainedValue())) {
+ addCspFilter("/" + module.getName() + "/*");
+ }
if (developmentMode) {
// BACKWARDS COMPATIBILITY: many linkers currently fail in dev mode.
try {
@@ -1151,6 +1168,32 @@
}
}
+ /**
+ * Adds a filter to the server that automatically adds Content-Security-Policy HTTP headers to
+ * responses on the given path.
+ */
+ private void addCspFilter(String path) {
+ wac.addFilter(new FilterHolder(new Filter() {
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+ httpResponse.addHeader("Content-Security-Policy",
+ "script-src 'nonce-gwt-nonce' 'unsafe-inline' 'strict-dynamic' https: http: " +
+ "'unsafe-eval' 'report-sample'");
+
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void init(FilterConfig arg0) throws ServletException { }
+
+ @Override
+ public void destroy() { }
+ }), path, EnumSet.of(DispatcherType.REQUEST));
+ }
+
private void checkArgs() {
if (runStyle.getTries() > 1
&& !(batchingStrategy instanceof NoBatchingStrategy)) {
diff --git a/user/src/com/google/gwt/junit/public/junit.html b/user/src/com/google/gwt/junit/public/junit.html
index 33cdeca..2978fa5 100644
--- a/user/src/com/google/gwt/junit/public/junit.html
+++ b/user/src/com/google/gwt/junit/public/junit.html
@@ -21,7 +21,7 @@
<meta name='gwt:onPropertyErrorFn' content='junitOnPropertyErrorFn'>
</head>
<body>
-<script>
+<script nonce="gwt-nonce">
function junitOnLoadErrorFn(moduleName, e) {
junitLaunchError('Failed to load module ' + moduleName + ': ' + e);
}
@@ -57,7 +57,7 @@
moduleName = moduleName.substr(0, pos);
pos = moduleName.lastIndexOf('/');
moduleName = moduleName.substr(pos + 1);
- document.write('<script language="javascript" src="' + encodeURIComponent(moduleName) + '.nocache.js"><\/script>');
+ document.write('<script language="javascript" nonce="gwt-nonce" src="' + encodeURIComponent(moduleName) + '.nocache.js"><\/script>');
}
loadSelectionScript();
</script>
diff --git a/user/test/com/google/gwt/dom/DOMTest.gwt.xml b/user/test/com/google/gwt/dom/DOMTest.gwt.xml
index 85f4cc8..1deafa7 100644
--- a/user/test/com/google/gwt/dom/DOMTest.gwt.xml
+++ b/user/test/com/google/gwt/dom/DOMTest.gwt.xml
@@ -14,4 +14,6 @@
<module>
<inherits name="com.google.gwt.dom.DOM"/>
<public path="public-test"/>
+ <inherits name="com.google.gwt.junit.JUnit"/>
+ <set-property name="gwt.strictCspTestingEnabled" value="true"/>
</module>
diff --git a/user/test/com/google/gwt/junit/JUnitSuite.java b/user/test/com/google/gwt/junit/JUnitSuite.java
index 877657f..c5d28ce 100644
--- a/user/test/com/google/gwt/junit/JUnitSuite.java
+++ b/user/test/com/google/gwt/junit/JUnitSuite.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.junit;
+import com.google.gwt.junit.client.CspTest;
import com.google.gwt.junit.client.DevModeOnCompiledScriptTest;
import com.google.gwt.junit.client.GWTTestCaseAsyncTest;
import com.google.gwt.junit.client.GWTTestCaseInheritanceTest;
@@ -68,6 +69,8 @@
suite.addTestSuite(JUnitMessageQueueTest.class);
suite.addTestSuite(JUnitShellTest.class);
+ suite.addTestSuite(CspTest.class);
+
return suite;
}
}
diff --git a/user/test/com/google/gwt/junit/JUnitTest.gwt.xml b/user/test/com/google/gwt/junit/JUnitTest.gwt.xml
index b9cd010..653d53b 100644
--- a/user/test/com/google/gwt/junit/JUnitTest.gwt.xml
+++ b/user/test/com/google/gwt/junit/JUnitTest.gwt.xml
@@ -14,4 +14,6 @@
<module>
<inherits name="com.google.gwt.user.User"/>
+ <inherits name="com.google.gwt.junit.JUnit"/>
+ <set-property name="gwt.strictCspTestingEnabled" value="true"/>
</module>
diff --git a/user/test/com/google/gwt/junit/client/CspTest.java b/user/test/com/google/gwt/junit/client/CspTest.java
new file mode 100644
index 0000000..f078ee1
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/CspTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed 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 com.google.gwt.junit.client;
+
+import com.google.gwt.dom.client.ButtonElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+
+/**
+ * This test case checks that the test runner correctly sets up CSP when enabled.
+ */
+public class CspTest extends GWTTestCase {
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.junit.JUnitTest";
+ }
+
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testCsp() {
+ initClickDidFire();
+
+ ButtonElement btn = Document.get().createPushButtonElement();
+ btn.setAttribute("onclick", "clickDidFire = true");
+ btn.click();
+
+ assertFalse(getClickDidFire());
+ }
+
+ private native void initClickDidFire() /*-{
+ $wnd.clickDidFire = false;
+ }-*/;
+
+ private native boolean getClickDidFire() /*-{
+ return $wnd.clickDidFire;
+ }-*/;
+
+}
diff --git a/user/test/com/google/gwt/user/UserTest.gwt.xml b/user/test/com/google/gwt/user/UserTest.gwt.xml
index 3656da2..b81506c 100644
--- a/user/test/com/google/gwt/user/UserTest.gwt.xml
+++ b/user/test/com/google/gwt/user/UserTest.gwt.xml
@@ -13,6 +13,9 @@
<!-- limitations under the License. -->
<module>
- <inherits name="com.google.gwt.user.User"/>
- <public path="public-test"/>
+ <inherits name="com.google.gwt.user.User"/>
+ <public path="public-test"/>
+ <set-property name="gwt.cspCompatModeEnabled" value="true"/>
+ <inherits name="com.google.gwt.junit.JUnit"/>
+ <set-property name="gwt.strictCspTestingEnabled" value="true"/>
</module>