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();

Reply via email to