Provide csp_nonce to Soy Templates The CSP nonce is only provided if available. If a filter is installed that attaches "nonce" attribute to request. For example in google deployment of gerrit we have such filter that sets nonce on all requests before they are processed by a servelet. CSP helps protects sites from XSS attacks. On Google hosts we set CSP headers that require all script elements to be accompanied by nonce (per request generated random string). Soy templates have a built in support for attaching nonce, as long as the value is provided using Inject Data mechanism. Google-Bud-Id: b/33429040 Release-Notes: skip Change-Id: Ifa3a07b8c77918a8a4ab48775b68e4f3b39bd3cb
diff --git a/java/com/google/gitiles/BaseServlet.java b/java/com/google/gitiles/BaseServlet.java index 91c66bb..860ffd8 100644 --- a/java/com/google/gitiles/BaseServlet.java +++ b/java/com/google/gitiles/BaseServlet.java
@@ -228,7 +228,7 @@ throws IOException { req.setAttribute(STREAMING_ATTRIBUTE, true); return renderer.renderHtmlStreaming( - res, false, templateName, startHtmlResponse(req, res, soyData)); + req, res, false, templateName, startHtmlResponse(req, res, soyData)); } /** @@ -261,7 +261,7 @@ gzip = true; } return renderer.renderHtmlStreaming( - res, gzip, templateName, startHtmlResponse(req, res, soyData)); + req, res, gzip, templateName, startHtmlResponse(req, res, soyData)); } private Map<String, ?> startHtmlResponse(
diff --git a/java/com/google/gitiles/Renderer.java b/java/com/google/gitiles/Renderer.java index 65aea7e..3b85e3d 100644 --- a/java/com/google/gitiles/Renderer.java +++ b/java/com/google/gitiles/Renderer.java
@@ -37,6 +37,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; @@ -148,17 +149,18 @@ return h.hash(); } - public String renderHtml(String templateName, Map<String, ?> soyData) { - return newRenderer(templateName).setData(soyData).renderHtml().get().toString(); - } - void renderHtml( HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData) throws IOException { res.setContentType("text/html"); res.setCharacterEncoding("UTF-8"); byte[] data = - newRenderer(templateName).setData(soyData).renderHtml().get().toString().getBytes(UTF_8); + newRenderer(templateName, Optional.of(req)) + .setData(soyData) + .renderHtml() + .get() + .toString() + .getBytes(UTF_8); if (BaseServlet.acceptsGzipEncoding(req)) { res.addHeader(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING); res.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip"); @@ -169,14 +171,20 @@ } OutputStream renderHtmlStreaming( - HttpServletResponse res, String templateName, Map<String, ?> soyData) throws IOException { - return renderHtmlStreaming(res, false, templateName, soyData); + HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData) + throws IOException { + return renderHtmlStreaming(req, res, false, templateName, soyData); } OutputStream renderHtmlStreaming( - HttpServletResponse res, boolean gzip, String templateName, Map<String, ?> soyData) + HttpServletRequest req, + HttpServletResponse res, + boolean gzip, + String templateName, + Map<String, ?> soyData) throws IOException { - String html = newRenderer(templateName).setData(soyData).renderHtml().get().toString(); + String html = + newRenderer(templateName, Optional.of(req)).setData(soyData).renderHtml().get().toString(); int id = html.indexOf(PLACEHOLDER); checkArgument(id >= 0, "Template must contain %s", PLACEHOLDER); @@ -214,15 +222,25 @@ } SoySauce.Renderer newRenderer(String templateName) { + return newRenderer(templateName, Optional.empty()); + } + + SoySauce.Renderer newRenderer(String templateName, Optional<HttpServletRequest> req) { ImmutableMap.Builder<String, Object> staticUrls = ImmutableMap.builder(); for (String key : STATIC_URL_GLOBALS.keySet()) { staticUrls.put( key.replaceFirst("^gitiles\\.", ""), LegacyConversions.riskilyAssumeTrustedResourceUrl(globals.get(key))); } - return getSauce() - .renderTemplate(templateName) - .setIj(ImmutableMap.of("staticUrls", staticUrls.build(), "SITE_TITLE", siteTitle)); + ImmutableMap.Builder<String, Object> ij = + ImmutableMap.<String, Object>builder() + .put("staticUrls", staticUrls.build()) + .put("SITE_TITLE", siteTitle); + Optional<String> nonce = req.map((r) -> (String) r.getAttribute("nonce")); + if (nonce.isPresent() && nonce.get() != null) { + ij.put("csp_nonce", nonce); + } + return getSauce().renderTemplate(templateName).setIj(ij.build()); } protected abstract SoySauce getSauce();