andruhon commented on a change in pull request #376: WICKET-6682 add CSP nonce 
support: initial commit
URL: https://github.com/apache/wicket/pull/376#discussion_r303193907
 
 

 ##########
 File path: 
wicket-core/src/main/java/org/apache/wicket/markup/head/filter/CspNonceHeaderResponse.java
 ##########
 @@ -0,0 +1,348 @@
+package org.apache.wicket.markup.head.filter;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.core.util.string.CssUtils;
+import org.apache.wicket.core.util.string.JavaScriptUtils;
+import org.apache.wicket.markup.head.*;
+import org.apache.wicket.markup.html.DecoratingHeaderResponse;
+import org.apache.wicket.request.Response;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.string.Strings;
+import org.apache.wicket.util.value.HeaderItemAttribute;
+import org.apache.wicket.util.value.HeaderItemAttributeMap;
+
+/**
+ * Add CSP nonce to all relevant JavaScript and CSS header items
+ * <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
+ */
+public abstract class CspNonceHeaderResponse extends DecoratingHeaderResponse {
+
+    public CspNonceHeaderResponse(IHeaderResponse real) {
+        super(real);
+    }
+
+    @Override
+    public void render(HeaderItem item) {
+        while (item instanceof IWrappedHeaderItem) {
+            item = ((IWrappedHeaderItem)item).getWrapped();
+        }
+
+        final String nonce = getNonce();
+
+        if (item instanceof JavaScriptContentHeaderItem) {
+            item = new JavaScriptContentWithNonceHeaderItem(
+                    ((JavaScriptContentHeaderItem) item).getJavaScript(),
+                    ((JavaScriptContentHeaderItem) item).getId()
+            ).setNonce(nonce);
+        } if (item instanceof JavaScriptReferenceHeaderItem) {
+            JavaScriptReferenceHeaderItem headerItem = 
(JavaScriptReferenceHeaderItem) item;
+            item = new JavaScriptReferenceWithNonceHeaderItem(
+                    headerItem.getReference(),
+                    headerItem.getPageParameters(),
+                    headerItem.getId(),
+                    headerItem.isDefer(),
+                    headerItem.getCharset()
+            ).setNonce(nonce);
+        } else if (item instanceof JavaScriptUrlReferenceHeaderItem) {
+            JavaScriptUrlReferenceHeaderItem headerItem = 
(JavaScriptUrlReferenceHeaderItem) item;
+            item = new JavaScriptUrlReferenceWithNonceHeaderItem(
+                    headerItem.getUrl(),
+                    headerItem.getId(),
+                    headerItem.isDefer(),
+                    headerItem.getCharset()
+            ).setNonce(nonce);
+        } else if (item instanceof OnDomReadyHeaderItem) {
+            OnDomReadyHeaderItem headerItem = (OnDomReadyHeaderItem) item;
+            item = new 
OnDomReadyHeaderWithNonceItem(headerItem.getJavaScript()).setNonce(nonce);
+        } else if (item instanceof OnLoadHeaderItem) {
+            OnLoadHeaderItem headerItem = (OnLoadHeaderItem) item;
+            item = new 
OnLoadWithNonceHeaderItem(headerItem.getJavaScript()).setNonce(nonce);
+        } else if (item instanceof CssContentHeaderItem) {
+            CssContentHeaderItem headerItem = (CssContentHeaderItem) item;
+            item = new CssContentHeaderWithNonceItem(headerItem.getCss(), 
headerItem.getId()).setNonce(nonce);
+        } else if (item instanceof CssReferenceHeaderItem) {
+            CssReferenceHeaderItem headerItem = (CssReferenceHeaderItem) item;
+            item = new CssReferenceWithNonceHeaderItem(
+                    headerItem.getReference(),
+                    headerItem.getPageParameters(),
+                    headerItem.getMedia(),
+                    headerItem.getRel()
+            ).setNonce(nonce);
+        } else if (item instanceof CssUrlReferenceHeaderItem) {
+            CssUrlReferenceHeaderItem headerItem = (CssUrlReferenceHeaderItem) 
item;
+            item = new CssUrlReferenceWithNonceHeaderItem(
+                    headerItem.getUrl(),
+                    headerItem.getMedia(),
+                    headerItem.getRel()
+            ).setNonce(nonce);
+        }
+
+        super.render(item);
+    }
+
+    protected abstract String getNonce();
+
+    protected static void renderScriptReferenceHeaderItem(
+            Response response,
+            final CharSequence url,
+            final String id,
+            boolean defer,
+            String charset,
+            boolean async,
+            String nonce
+    ) {
+        Args.notEmpty(url, "url");
+        Args.notEmpty(nonce, "nonce");
+        boolean isAjax = 
RequestCycle.get().find(IPartialPageRequestHandler.class).isPresent();
+        // the url needs to be escaped when Ajax, because it will break the 
Ajax Response XML (WICKET-4777)
+        CharSequence escapedUrl = isAjax ? Strings.escapeMarkup(url): url;
+
+        HeaderItemAttributeMap attributes = new HeaderItemAttributeMap();
+        attributes.add(HeaderItemAttribute.SCRIPT_SRC, 
String.valueOf(escapedUrl));
+        attributes.add(HeaderItemAttribute.TYPE, "text/javascript");
+        if (id != null) {
+            attributes.add(HeaderItemAttribute.ID, id);
+        }
+        if (defer) {
+            attributes.add(HeaderItemAttribute.SCRIPT_DEFER, "defer");
+        }
+        if (async) {
+            attributes.add(HeaderItemAttribute.SCRIPT_ASYNC, "async");
+        }
+        attributes.add(HeaderItemAttribute.CSP_NONCE, nonce);
+        if (!Strings.isEmpty(charset)) {
+            attributes.add("charset", charset);
+        }
+
+        JavaScriptUtils.writeJavaScriptUrl(response, attributes);
+    }
+
+    protected static void renderCssReferenceHeaderItem(
+            Response response, String id, String url, String media, String 
rel, String nonce
+    ) {
+        Args.notEmpty(url, "url");
+
+        HeaderItemAttributeMap attributes = new HeaderItemAttributeMap();
+        if (Strings.isEmpty(rel) == false)
+        {
+            attributes.add(HeaderItemAttribute.LINK_REL, rel);
+        } else {
+            attributes.add(HeaderItemAttribute.LINK_REL, "stylesheet");
+        }
+
+        attributes.add(HeaderItemAttribute.LINK_HREF, 
String.valueOf(Strings.escapeMarkup(url)));
+
+        if (Strings.isEmpty(media) == false)
+        {
+            attributes.add(HeaderItemAttribute.LINK_MEDIA, media.toString());
+        }
+        if (Strings.isEmpty(id) == false)
+        {
+            attributes.add(HeaderItemAttribute.ID, id);
+        }
+
+        attributes.add(HeaderItemAttribute.CSP_NONCE, nonce);
+
+        CssUtils.writeLinkUrl(response, attributes);
+
+        response.write("\n");
+    }
+
+    private static class JavaScriptContentWithNonceHeaderItem extends 
JavaScriptContentHeaderItem {
+
+        private String nonce;
+
+        public JavaScriptContentWithNonceHeaderItem(CharSequence javaScript, 
String id) {
+            super(javaScript, id, null);
+        }
+
+        @Override
+        public void render(Response response) {
+            String id = getId();
+            HeaderItemAttributeMap attributes = new HeaderItemAttributeMap();
+            attributes.add(HeaderItemAttribute.CSP_NONCE, this.nonce);
+            if (id != null) {
+                attributes.add(HeaderItemAttribute.ID, id);
+            }
+            JavaScriptUtils.writeJavaScript(response, getJavaScript(), 
attributes);
+        }
+
+        public JavaScriptContentWithNonceHeaderItem setNonce(String nonce) {
+            Args.notNull(nonce, "nonce");
+            this.nonce = nonce;
+            return this;
+        }
+    }
+
+    private static class JavaScriptReferenceWithNonceHeaderItem extends 
JavaScriptReferenceHeaderItem {
+
+        private String nonce;
+
+        public JavaScriptReferenceWithNonceHeaderItem(ResourceReference 
reference, PageParameters pageParameters, String id, boolean defer, String 
charset) {
 
 Review comment:
   Yes it can. If the the CSP policy is strict as in my example the nonce is 
needed for the reference like `<script src="jquery.js"></script>` and even for 
links such as `<link  href="my.css" />`, for some reason nonce for link is not 
documented on developer.mozilla.org, but that's how it practically works with 
`script-src 'unsafe-eval' 'nonce-%s'; style-src 'nonce-%s';`

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


With regards,
Apache Git Services

Reply via email to