This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch support/struts-6-x-x
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/support/struts-6-x-x by this
push:
new 60ec4778d WW-5846 fix(core)!: match exemptedPaths against the request
path (#2125)
60ec4778d is described below
commit 60ec4778d88f6625409be325f80e50c4e13fa68e
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sat Nov 7 19:14:48 2026 +0100
WW-5846 fix(core)!: match exemptedPaths against the request path (#2125)
FetchMetadataInterceptor, CoopInterceptor and CoepInterceptor compared each
exemptedPaths entry with the context path, which is the same for every
request: an entry naming a path never matched, and an entry equal to the
context path exempted the whole application.
They now compare against RequestUtils.getUri(request), the value the action
mapper uses: servlet path plus path info, or the included servlet path
during
an include dispatch. Matching is exact, so an entry includes the action
extension, e.g. /saml/acs.action. The Fetch Metadata rejection log names the
request path, which is the value to add to exemptedPaths.
Tests now set the servlet path instead of faking a context path, and the
struts-testing.xml fixtures use the paths a real request carries.
BREAKING CHANGE: an exemptedPaths entry equal to the context path no longer
disables the interceptor for the whole application; use the interceptor's
"disabled" parameter instead. Entries naming a request path now take effect.
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../struts2/interceptor/CoepInterceptor.java | 3 +-
.../struts2/interceptor/CoopInterceptor.java | 3 +-
.../interceptor/FetchMetadataInterceptor.java | 7 +--
.../struts2/interceptor/CoepInterceptorTest.java | 24 ++++++++++-
.../struts2/interceptor/CoopInterceptorTest.java | 26 +++++++++--
.../interceptor/FetchMetadataInterceptorTest.java | 50 ++++++++++++++++------
core/src/test/resources/struts-testing.xml | 6 +--
7 files changed, 94 insertions(+), 25 deletions(-)
diff --git
a/core/src/main/java/org/apache/struts2/interceptor/CoepInterceptor.java
b/core/src/main/java/org/apache/struts2/interceptor/CoepInterceptor.java
index 850556e32..cafe00060 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/CoepInterceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/CoepInterceptor.java
@@ -22,6 +22,7 @@ import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.TextParseUtil;
+import org.apache.struts2.RequestUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -58,7 +59,7 @@ public class CoepInterceptor extends AbstractInterceptor
implements PreResultLis
@Override
public void beforeResult(ActionInvocation invocation, String resultCode) {
HttpServletRequest req =
invocation.getInvocationContext().getServletRequest();
- final String path = req.getContextPath();
+ final String path = RequestUtils.getUri(req);
if (exemptedPaths.contains(path)) {
// no need to add headers
diff --git
a/core/src/main/java/org/apache/struts2/interceptor/CoopInterceptor.java
b/core/src/main/java/org/apache/struts2/interceptor/CoopInterceptor.java
index 123170baf..209db33e2 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/CoopInterceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/CoopInterceptor.java
@@ -22,6 +22,7 @@ import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.TextParseUtil;
+import org.apache.struts2.RequestUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -60,7 +61,7 @@ public class CoopInterceptor extends AbstractInterceptor
implements PreResultLis
@Override
public void beforeResult(ActionInvocation invocation, String resultCode) {
HttpServletRequest request =
invocation.getInvocationContext().getServletRequest();
- String path = request.getContextPath();
+ String path = RequestUtils.getUri(request);
if (isExempted(path)) {
// no need to add headers
diff --git
a/core/src/main/java/org/apache/struts2/interceptor/FetchMetadataInterceptor.java
b/core/src/main/java/org/apache/struts2/interceptor/FetchMetadataInterceptor.java
index 0f46206f2..17d7cbbda 100644
---
a/core/src/main/java/org/apache/struts2/interceptor/FetchMetadataInterceptor.java
+++
b/core/src/main/java/org/apache/struts2/interceptor/FetchMetadataInterceptor.java
@@ -23,6 +23,7 @@ import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.util.TextParseUtil;
+import org.apache.struts2.RequestUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -67,9 +68,9 @@ public class FetchMetadataInterceptor extends
AbstractInterceptor {
addVaryHeaders(invocation);
- String contextPath = request.getContextPath();
+ String path = RequestUtils.getUri(request);
// Apply exemptions: paths/endpoints meant to be served cross-origin
- if (exemptedPaths.contains(contextPath)) {
+ if (exemptedPaths.contains(path)) {
return invocation.invoke();
}
@@ -78,7 +79,7 @@ public class FetchMetadataInterceptor extends
AbstractInterceptor {
return invocation.invoke();
}
- LOG.warn("Fetch metadata rejected cross-origin request to: {}",
contextPath);
+ LOG.warn("Fetch metadata rejected cross-origin request to: {}", path);
return SC_FORBIDDEN;
}
diff --git
a/core/src/test/java/org/apache/struts2/interceptor/CoepInterceptorTest.java
b/core/src/test/java/org/apache/struts2/interceptor/CoepInterceptorTest.java
index 72ea1a024..83392d6bf 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/CoepInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/CoepInterceptorTest.java
@@ -52,7 +52,19 @@ public class CoepInterceptorTest extends
StrutsInternalTestCase {
}
public void testExemptedPath() throws Exception{
- request.setContextPath("/foo");
+ request.setServletPath("/foo");
+ interceptor.setEnforcingMode("true");
+
+ interceptor.intercept(mai);
+
+ String header = response.getHeader(COEP_ENFORCING_HEADER);
+ assertTrue("COEP applied to exempted path", Strings.isEmpty(header));
+ }
+
+ public void testExemptedPathInfo() throws Exception {
+ interceptor.setExemptedPaths("/rest/orders");
+ request.setServletPath("/rest");
+ request.setPathInfo("/orders");
interceptor.setEnforcingMode("true");
interceptor.intercept(mai);
@@ -61,6 +73,16 @@ public class CoepInterceptorTest extends
StrutsInternalTestCase {
assertTrue("COEP applied to exempted path", Strings.isEmpty(header));
}
+ public void testHeaderSetWhenOnlyContextPathIsExempted() throws Exception {
+ request.setContextPath("/foo");
+ request.setServletPath("/index.action");
+ interceptor.setEnforcingMode("true");
+
+ interceptor.intercept(mai);
+
+ assertEquals("COEP header value is incorrect", HEADER_CONTENT,
response.getHeader(COEP_ENFORCING_HEADER));
+ }
+
public void testReportingHeader() throws Exception {
interceptor.setEnforcingMode("false");
diff --git
a/core/src/test/java/org/apache/struts2/interceptor/CoopInterceptorTest.java
b/core/src/test/java/org/apache/struts2/interceptor/CoopInterceptorTest.java
index 544e7b5d8..b9e7aac0c 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/CoopInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/CoopInterceptorTest.java
@@ -38,7 +38,7 @@ public class CoopInterceptorTest extends
StrutsInternalTestCase {
String COOP_HEADER = "Cross-Origin-Opener-Policy";
public void testHeaderIsSetNonExemptedPath() throws Exception {
- request.setContextPath("/some");
+ request.setServletPath("/some.action");
interceptor.intercept(mai);
String header = response.getHeader(COOP_HEADER);
@@ -47,16 +47,34 @@ public class CoopInterceptorTest extends
StrutsInternalTestCase {
}
public void testHeaderIsNotSetExemptedPath() throws Exception {
- request.setContextPath("/foo");
+ request.setServletPath("/foo");
+ interceptor.intercept(mai);
+
+ String header = response.getHeader(COOP_HEADER);
+ assertTrue("Coop header exists in exempted path",
Strings.isEmpty(header));
+ }
+
+ public void testHeaderIsNotSetExemptedPathInfo() throws Exception {
+ interceptor.setExemptedPaths("/rest/orders");
+ request.setServletPath("/rest");
+ request.setPathInfo("/orders");
interceptor.intercept(mai);
String header = response.getHeader(COOP_HEADER);
assertTrue("Coop header exists in exempted path",
Strings.isEmpty(header));
}
+ public void testHeaderIsSetWhenOnlyContextPathIsExempted() throws
Exception {
+ request.setContextPath("/foo");
+ request.setServletPath("/index.action");
+ interceptor.intercept(mai);
+
+ assertEquals("Coop header is not same-origin", SAME_ORIGIN,
response.getHeader(COOP_HEADER));
+ }
+
public void testChangeDefaultMode() throws Exception {
interceptor.setMode("unsafe-none");
- request.setContextPath("/some");
+ request.setServletPath("/some.action");
interceptor.intercept(mai);
String header = response.getHeader(COOP_HEADER);
@@ -65,7 +83,7 @@ public class CoopInterceptorTest extends
StrutsInternalTestCase {
}
public void testErrorNotRecognizedMode() {
- request.setContextPath("/some");
+ request.setServletPath("/some.action");
try{
interceptor.setMode("foobar");
diff --git
a/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
b/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
index 9b52c94df..189472aa7 100644
---
a/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
+++
b/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
@@ -33,6 +33,7 @@ import
org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
+import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Collection;
@@ -150,21 +151,46 @@ public class FetchMetadataInterceptorTest extends
XWorkTestCase {
public void testPathInExemptedPaths() throws Exception {
request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
- request.setContextPath("/foo");
+ request.setServletPath("/foo");
assertNotEquals("Expected interceptor to accept this request",
SC_FORBIDDEN, interceptor.intercept(mai));
}
public void testPathNotInExemptedPaths() throws Exception {
request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
- request.setContextPath("/foobar");
+ request.setServletPath("/foobar");
+
+ assertEquals("Expected interceptor to NOT accept this request",
SC_FORBIDDEN, interceptor.intercept(mai));
+ }
+
+ public void testPathInfoInExemptedPaths() throws Exception {
+ interceptor.setExemptedPaths("/rest/orders");
+ request.addHeader(SEC_FETCH_SITE_HEADER, SITE_CROSS_SITE);
+ request.setServletPath("/rest");
+ request.setPathInfo("/orders");
+
+ assertNotEquals("Expected interceptor to accept this request",
SC_FORBIDDEN, interceptor.intercept(mai));
+ }
+
+ public void testContextPathInExemptedPathsDoesNotExempt() throws Exception
{
+ request.addHeader(SEC_FETCH_SITE_HEADER, SITE_CROSS_SITE);
+ request.setContextPath("/foo");
+ request.setServletPath("/index.action");
assertEquals("Expected interceptor to NOT accept this request",
SC_FORBIDDEN, interceptor.intercept(mai));
}
+ public void testIncludedPathInExemptedPaths() throws Exception {
+ request.addHeader(SEC_FETCH_SITE_HEADER, SITE_CROSS_SITE);
+ request.setServletPath("/outer.action");
+ request.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH, "/foo");
+
+ assertNotEquals("Expected interceptor to accept this request",
SC_FORBIDDEN, interceptor.intercept(mai));
+ }
+
public void testVaryHeaderAcceptedReq() throws Exception {
request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
- request.setContextPath("/foo");
+ request.setServletPath("/foo");
interceptor.intercept(mai);
@@ -222,11 +248,11 @@ public class FetchMetadataInterceptorTest extends
XWorkTestCase {
FetchMetadataInterceptor configuredFetchMetadataInterceptor =
(FetchMetadataInterceptor)
configuredFetchMetadataInterceptorMapping.getInterceptor();
request.removeHeader(SEC_FETCH_SITE_HEADER);
request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
- request.setContextPath("/foo");
+ request.setServletPath("/foo");
assertEquals("Expected interceptor to NOT accept this request [/foo]",
SC_FORBIDDEN, configuredFetchMetadataInterceptor.intercept(mai));
- request.setContextPath("/fetchMetadataExemptedGlobal");
+ request.setServletPath("/fetchMetadataExemptedGlobal.action");
assertNotEquals("Expected interceptor to accept this request
[/fetchMetadataExemptedGlobal]", SC_FORBIDDEN,
configuredFetchMetadataInterceptor.intercept(mai));
- request.setContextPath("/someOtherPath");
+ request.setServletPath("/someOtherPath.action");
assertNotEquals("Expected interceptor to accept this request
[/someOtherPath]", SC_FORBIDDEN,
configuredFetchMetadataInterceptor.intercept(mai));
// The test configuration in "struts-testing.xml" should also contain
three actions configured differently for the "fetchMetadata" interceptor.
@@ -257,8 +283,8 @@ public class FetchMetadataInterceptorTest extends
XWorkTestCase {
configuredFetchMetadataInterceptor = (FetchMetadataInterceptor)
configuredFetchMetadataInterceptorMapping.getInterceptor();
request.removeHeader(SEC_FETCH_SITE_HEADER);
request.addHeader(SEC_FETCH_SITE_HEADER,
fetchMetadataExemptedActionConfig.getName());
- request.setContextPath("/" +
fetchMetadataExemptedActionConfig.getName());
- assertNotEquals("Expected interceptor to accept this request [" + "/"
+ fetchMetadataExemptedActionConfig.getName() + "]", SC_FORBIDDEN,
configuredFetchMetadataInterceptor.intercept(mai));
+ request.setServletPath("/" +
fetchMetadataExemptedActionConfig.getName() + ".action");
+ assertNotEquals("Expected interceptor to accept this request [" + "/"
+ fetchMetadataExemptedActionConfig.getName() + ".action" + "]", SC_FORBIDDEN,
configuredFetchMetadataInterceptor.intercept(mai));
// Test fetchMetadata interceptor for the "fetchMetadataNotExempted"
action.
currentActionInterceptors =
fetchMetadataNotExemptedActionConfig.getInterceptors();
@@ -277,8 +303,8 @@ public class FetchMetadataInterceptorTest extends
XWorkTestCase {
configuredFetchMetadataInterceptor = (FetchMetadataInterceptor)
configuredFetchMetadataInterceptorMapping.getInterceptor();
request.removeHeader(SEC_FETCH_SITE_HEADER);
request.addHeader(SEC_FETCH_SITE_HEADER,
fetchMetadataNotExemptedActionConfig.getName());
- request.setContextPath("/" +
fetchMetadataNotExemptedActionConfig.getName());
- assertEquals("Expected interceptor to NOT accept this request [" + "/"
+ fetchMetadataNotExemptedActionConfig.getName() + "]", SC_FORBIDDEN,
configuredFetchMetadataInterceptor.intercept(mai));
+ request.setServletPath("/" +
fetchMetadataNotExemptedActionConfig.getName() + ".action");
+ assertEquals("Expected interceptor to NOT accept this request [" + "/"
+ fetchMetadataNotExemptedActionConfig.getName() + ".action" + "]",
SC_FORBIDDEN, configuredFetchMetadataInterceptor.intercept(mai));
// Test fetchMetadata interceptor for the
"fetchMetadataExemptedGlobal" action.
currentActionInterceptors =
fetchMetadataExemptedGlobalActionConfig.getInterceptors();
@@ -297,8 +323,8 @@ public class FetchMetadataInterceptorTest extends
XWorkTestCase {
configuredFetchMetadataInterceptor = (FetchMetadataInterceptor)
configuredFetchMetadataInterceptorMapping.getInterceptor();
request.removeHeader(SEC_FETCH_SITE_HEADER);
request.addHeader(SEC_FETCH_SITE_HEADER,
fetchMetadataExemptedGlobalActionConfig.getName());
- request.setContextPath("/" +
fetchMetadataExemptedGlobalActionConfig.getName());
- assertNotEquals("Expected interceptor to accept this request [" + "/"
+ fetchMetadataExemptedGlobalActionConfig.getName() + "]", SC_FORBIDDEN,
configuredFetchMetadataInterceptor.intercept(mai));
+ request.setServletPath("/" +
fetchMetadataExemptedGlobalActionConfig.getName() + ".action");
+ assertNotEquals("Expected interceptor to accept this request [" + "/"
+ fetchMetadataExemptedGlobalActionConfig.getName() + ".action" + "]",
SC_FORBIDDEN, configuredFetchMetadataInterceptor.intercept(mai));
}
}
diff --git a/core/src/test/resources/struts-testing.xml
b/core/src/test/resources/struts-testing.xml
index f1af997ce..a73c2c5b6 100644
--- a/core/src/test/resources/struts-testing.xml
+++ b/core/src/test/resources/struts-testing.xml
@@ -28,21 +28,21 @@
<interceptors>
<interceptor-stack name="defaultInterceptorStack">
<interceptor-ref name="defaultStack">
- <param
name="fetchMetadata.setExemptedPaths">/fetchMetadataExemptedGlobal,/someOtherPath</param>
+ <param
name="fetchMetadata.setExemptedPaths">/fetchMetadataExemptedGlobal.action,/someOtherPath.action</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<action name="fetchMetadataExempted"
class="com.opensymphony.xwork2.SimpleAction">
<interceptor-ref name="defaultInterceptorStack">
- <param
name="fetchMetadata.setExemptedPaths">/fetchMetadataExempted</param>
+ <param
name="fetchMetadata.setExemptedPaths">/fetchMetadataExempted.action</param>
</interceptor-ref>
<result name="success">hello.jsp</result>
</action>
<action name="fetchMetadataNotExempted"
class="com.opensymphony.xwork2.SimpleAction">
<interceptor-ref name="defaultInterceptorStack">
- <param
name="fetchMetadata.setExemptedPaths">/nonMatchingActionPath</param>
+ <param
name="fetchMetadata.setExemptedPaths">/nonMatchingActionPath.action</param>
</interceptor-ref>
<result name="success">hello.jsp</result>
</action>