This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch WW-5275-custom-csp
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 68a401aacfbde7ab0210cbc2503b466e08ab6264
Author: Lukasz Lenart <lukaszlen...@apache.org>
AuthorDate: Sun Feb 12 16:37:10 2023 +0100

    WW-5275 Allows to provide a custom CspSettings per action
---
 .../apache/struts2/action/CspSettingsAware.java    | 33 +++++++++++++
 .../struts2/interceptor/csp/CspInterceptor.java    | 45 +++++++++++++-----
 .../struts2/interceptor/csp/CspSettings.java       |  8 +++-
 .../interceptor/csp/DefaultCspSettings.java        |  8 ++++
 .../struts2/interceptor/CspInterceptorTest.java    | 54 ++++++++++++++--------
 5 files changed, 116 insertions(+), 32 deletions(-)

diff --git a/core/src/main/java/org/apache/struts2/action/CspSettingsAware.java 
b/core/src/main/java/org/apache/struts2/action/CspSettingsAware.java
new file mode 100644
index 000000000..458a7c7f3
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/action/CspSettingsAware.java
@@ -0,0 +1,33 @@
+/*
+ * 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.action;
+
+import org.apache.struts2.interceptor.csp.CspSettings;
+
+/**
+ * Implement this interface by an action to provide a custom {@link 
CspSettings},
+ * see {@link org.apache.struts2.interceptor.csp.CspInterceptor} for more 
details
+ *
+ * @since Struts 6.2.0
+ */
+public interface CspSettingsAware {
+
+    CspSettings getCspSettings();
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/csp/CspInterceptor.java 
b/core/src/main/java/org/apache/struts2/interceptor/csp/CspInterceptor.java
index 5bae4f543..8e4356646 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/csp/CspInterceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/csp/CspInterceptor.java
@@ -20,7 +20,9 @@ package org.apache.struts2.interceptor.csp;
 
 import com.opensymphony.xwork2.ActionInvocation;
 import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
-import com.opensymphony.xwork2.interceptor.PreResultListener;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.action.CspSettingsAware;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -37,20 +39,43 @@ import java.util.Optional;
  * @see CspSettings
  * @see DefaultCspSettings
  **/
-public final class CspInterceptor extends AbstractInterceptor implements 
PreResultListener {
+public final class CspInterceptor extends AbstractInterceptor {
 
-    private final CspSettings settings = new DefaultCspSettings();
+    private static final Logger LOG = 
LogManager.getLogger(CspInterceptor.class);
+
+    private Boolean enforcingMode;
+    private String reportUri;
 
     @Override
     public String intercept(ActionInvocation invocation) throws Exception {
-        invocation.addPreResultListener(this);
+        Object action = invocation.getAction();
+        if (action instanceof CspSettingsAware) {
+            LOG.trace("Using CspSettings provided by the action: {}", action);
+            applySettings(invocation, ((CspSettingsAware) 
action).getCspSettings());
+        } else {
+            LOG.trace("Using DefaultCspSettings with action: {}", action);
+            applySettings(invocation, new DefaultCspSettings());
+        }
         return invocation.invoke();
     }
 
-    public void beforeResult(ActionInvocation invocation, String resultCode) {
+    private void applySettings(ActionInvocation invocation, CspSettings 
cspSettings) {
+        if (enforcingMode != null) {
+            LOG.trace("Applying: {} to enforcingMode", enforcingMode);
+            cspSettings.setEnforcingMode(enforcingMode);
+        }
+        if (reportUri != null) {
+            LOG.trace("Applying: {} to reportUri", reportUri);
+            cspSettings.setReportUri(reportUri);
+        }
+
         HttpServletRequest request = 
invocation.getInvocationContext().getServletRequest();
         HttpServletResponse response = 
invocation.getInvocationContext().getServletResponse();
-        settings.addCspHeaders(request, response);
+
+        invocation.addPreResultListener((actionInvocation, resultCode) -> {
+            LOG.trace("Applying CSP header: {} to the request", cspSettings);
+            cspSettings.addCspHeaders(request, response);
+        });
     }
 
     public void setReportUri(String reportUri) {
@@ -63,21 +88,19 @@ public final class CspInterceptor extends 
AbstractInterceptor implements PreResu
             throw new IllegalArgumentException("Illegal configuration: report 
URI is not relative to the root. Please set a report URI that starts with /");
         }
 
-        settings.setReportUri(reportUri);
+        this.reportUri = reportUri;
     }
 
     private Optional<URI> buildUri(String reportUri) {
         try {
             return Optional.of(URI.create(reportUri));
         } catch (IllegalArgumentException ignored) {
+            return Optional.empty();
         }
-
-        return Optional.empty();
     }
 
     public void setEnforcingMode(String value) {
-        boolean enforcingMode = Boolean.parseBoolean(value);
-        settings.setEnforcingMode(enforcingMode);
+        this.enforcingMode = Boolean.parseBoolean(value);
     }
 
 }
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/csp/CspSettings.java 
b/core/src/main/java/org/apache/struts2/interceptor/csp/CspSettings.java
index adf5b5072..acb142962 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/csp/CspSettings.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/csp/CspSettings.java
@@ -51,9 +51,13 @@ public interface CspSettings {
 
     void addCspHeaders(HttpServletRequest request, HttpServletResponse 
response);
 
-    // sets the uri where csp violation reports will be sent
+    /**
+     * Sets the uri where csp violation reports will be sent
+     */
     void setReportUri(String uri);
 
-    // sets CSP headers in enforcing mode when true, and report-only when false
+    /**
+     * Sets CSP headers in enforcing mode when true, and report-only when false
+     */
     void setEnforcingMode(boolean value);
 }
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/csp/DefaultCspSettings.java 
b/core/src/main/java/org/apache/struts2/interceptor/csp/DefaultCspSettings.java
index 199859ad4..d1768e869 100644
--- 
a/core/src/main/java/org/apache/struts2/interceptor/csp/DefaultCspSettings.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/csp/DefaultCspSettings.java
@@ -111,4 +111,12 @@ public class DefaultCspSettings implements CspSettings {
         this.reportUri = reportUri;
     }
 
+    @Override
+    public String toString() {
+        return "DefaultCspSettings{" +
+            "reportUri='" + reportUri + '\'' +
+            ", cspHeader='" + cspHeader + '\'' +
+            '}';
+    }
+
 }
diff --git 
a/core/src/test/java/org/apache/struts2/interceptor/CspInterceptorTest.java 
b/core/src/test/java/org/apache/struts2/interceptor/CspInterceptorTest.java
index 5727a9dcf..0dabc049c 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/CspInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/CspInterceptorTest.java
@@ -22,23 +22,16 @@ import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.mock.MockActionInvocation;
 import org.apache.logging.log4j.util.Strings;
 import org.apache.struts2.StrutsInternalTestCase;
+import org.apache.struts2.action.CspSettingsAware;
 import org.apache.struts2.dispatcher.SessionMap;
 import org.apache.struts2.interceptor.csp.CspInterceptor;
+import org.apache.struts2.interceptor.csp.CspSettings;
+import org.apache.struts2.interceptor.csp.DefaultCspSettings;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 
 import javax.servlet.http.HttpSession;
 
-import static org.apache.struts2.interceptor.csp.CspSettings.BASE_URI;
-import static 
org.apache.struts2.interceptor.csp.CspSettings.CSP_ENFORCE_HEADER;
-import static org.apache.struts2.interceptor.csp.CspSettings.CSP_REPORT_HEADER;
-import static org.apache.struts2.interceptor.csp.CspSettings.HTTP;
-import static org.apache.struts2.interceptor.csp.CspSettings.HTTPS;
-import static org.apache.struts2.interceptor.csp.CspSettings.NONE;
-import static org.apache.struts2.interceptor.csp.CspSettings.OBJECT_SRC;
-import static org.apache.struts2.interceptor.csp.CspSettings.REPORT_URI;
-import static org.apache.struts2.interceptor.csp.CspSettings.SCRIPT_SRC;
-import static org.apache.struts2.interceptor.csp.CspSettings.STRICT_DYNAMIC;
 import static org.junit.Assert.assertNotEquals;
 
 public class CspInterceptorTest extends StrutsInternalTestCase {
@@ -145,28 +138,35 @@ public class CspInterceptorTest extends 
StrutsInternalTestCase {
         }
     }
 
+    public void testCustomPreResultListener() throws Exception {
+        mai.setAction(new CustomerCspAction("/report-uri"));
+        interceptor.setEnforcingMode("false");
+        interceptor.intercept(mai);
+        checkHeader("/report-uri", "false");
+    }
+
     public void checkHeader(String reportUri, String enforcingMode) {
         String expectedCspHeader;
         if (Strings.isEmpty(reportUri)) {
             expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s 
%s; %s '%s'; ",
-                OBJECT_SRC, NONE,
-                SCRIPT_SRC, session.getAttribute("nonce"), STRICT_DYNAMIC, 
HTTP, HTTPS,
-                BASE_URI, NONE
+                CspSettings.OBJECT_SRC, CspSettings.NONE,
+                CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), 
CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
+                CspSettings.BASE_URI, CspSettings.NONE
             );
         } else {
             expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s 
%s; %s '%s'; %s %s",
-                OBJECT_SRC, NONE,
-                SCRIPT_SRC, session.getAttribute("nonce"), STRICT_DYNAMIC, 
HTTP, HTTPS,
-                BASE_URI, NONE,
-                REPORT_URI, reportUri
+                CspSettings.OBJECT_SRC, CspSettings.NONE,
+                CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), 
CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
+                CspSettings.BASE_URI, CspSettings.NONE,
+                CspSettings.REPORT_URI, reportUri
             );
         }
 
         String header;
         if (enforcingMode.equals("true")) {
-            header = response.getHeader(CSP_ENFORCE_HEADER);
+            header = response.getHeader(CspSettings.CSP_ENFORCE_HEADER);
         } else {
-            header = response.getHeader(CSP_REPORT_HEADER);
+            header = response.getHeader(CspSettings.CSP_REPORT_HEADER);
         }
 
         assertFalse("No CSP header exists", Strings.isEmpty(header));
@@ -185,4 +185,20 @@ public class CspInterceptorTest extends 
StrutsInternalTestCase {
         mai.setInvocationContext(context);
         session = request.getSession();
     }
+
+    private static class CustomerCspAction implements CspSettingsAware {
+
+        private final String reportUri;
+
+        private CustomerCspAction(String reportUri) {
+            this.reportUri = reportUri;
+        }
+
+        @Override
+        public CspSettings getCspSettings() {
+            DefaultCspSettings settings = new DefaultCspSettings();
+            settings.setReportUri(reportUri);
+            return settings;
+        }
+    }
 }

Reply via email to