[
https://issues.apache.org/jira/browse/WW-5504?focusedWorklogId=982150&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-982150
]
ASF GitHub Bot logged work on WW-5504:
--------------------------------------
Author: ASF GitHub Bot
Created on: 07/Sep/25 07:54
Start Date: 07/Sep/25 07:54
Worklog Time Spent: 10m
Work Description: Copilot commented on code in PR #1174:
URL: https://github.com/apache/struts/pull/1174#discussion_r2328562896
##########
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 deprecated method throws an UnsupportedOperationException immediately,
which creates a breaking change for any existing subclasses that override this
method. Consider providing a default implementation that extracts the nonce
from the request and calls the new method, or document this as a breaking
change.
```suggestion
// Default implementation: extract nonce from request attribute
"cspNonce"
Object nonceObj = request.getAttribute("cspNonce");
String nonceValue = (nonceObj instanceof String) ? (String) nonceObj
: null;
if (nonceValue == null) {
// Fallback: generate a new nonce if not present
nonceValue = generateNonceValue();
}
return createPolicyFormat(nonceValue);
```
##########
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!");
}
+ /**
+ * @deprecated since 6.8.0, for removal
+ */
+ @Deprecated
protected String getNonceString(HttpServletRequest request) {
- Object nonce = request.getSession().getAttribute("nonce");
- return Objects.toString(nonce);
+ throw new UnsupportedOperationException("Unsupported implementation,
don't use!");
}
Review Comment:
Similarly, this deprecated method throws an UnsupportedOperationException
immediately, creating a potential breaking change. Consider providing a bridge
implementation or clearly documenting the breaking change in release notes.
Issue Time Tracking
-------------------
Worklog Id: (was: 982150)
Time Spent: 1.5h (was: 1h 20m)
> 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
> Assignee: Lukasz Lenart
> Priority: Major
> Fix For: 6.8.0, 7.1.0
>
> Time Spent: 1.5h
> 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)