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

Reply via email to