This is an automated email from the ASF dual-hosted git repository.
mgrigorov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git
The following commit(s) were added to refs/heads/master by this push:
new ca8ec2797d WICKET-7149: Add Integrity and CrossOrigin values to
ResourceReference and related code (#1102)
ca8ec2797d is described below
commit ca8ec2797d694bec701e001d66708338f8f9561e
Author: mpritt <[email protected]>
AuthorDate: Thu Feb 20 01:40:02 2025 -0700
WICKET-7149: Add Integrity and CrossOrigin values to ResourceReference and
related code (#1102)
WICKET-7149: Adding Integrity and CrossOrigin values to ResourceReference
class and using values for creating JS/CSS reference header items
Added two new variables to the ResourceReference class. These new values
allow for better JavaScriptReferenceHeaderItem and CssReferenceHeaderItem
creation, allowing those classes to access the resource reference
identity/CrossOrigin values if they are not already defined at the header item
level. Also fixed a bug with the AbstractCssReferenceHeaderItem class where it
was directly accessing the identity and crossOrigin values instead of it going
through the accessor methods like it should.
* Create JsCssReferenceHeaderItemTest.java
Added unit tests for ResourceReference, JavaScriptReferenceHeaderItem and
CssReferenceHeaderItem classes.
* WICKET-7149: Tidy-up the new JsCssReferenceHeaderItemTest
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
---------
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
Co-authored-by: Martin Tzvetanov Grigorov <[email protected]>
---
.../markup/html/JsCssReferenceHeaderItemTest.java | 212 +++++++++++++++++++++
.../head/AbstractCssReferenceHeaderItem.java | 4 +-
.../wicket/markup/head/CssReferenceHeaderItem.java | 16 +-
.../markup/head/JavaScriptReferenceHeaderItem.java | 25 +++
.../wicket/request/resource/ResourceReference.java | 45 +++++
5 files changed, 297 insertions(+), 5 deletions(-)
diff --git
a/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/JsCssReferenceHeaderItemTest.java
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/JsCssReferenceHeaderItemTest.java
new file mode 100644
index 0000000000..b1e25b1f8f
--- /dev/null
+++
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/JsCssReferenceHeaderItemTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.html;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.Serial;
+
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.markup.IMarkupResourceStreamProvider;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.CssReferenceHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
+import org.apache.wicket.markup.parser.XmlPullParser;
+import org.apache.wicket.markup.parser.XmlTag;
+import org.apache.wicket.request.resource.PackageResourceReference;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.StringResourceStream;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Michael Pritt
+ */
+class JsCssReferenceHeaderItemTest extends WicketTestCase
+{
+ private static final Logger log =
LoggerFactory.getLogger(JsCssReferenceHeaderItemTest.class);
+
+ private static final String JS_RESOURCE_HASH =
"sha384-jsResourceHash2856816aw771";
+ private static final String JS_REFERENCE_HASH =
"sha384-jssReferenceHash28546qt8725171";
+ private static final String CSS_RESOURCE_HASH =
"sha384-cssResourceHash2512ab6wts23";
+ private static final String CSS_REFERENCE_HASH =
"sha384-cssReferenceHash2awet512asd623";
+
+ /**
+ * Basic ResourceReference integrity hash check
+ */
+ @Test
+ void resourceReferenceTest_basic() throws Exception
+ {
+ tester.startPage(TestPage.class);
+ XmlPullParser parser = new XmlPullParser();
+ parser.parse(tester.getLastResponseAsString());
+ XmlTag tag = parser.nextTag();
+ CharSequence integrity = null;
+ CharSequence crossOrigin = null;
+ do
+ {
+ if (tag != null && tag.isOpen() &&
"script".equals(tag.getName()) && "jsref".contentEquals(tag.getAttribute("id")))
+ {
+ log.info(" SCRIPT TAG: " + tag.toDebugString()
+ "\nExpect js resource hash: " + JS_RESOURCE_HASH
+ + "\nExpect crossOrigin: " +
CrossOrigin.USE_CREDENTIALS.getRealName());
+ integrity = tag.getAttribute("integrity");
+ crossOrigin = tag.getAttribute("crossOrigin");
+ break;
+ }
+ }
+ while ((tag = parser.nextTag()) != null);
+ assertEquals(JS_RESOURCE_HASH, integrity);
+ assertEquals(CrossOrigin.USE_CREDENTIALS.getRealName(),
crossOrigin);
+ }
+
+
+ /**
+ * Basic JavaScriptReferenceHeaderItem integrity check - should
override any integrity at resource level
+ */
+ @Test
+ void javaScriptReferenceIntegrityTest_override() throws Exception
+ {
+ tester.startPage(TestPage.class);
+ XmlPullParser parser = new XmlPullParser();
+ parser.parse(tester.getLastResponseAsString());
+ XmlTag tag = parser.nextTag();
+ CharSequence integrity = null;
+ CharSequence crossOrigin = null;
+ do
+ {
+ if (tag != null && tag.isOpen() &&
"script".equals(tag.getName()) &&
"jsref2".contentEquals(tag.getAttribute("id")))
+ {
+ log.info(" SCRIPT TAG: " + tag.toDebugString()
+ "\nExpect js reference hash: " + JS_REFERENCE_HASH
+ + "\nExpect crossOrigin: " +
CrossOrigin.ANONYMOUS.getRealName());
+ integrity = tag.getAttribute("integrity");
+ crossOrigin = tag.getAttribute("crossOrigin");
+ break;
+ }
+ }
+ while ((tag = parser.nextTag()) != null);
+ assertEquals(JS_REFERENCE_HASH, integrity);
+ assertEquals(CrossOrigin.ANONYMOUS.getRealName(), crossOrigin);
+ }
+
+ @Test
+ void cssReferenceIntegrityTest_basic() throws Exception
+ {
+ tester.startPage(TestPage.class);
+ XmlPullParser parser = new XmlPullParser();
+ parser.parse(tester.getLastResponseAsString());
+ XmlTag tag = parser.nextTag();
+ CharSequence integrity = null;
+ CharSequence crossOrigin = null;
+ do
+ {
+ if (tag != null && "link".equals(tag.getName()) &&
tag.getAttribute("id").equals("cssref"))
+ {
+ log.debug(" LINK TAG: " + tag.toDebugString() +
"\nExpect resource hash: " + CSS_RESOURCE_HASH
+ + "\nExpect crossOrigin: " +
CrossOrigin.ANONYMOUS.getRealName());
+ integrity = tag.getAttribute("integrity");
+ crossOrigin = tag.getAttribute("crossOrigin");
+ break;
+ }
+ }
+ while ((tag = parser.nextTag()) != null);
+ assertEquals(CSS_RESOURCE_HASH, integrity);
+ assertEquals(CrossOrigin.ANONYMOUS.getRealName(), crossOrigin);
+ }
+
+
+ @Test
+ void cssReferenceIntegrityTest_override() throws Exception
+ {
+ tester.startPage(TestPage.class);
+ XmlPullParser parser = new XmlPullParser();
+ parser.parse(tester.getLastResponseAsString());
+ XmlTag tag = parser.nextTag();
+ CharSequence integrity = null;
+ CharSequence crossOrigin = null;
+ do
+ {
+ if (tag != null && "link".equals(tag.getName()) &&
tag.getAttribute("id").equals("cssref2"))
+ {
+ log.debug(" LINK TAG: " + tag.toDebugString() +
"\nExpect reference hash: " + CSS_REFERENCE_HASH
+ + "\nExpect crossOrigin: " +
CrossOrigin.USE_CREDENTIALS.getRealName());
+ integrity = tag.getAttribute("integrity");
+ crossOrigin = tag.getAttribute("crossOrigin");
+ break;
+ }
+ }
+ while ((tag = parser.nextTag()) != null);
+ assertEquals(CSS_REFERENCE_HASH, integrity);
+ assertEquals(CrossOrigin.USE_CREDENTIALS.getRealName(),
crossOrigin);
+ }
+
+ public static class TestPage extends WebPage implements
IMarkupResourceStreamProvider
+ {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void renderHead(IHeaderResponse response)
+ {
+ super.renderHead(response);
+
+ // JS Reference - Basic
+ PackageResourceReference jsRef = new
PackageResourceReference("jsres");
+ jsRef.setIntegrity(JS_RESOURCE_HASH);
+ jsRef.setCrossOrigin(CrossOrigin.USE_CREDENTIALS);
+ JavaScriptReferenceHeaderItem jsHeaderItem =
JavaScriptHeaderItem.forReference(jsRef, "jsref");
+ response.render(jsHeaderItem);
+
+ // JS Reference - Override
+ jsRef = new PackageResourceReference("jsres2");
+ jsRef.setIntegrity(JS_RESOURCE_HASH);
+ jsRef.setCrossOrigin(CrossOrigin.USE_CREDENTIALS);
+ jsHeaderItem = JavaScriptHeaderItem.forReference(jsRef,
"jsref2");
+ jsHeaderItem.setIntegrity(JS_REFERENCE_HASH);
+ jsHeaderItem.setCrossOrigin(CrossOrigin.ANONYMOUS);
+ response.render(jsHeaderItem);
+
+ // CSS Reference - Basic
+ PackageResourceReference cssRef = new
PackageResourceReference("cssres");
+ cssRef.setIntegrity(CSS_RESOURCE_HASH);
+ cssRef.setCrossOrigin(CrossOrigin.ANONYMOUS);
+ CssReferenceHeaderItem cssHeaderItem =
CssHeaderItem.forReference(cssRef);
+ cssHeaderItem.setId("cssref");
+ response.render(cssHeaderItem);
+
+ // CSS References - Override
+ cssRef = new PackageResourceReference("cssres2");
+ cssRef.setIntegrity(CSS_RESOURCE_HASH);
+ cssRef.setCrossOrigin(CrossOrigin.ANONYMOUS);
+ cssHeaderItem = CssHeaderItem.forReference(cssRef);
+ cssHeaderItem.setId("cssref2");
+ cssHeaderItem.setIntegrity(CSS_REFERENCE_HASH);
+
cssHeaderItem.setCrossOrigin(CrossOrigin.USE_CREDENTIALS);
+ response.render(cssHeaderItem);
+ }
+
+ @Override
+ public IResourceStream getMarkupResourceStream(MarkupContainer
container,
+ Class<?> containerClass)
+ {
+ return new
StringResourceStream("<html><body></body></html>");
+ }
+ }
+}
diff --git
a/wicket-core/src/main/java/org/apache/wicket/markup/head/AbstractCssReferenceHeaderItem.java
b/wicket-core/src/main/java/org/apache/wicket/markup/head/AbstractCssReferenceHeaderItem.java
index cfe993b950..aa1ff06d41 100644
---
a/wicket-core/src/main/java/org/apache/wicket/markup/head/AbstractCssReferenceHeaderItem.java
+++
b/wicket-core/src/main/java/org/apache/wicket/markup/head/AbstractCssReferenceHeaderItem.java
@@ -93,8 +93,8 @@ public abstract class AbstractCssReferenceHeaderItem extends
CssHeaderItem imple
attributes.putAttribute(CssUtils.ATTR_ID, getId());
attributes.putAttribute(CssUtils.ATTR_LINK_MEDIA, getMedia());
attributes.putAttribute(CssUtils.ATTR_CROSS_ORIGIN,
- crossOrigin == null ? null : crossOrigin.getRealName());
- attributes.putAttribute(CssUtils.ATTR_INTEGRITY, integrity);
+ getCrossOrigin() == null ? null :
getCrossOrigin().getRealName());
+ attributes.putAttribute(CssUtils.ATTR_INTEGRITY,
getIntegrity());
attributes.putAttribute(CssUtils.ATTR_CSP_NONCE, getNonce());
CssUtils.writeLink(response, attributes);
diff --git
a/wicket-core/src/main/java/org/apache/wicket/markup/head/CssReferenceHeaderItem.java
b/wicket-core/src/main/java/org/apache/wicket/markup/head/CssReferenceHeaderItem.java
index a2ee043045..8cbd67335e 100644
---
a/wicket-core/src/main/java/org/apache/wicket/markup/head/CssReferenceHeaderItem.java
+++
b/wicket-core/src/main/java/org/apache/wicket/markup/head/CssReferenceHeaderItem.java
@@ -103,13 +103,23 @@ public class CssReferenceHeaderItem extends
AbstractCssReferenceHeaderItem imple
@Override
public CrossOrigin getCrossOrigin()
{
- return null;
+ CrossOrigin tempOrigin = super.getCrossOrigin();
+ if (tempOrigin == null)
+ {
+ tempOrigin = reference.getCrossOrigin();
+ }
+ return tempOrigin;
}
- @Override
+ @Override
public String getIntegrity()
{
- return null;
+ String tempIntegrity = super.getIntegrity();
+ if (tempIntegrity == null)
+ {
+ tempIntegrity = reference.getIntegrity();
+ }
+ return tempIntegrity;
}
@Override
diff --git
a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptReferenceHeaderItem.java
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptReferenceHeaderItem.java
index a04c6ed306..0292768b32 100644
---
a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptReferenceHeaderItem.java
+++
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptReferenceHeaderItem.java
@@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import org.apache.wicket.markup.html.CrossOrigin;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.cycle.RequestCycle;
@@ -39,6 +40,8 @@ public class JavaScriptReferenceHeaderItem extends
AbstractJavaScriptReferenceHe
implements
IReferenceHeaderItem
{
+ private static final long serialVersionUID = 1L;
+
private final ResourceReference reference;
private final PageParameters pageParameters;
@@ -140,4 +143,26 @@ public class JavaScriptReferenceHeaderItem extends
AbstractJavaScriptReferenceHe
return java.util.Objects.equals(reference, that.reference) &&
java.util.Objects.equals(pageParameters,
that.pageParameters);
}
+
+ @Override
+ public String getIntegrity()
+ {
+ String tempIntegrity = super.getIntegrity();
+ if (tempIntegrity == null)
+ {
+ tempIntegrity = reference.getIntegrity();
+ }
+ return tempIntegrity;
+ }
+
+ @Override
+ public CrossOrigin getCrossOrigin()
+ {
+ CrossOrigin tempOrigin = super.getCrossOrigin();
+ if (tempOrigin == null)
+ {
+ tempOrigin = reference.getCrossOrigin();
+ }
+ return tempOrigin;
+ }
}
diff --git
a/wicket-core/src/main/java/org/apache/wicket/request/resource/ResourceReference.java
b/wicket-core/src/main/java/org/apache/wicket/request/resource/ResourceReference.java
index 9b96144ff2..8b7420f3a3 100644
---
a/wicket-core/src/main/java/org/apache/wicket/request/resource/ResourceReference.java
+++
b/wicket-core/src/main/java/org/apache/wicket/request/resource/ResourceReference.java
@@ -24,6 +24,7 @@ import java.util.Locale;
import org.apache.wicket.Application;
import org.apache.wicket.core.util.lang.WicketObjects;
import org.apache.wicket.markup.head.HeaderItem;
+import org.apache.wicket.markup.html.CrossOrigin;
import org.apache.wicket.util.io.IClusterable;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Objects;
@@ -47,6 +48,10 @@ public abstract class ResourceReference implements
IClusterable
private final Key data;
+ private String integrity;
+
+ private CrossOrigin crossOrigin;
+
/**
* Creates new {@link ResourceReference} instance.
*
@@ -243,6 +248,46 @@ public abstract class ResourceReference implements
IClusterable
return new UrlAttributes(getLocale(), getStyle(),
getVariation());
}
+ /**
+ * Returns the integrity value of the resource which is a string
containing
+ * one or more base64 encoded hashes.
+ *
+ * hashes are whitespace separated (see reference below):
+ *
https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
+ */
+ public String getIntegrity() {
+ return integrity;
+ }
+
+ /**
+ * Sets the integrity value of the resource which containes one or more
+ * base64 encoded hashes
+ *
+ * hashes are whitespace separated (see reference below):
+ *
https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
+ */
+ public void setIntegrity(String integrity) {
+ this.integrity = integrity;
+ }
+
+ /**
+ * Returns the cross origin policy to use when creating the header
reference
+ * for the resource.
+ *
+ * @return cross origin policy
+ */
+ public CrossOrigin getCrossOrigin() {
+ return crossOrigin;
+ }
+
+ /**
+ * Sets the cross origin policy to use when creating the header
reference
+ * for the resource.
+ */
+ public void setCrossOrigin(CrossOrigin crossOrigin) {
+ this.crossOrigin = crossOrigin;
+ }
+
/**
* Factory method to build a resource reference that uses the provided
supplier to return
* the resource.