[ 
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)

Reply via email to