[
https://issues.apache.org/jira/browse/WW-5504?focusedWorklogId=982148&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-982148
]
ASF GitHub Bot logged work on WW-5504:
--------------------------------------
Author: ASF GitHub Bot
Created on: 07/Sep/25 07:47
Start Date: 07/Sep/25 07:47
Worklog Time Spent: 10m
Work Description: Copilot commented on code in PR #1174:
URL: https://github.com/apache/struts/pull/1174#discussion_r2328560371
##########
core/src/main/java/org/apache/struts2/interceptor/csp/CspNonceReader.java:
##########
@@ -0,0 +1,76 @@
+/*
+ * 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.struts2.interceptor.csp;
+
+import org.apache.struts2.util.ValueStack;
+
+/**
+ * Reads the nonce value using the ValueStack, {@link StrutsCspNonceReader} is
the default implementation
+ * @since 6.8.0
+ */
+public interface CspNonceReader {
+
+ NonceValue readNonceValue(ValueStack stack);
+
+ class NonceValue {
+ private final String nonceValue;
+ private final CspNonceSource source;
+
+ private NonceValue(String nonceValue, CspNonceSource source) {
+ this.nonceValue = nonceValue;
+ this.source = source;
+ }
+
+ public static NonceValue ofSession(String nonceValue) {
+ return new NonceValue(nonceValue, CspNonceSource.SESSION);
+ }
+
+ public static NonceValue ofRequest(String nonceValue) {
+ return new NonceValue(nonceValue, CspNonceSource.REQUEST);
+ }
+
+ public static NonceValue ofNullSession() {
+ return new NonceValue(null, CspNonceSource.REQUEST);
Review Comment:
The `ofNullSession()` method incorrectly uses `CspNonceSource.REQUEST`
instead of `CspNonceSource.SESSION`. This should be `CspNonceSource.SESSION` to
properly indicate the source type.
```suggestion
return new NonceValue(null, CspNonceSource.SESSION);
```
##########
core/src/main/java/org/apache/struts2/interceptor/csp/CspNonceReader.java:
##########
@@ -0,0 +1,76 @@
+/*
+ * 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.struts2.interceptor.csp;
+
+import org.apache.struts2.util.ValueStack;
+
+/**
+ * Reads the nonce value using the ValueStack, {@link StrutsCspNonceReader} is
the default implementation
+ * @since 6.8.0
+ */
+public interface CspNonceReader {
+
+ NonceValue readNonceValue(ValueStack stack);
+
+ class NonceValue {
+ private final String nonceValue;
+ private final CspNonceSource source;
+
+ private NonceValue(String nonceValue, CspNonceSource source) {
+ this.nonceValue = nonceValue;
+ this.source = source;
+ }
+
+ public static NonceValue ofSession(String nonceValue) {
+ return new NonceValue(nonceValue, CspNonceSource.SESSION);
+ }
+
+ public static NonceValue ofRequest(String nonceValue) {
+ return new NonceValue(nonceValue, CspNonceSource.REQUEST);
+ }
+
+ public static NonceValue ofNullSession() {
+ return new NonceValue(null, CspNonceSource.REQUEST);
+ }
+
+ public static NonceValue ofNullRequest() {
+ return new NonceValue(null, CspNonceSource.REQUEST);
+ }
+
+ public boolean isNonceValueSet() {
+ return nonceValue != null;
+ }
+
+ public String getNonceValue() {
+ return nonceValue;
+ }
+
+ public CspNonceSource getSource() {
+ return source;
+ }
+
+ @Override
+ public String toString() {
+ return "NonceValue{" +
+ String.format("nonceValue='%s**********'",
nonceValue.substring(0, 4)) +
Review Comment:
The `toString()` method will throw a `NullPointerException` when
`nonceValue` is null, which can happen for null session/request cases. Add a
null check before calling `substring()`.
```suggestion
String displayNonce;
if (nonceValue != null && nonceValue.length() >= 4) {
displayNonce = String.format("nonceValue='%s**********'",
nonceValue.substring(0, 4));
} else if (nonceValue != null) {
displayNonce = String.format("nonceValue='%s**********'",
nonceValue);
} else {
displayNonce = "nonceValue='<null>'";
}
return "NonceValue{" +
displayNonce +
```
##########
core/src/test/java/org/apache/struts2/components/UIBeanTest.java:
##########
@@ -21,6 +21,8 @@
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.util.ValueStack;
+import net.bytebuddy.implementation.InvokeDynamic;
Review Comment:
The import `net.bytebuddy.implementation.InvokeDynamic` appears to be unused
in this file. This import should be removed to keep the code clean.
```suggestion
```
##########
core/src/main/java/org/apache/struts2/interceptor/csp/DefaultCspSettings.java:
##########
@@ -43,63 +45,102 @@
*/
public class DefaultCspSettings implements CspSettings {
- private final static Logger LOG =
LogManager.getLogger(DefaultCspSettings.class);
+ private static final Logger LOG =
LogManager.getLogger(DefaultCspSettings.class);
+ private static final String NONCE_KEY = "nonce";
private final SecureRandom sRand = new SecureRandom();
+ private CspNonceSource nonceSource = CspNonceSource.SESSION;
+
protected String reportUri;
protected String reportTo;
// default to reporting mode
protected String cspHeader = CSP_REPORT_HEADER;
+ @Inject(value = StrutsConstants.STRUTS_CSP_NONCE_SOURCE, required = false)
+ public void setNonceSource(String nonceSource) {
+ if (StringUtils.isBlank(nonceSource)) {
+ this.nonceSource = CspNonceSource.SESSION;
+ } else {
+ this.nonceSource =
CspNonceSource.valueOf(nonceSource.toUpperCase());
+ }
+ }
+
@Override
public void addCspHeaders(HttpServletRequest request, HttpServletResponse
response) {
+ if (this.nonceSource == CspNonceSource.SESSION) {
+ addCspHeadersWithSession(request, response);
+ } else if (this.nonceSource == CspNonceSource.REQUEST) {
+ addCspHeadersWithRequest(request, response);
+ } else {
+ LOG.warn("Unknown nonce source: {}, ignoring CSP settings",
nonceSource);
+ }
+ }
+
+ private void addCspHeadersWithSession(HttpServletRequest request,
HttpServletResponse response) {
if (isSessionActive(request)) {
LOG.trace("Session is active, applying CSP settings");
- associateNonceWithSession(request);
- response.setHeader(cspHeader, createPolicyFormat(request));
+ String nonceValue = generateNonceValue();
+ request.getSession().setAttribute(NONCE_KEY, nonceValue);
+ response.setHeader(cspHeader, createPolicyFormat(nonceValue));
} else {
- LOG.trace("Session is not active, ignoring CSP settings");
+ LOG.debug("Session is not active, ignoring CSP settings");
}
}
+ private void addCspHeadersWithRequest(HttpServletRequest request,
HttpServletResponse response) {
+ String nonceValue = generateNonceValue();
+ request.setAttribute(NONCE_KEY, nonceValue);
+ response.setHeader(cspHeader, createPolicyFormat(nonceValue));
+ }
+
private boolean isSessionActive(HttpServletRequest request) {
return request.getSession(false) != null;
}
- private void associateNonceWithSession(HttpServletRequest request) {
- String nonceValue =
Base64.getUrlEncoder().encodeToString(getRandomBytes());
- request.getSession().setAttribute("nonce", nonceValue);
+ private String generateNonceValue() {
+ return Base64.getUrlEncoder().encodeToString(getRandomBytes());
}
- protected String createPolicyFormat(HttpServletRequest request) {
- StringBuilder policyFormatBuilder = new StringBuilder()
- .append(OBJECT_SRC)
- .append(format(" '%s'; ", NONE))
- .append(SCRIPT_SRC)
- .append(" 'nonce-%s' ") // nonce placeholder
- .append(format("'%s' ", STRICT_DYNAMIC))
- .append(format("%s %s; ", HTTP, HTTPS))
- .append(BASE_URI)
- .append(format(" '%s'; ", NONE));
+ protected String createPolicyFormat(String nonceValue) {
+ StringBuilder builder = new StringBuilder()
+ .append(OBJECT_SRC)
+ .append(format(" '%s'; ", NONE))
+ .append(SCRIPT_SRC)
+ .append(format(" 'nonce-%s' ", nonceValue))
+ .append(format("'%s' ", STRICT_DYNAMIC))
+ .append(format("%s %s; ", HTTP, HTTPS))
+ .append(BASE_URI)
+ .append(format(" '%s'; ", NONE));
if (reportUri != null) {
- policyFormatBuilder
- .append(REPORT_URI)
- .append(format(" %s; ", reportUri));
- if(reportTo != null) {
- policyFormatBuilder
+ builder
+ .append(REPORT_URI)
+ .append(format(" %s; ", reportUri));
+ if (reportTo != null) {
+ builder
.append(REPORT_TO)
.append(format(" %s; ", reportTo));
}
}
- return format(policyFormatBuilder.toString(), getNonceString(request));
+ return builder.toString();
+ }
+
+ /**
+ * @deprecated since 6.8.0, for removal
+ */
+ @Deprecated
+ protected String createPolicyFormat(HttpServletRequest request) {
+ throw new UnsupportedOperationException("Unsupported implementation,
use #createPolicyFormat(String) instead!");
Review Comment:
The error message uses '#createPolicyFormat(String)' which is not standard
Java documentation syntax. It should be 'createPolicyFormat(String)' or use
proper JavaDoc syntax like '{@link #createPolicyFormat(String)}'.
```suggestion
throw new UnsupportedOperationException("Unsupported implementation,
use createPolicyFormat(String) instead!");
```
Issue Time Tracking
-------------------
Worklog Id: (was: 982148)
Time Spent: 1h 10m (was: 1h)
> CSP Nonce changes within a page
> -------------------------------
>
> Key: WW-5504
> URL: https://issues.apache.org/jira/browse/WW-5504
> Project: Struts 2
> Issue Type: Bug
> Components: Core Interceptors
> Affects Versions: 6.7.0
> Reporter: Andreas Sachs
> Priority: Major
> Fix For: 6.8.0, 7.1.0
>
> Time Spent: 1h 10m
> Remaining Estimate: 0h
>
> Sometimes the CSP nonce changes within a page.
>
> <script type="text/javascript" src="..." nonce="A"> </script>
> <script type="text/javascript" src="..." nonce="A"> </script>
> ...
> <script type="text/javascript" src="..." nonce="B"> </script>
>
> This happens if there are concurrent requests within the same session.
>
> Each request stores a new nonce in the session:
>
> DefaultCspSettings:
> request.getSession().setAttribute("nonce", nonceValue);
>
> If the first request is not finished, the second request will change the
> nonce of the first request.
>
>
>
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)