This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch release/struts-6-8-x
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/release/struts-6-8-x by this
push:
new 66ea9eaf2 fix(core): WW-5535 enforce HTTP method annotations for
wildcard actions (#1593)
66ea9eaf2 is described below
commit 66ea9eaf24b27fb775fada0272ff74b325929182
Author: Lukasz Lenart <[email protected]>
AuthorDate: Fri Feb 27 13:26:06 2026 +0100
fix(core): WW-5535 enforce HTTP method annotations for wildcard actions
(#1593)
DefaultActionProxy.resolveMethod() incorrectly set methodSpecified=false
for config-resolved methods (including wildcard-substituted ones), causing
HttpMethodInterceptor to skip method-level @HttpPost/@HttpGet annotation
checks. Move methodSpecified=false inside the inner if block so it only
applies when truly defaulting to "execute".
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <[email protected]>
---
.../opensymphony/xwork2/DefaultActionProxy.java | 2 +-
.../main/java/org/apache/struts2/ActionProxy.java | 6 ++-
.../xwork2/DefaultActionProxyTest.java | 53 ++++++++++++++++++++--
...XmlConfigurationProviderAllowedMethodsTest.java | 2 +-
.../httpmethod/HttpMethodInterceptorTest.java | 37 +++++++++++++++
.../providers/xwork-test-allowed-methods.xml | 14 +++++-
6 files changed, 104 insertions(+), 10 deletions(-)
diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java
b/core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java
index 4d73813ee..6bc28ed25 100644
--- a/core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java
+++ b/core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java
@@ -168,8 +168,8 @@ public class DefaultActionProxy implements ActionProxy,
Serializable {
this.method = config.getMethodName();
if (StringUtils.isEmpty(this.method)) {
this.method = ActionConfig.DEFAULT_METHOD;
+ methodSpecified = false;
}
- methodSpecified = false;
}
}
diff --git a/core/src/main/java/org/apache/struts2/ActionProxy.java
b/core/src/main/java/org/apache/struts2/ActionProxy.java
index d5e19e44d..ad800d406 100644
--- a/core/src/main/java/org/apache/struts2/ActionProxy.java
+++ b/core/src/main/java/org/apache/struts2/ActionProxy.java
@@ -93,9 +93,11 @@ public interface ActionProxy {
String getMethod();
/**
- * Gets status of the method value's initialization.
+ * Gets status of the method value's initialization. Returns {@code true}
when the method was explicitly provided
+ * (e.g. via URL parameter, wildcard substitution, or action
configuration), and {@code false} only when the
+ * framework defaults to {@code "execute"} because no method was specified
anywhere.
*
- * @return true if the method returned by getMethod() is not a default
initializer value.
+ * @return true if the method returned by getMethod() is not the default
"execute" fallback.
*/
boolean isMethodSpecified();
diff --git
a/core/src/test/java/com/opensymphony/xwork2/DefaultActionProxyTest.java
b/core/src/test/java/com/opensymphony/xwork2/DefaultActionProxyTest.java
index 125c5b4bd..5db2f68cf 100644
--- a/core/src/test/java/com/opensymphony/xwork2/DefaultActionProxyTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/DefaultActionProxyTest.java
@@ -18,15 +18,14 @@
*/
package com.opensymphony.xwork2;
+import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
-import org.junit.Test;
public class DefaultActionProxyTest extends StrutsInternalTestCase {
- @Test
- public void testThorwExceptionOnNotAllowedMethod() throws Exception {
+ public void testThrowExceptionOnNotAllowedMethod() {
final String filename =
"com/opensymphony/xwork2/config/providers/xwork-test-allowed-methods.xml";
loadConfigurationProviders(new
StrutsXmlConfigurationProvider(filename));
DefaultActionProxy dap = new DefaultActionProxy(new
MockActionInvocation(), "strict", "Default", "notAllowed", true, true);
@@ -35,8 +34,52 @@ public class DefaultActionProxyTest extends
StrutsInternalTestCase {
try {
dap.prepare();
fail("Must throw exception!");
- } catch (Exception e) {
- assertEquals(e.getMessage(), "Method notAllowed for action Default
is not allowed!");
+ } catch (ConfigurationException e) {
+ assertEquals("Method notAllowed for action Default is not
allowed!", e.getMessage());
}
}
+
+ public void testMethodSpecifiedWhenPassedExplicitly() {
+ final String filename =
"com/opensymphony/xwork2/config/providers/xwork-test-allowed-methods.xml";
+ loadConfigurationProviders(new
StrutsXmlConfigurationProvider(filename));
+ DefaultActionProxy dap = new DefaultActionProxy(new
MockActionInvocation(), "", "NoMethod", "onPostOnly", true, true);
+ container.inject(dap);
+ dap.prepare();
+
+ assertTrue("Method passed explicitly should be marked as specified",
dap.isMethodSpecified());
+ assertEquals("onPostOnly", dap.getMethod());
+ }
+
+ public void testMethodSpecifiedWhenResolvedFromConfig() {
+ final String filename =
"com/opensymphony/xwork2/config/providers/xwork-test-allowed-methods.xml";
+ loadConfigurationProviders(new
StrutsXmlConfigurationProvider(filename));
+ DefaultActionProxy dap = new DefaultActionProxy(new
MockActionInvocation(), "", "ConfigMethod", null, true, true);
+ container.inject(dap);
+ dap.prepare();
+
+ assertTrue("Method resolved from action config should be marked as
specified", dap.isMethodSpecified());
+ assertEquals("onPostOnly", dap.getMethod());
+ }
+
+ public void testMethodNotSpecifiedWhenDefaultingToExecute() {
+ final String filename =
"com/opensymphony/xwork2/config/providers/xwork-test-allowed-methods.xml";
+ loadConfigurationProviders(new
StrutsXmlConfigurationProvider(filename));
+ DefaultActionProxy dap = new DefaultActionProxy(new
MockActionInvocation(), "", "NoMethod", null, true, true);
+ container.inject(dap);
+ dap.prepare();
+
+ assertFalse("Method defaulting to execute should not be marked as
specified", dap.isMethodSpecified());
+ assertEquals("execute", dap.getMethod());
+ }
+
+ public void testMethodSpecifiedWithWildcardAction() {
+ final String filename =
"com/opensymphony/xwork2/config/providers/xwork-test-allowed-methods.xml";
+ loadConfigurationProviders(new
StrutsXmlConfigurationProvider(filename));
+ DefaultActionProxy dap = new DefaultActionProxy(new
MockActionInvocation(), "", "Wild-onPostOnly", null, true, true);
+ container.inject(dap);
+ dap.prepare();
+
+ assertTrue("Method resolved from wildcard should be marked as
specified", dap.isMethodSpecified());
+ assertEquals("onPostOnly", dap.getMethod());
+ }
}
\ No newline at end of file
diff --git
a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderAllowedMethodsTest.java
b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderAllowedMethodsTest.java
index 1ac2315bc..00604757d 100644
---
a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderAllowedMethodsTest.java
+++
b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderAllowedMethodsTest.java
@@ -42,7 +42,7 @@ public class XmlConfigurationProviderAllowedMethodsTest
extends ConfigurationTes
Map actionConfigs = pkg.getActionConfigs();
// assertions
- assertEquals(5, actionConfigs.size());
+ assertEquals(8, actionConfigs.size());
ActionConfig action = (ActionConfig) actionConfigs.get("Default");
assertEquals(1, action.getAllowedMethods().size());
diff --git
a/core/src/test/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptorTest.java
b/core/src/test/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptorTest.java
index 98e78c3fa..f68b01df3 100644
---
a/core/src/test/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptorTest.java
+++
b/core/src/test/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptorTest.java
@@ -217,6 +217,43 @@ public class HttpMethodInterceptorTest extends
StrutsInternalTestCase {
assertEquals(HttpMethod.POST, action.getHttpMethod());
}
+ public void testWildcardResolvedMethodWithPostAnnotationRejectsGet()
throws Exception {
+ // given
+ HttpMethodsTestAction action = new HttpMethodsTestAction();
+ prepareActionInvocation(action);
+ actionProxy.setMethod("onPostOnly");
+ actionProxy.setMethodSpecified(true);
+
+ invocation.setResultCode("onPostOnly");
+
+ prepareRequest("GET");
+
+ // when
+ String resultName = interceptor.intercept(invocation);
+
+ // then
+ assertEquals("bad-request", resultName);
+ }
+
+ public void testWildcardResolvedMethodWithPostAnnotationAllowsPost()
throws Exception {
+ // given
+ HttpMethodsTestAction action = new HttpMethodsTestAction();
+ prepareActionInvocation(action);
+ actionProxy.setMethod("onPostOnly");
+ actionProxy.setMethodSpecified(true);
+
+ invocation.setResultCode("onPostOnly");
+
+ prepareRequest("POST");
+
+ // when
+ String resultName = interceptor.intercept(invocation);
+
+ // then
+ assertEquals("onPostOnly", resultName);
+ assertEquals(HttpMethod.POST, action.getHttpMethod());
+ }
+
private void prepareActionInvocation(Object action) {
interceptor = new HttpMethodInterceptor();
invocation = new MockActionInvocation();
diff --git
a/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-allowed-methods.xml
b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-allowed-methods.xml
index 247b4ef27..0c78ff5cf 100644
---
a/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-allowed-methods.xml
+++
b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-allowed-methods.xml
@@ -29,7 +29,7 @@
</action>
<action name="Boring">
- <allowed-methods> </allowed-methods>
+ <allowed-methods></allowed-methods>
</action>
<action name="Foo">
@@ -43,6 +43,18 @@
<action name="Baz" method="baz">
<allowed-methods>foo,bar</allowed-methods>
</action>
+
+ <action name="Wild-*" class="org.apache.struts2.HttpMethodsTestAction"
method="{1}">
+ <allowed-methods>regex:.*</allowed-methods>
+ </action>
+
+ <action name="ConfigMethod"
class="org.apache.struts2.HttpMethodsTestAction" method="onPostOnly">
+ <allowed-methods>regex:.*</allowed-methods>
+ </action>
+
+ <action name="NoMethod"
class="org.apache.struts2.HttpMethodsTestAction">
+ <allowed-methods>regex:.*</allowed-methods>
+ </action>
</package>
<package name="strict" strict-method-invocation="true">