This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5631-chaining-require-annotations in repository https://gitbox.apache.org/repos/asf/struts.git
commit a1740a45cb1d6b6ba89542bfea0b354a7acc3f4e Author: Lukasz Lenart <[email protected]> AuthorDate: Wed May 27 08:30:07 2026 +0200 WW-5631 test(chaining): cover fail-closed introspection; clarify target==action Add a test asserting nothing is copied when the target action cannot be introspected (fail-closed), and document why isAuthorized is called with target == action for chaining (no ModelDriven exemption). Co-Authored-By: Claude Opus 4.7 <[email protected]> --- .../struts2/interceptor/ChainingInterceptor.java | 2 ++ .../interceptor/ChainingInterceptorTest.java | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java index b294b47f6..34af10d22 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java @@ -242,6 +242,8 @@ public class ChainingInterceptor extends AbstractInterceptor { continue; } String name = descriptor.getName(); + // target == action is deliberate: chaining copies onto the action object itself (not a + // ModelDriven model), so the authorizer's ModelDriven exemption must not apply here. if (!parameterAuthorizer.isAuthorized(name, action, action)) { LOG.warn("Chaining: property [{}] not copied to [{}] because it is not annotated with @StrutsParameter", name, targetClass.getName()); diff --git a/core/src/test/java/org/apache/struts2/interceptor/ChainingInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/ChainingInterceptorTest.java index ab9305bce..1a35bd73b 100644 --- a/core/src/test/java/org/apache/struts2/interceptor/ChainingInterceptorTest.java +++ b/core/src/test/java/org/apache/struts2/interceptor/ChainingInterceptorTest.java @@ -33,6 +33,7 @@ import org.apache.struts2.util.ProxyService; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; +import java.beans.IntrospectionException; import java.util.*; /** @@ -278,6 +279,27 @@ public class ChainingInterceptorTest extends XWorkTestCase { assertFalse("proxied unannotated target property must NOT be copied", target.getManagerApproved()); } + public void testFailsClosedWhenTargetCannotBeIntrospected() throws Exception { + AnnotatedChainingAction source = new AnnotatedChainingAction(); + source.setManagerApproved(true); + AnnotatedChainingAction target = new AnnotatedChainingAction(); + mockInvocation.matchAndReturn("getAction", target); + stack.push(source); + stack.push(target); + + // Introspection failure must fail closed: copy nothing, even for an annotated property. + OgnlUtil ognlUtil = Mockito.mock(OgnlUtil.class); + Mockito.when(ognlUtil.getBeanInfo(ArgumentMatchers.any(Class.class))) + .thenThrow(new IntrospectionException("boom")); + interceptor.setOgnlUtil(ognlUtil); + + enableChainingEnforcement(true, false); + interceptor.intercept(invocation); + + assertFalse("nothing should be copied when the target cannot be introspected", + target.getManagerApproved()); + } + @Override protected void setUp() throws Exception { super.setUp();
