This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch fix/WW-5535-http-method-interceptor-wildcard-6.8.x in repository https://gitbox.apache.org/repos/asf/struts.git
commit 784af67339f34fee1f0c0add3e78a8db16aa4425 Author: Lukasz Lenart <[email protected]> AuthorDate: Sun Feb 22 17:26:13 2026 +0100 fix(core): WW-5535 enforce HTTP method annotations for wildcard actions 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">
