This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch fix/WW-5468-modeldriven
in repository https://gitbox.apache.org/repos/asf/struts.git

commit fead176e8a553d2d7fe5703d1075d84db1bc94e5
Author: Lukasz Lenart <lukaszlen...@apache.org>
AuthorDate: Fri Oct 11 10:49:13 2024 +0200

    WW-5468 Fixes @StrutsParameter for ModelDriven
---
 .../java/com/opensymphony/xwork2/ModelDriven.java  |  3 ++
 .../parameter/ParametersInterceptor.java           | 53 ++++++++++++++--------
 .../parameter/StrutsParameterAnnotationTest.java   | 34 ++++++++++++--
 .../apache/struts2/junit/StrutsRestTestCase.java   |  2 +-
 4 files changed, 68 insertions(+), 24 deletions(-)

diff --git a/core/src/main/java/com/opensymphony/xwork2/ModelDriven.java 
b/core/src/main/java/com/opensymphony/xwork2/ModelDriven.java
index c07c6bbe7..59641a997 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ModelDriven.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ModelDriven.java
@@ -18,6 +18,8 @@
  */
 package com.opensymphony.xwork2;
 
+import org.apache.struts2.interceptor.parameter.StrutsParameter;
+
 /**
  * ModelDriven Actions provide a model object to be pushed onto the ValueStack
  * in addition to the Action itself, allowing a FormBean type approach like 
Struts.
@@ -31,6 +33,7 @@ public interface ModelDriven<T> {
      *
      * @return the model
      */
+    @StrutsParameter
     T getModel();
 
 }
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/parameter/ParametersInterceptor.java
 
b/core/src/main/java/org/apache/struts2/interceptor/parameter/ParametersInterceptor.java
index 353f3cb82..a1640a820 100644
--- 
a/core/src/main/java/org/apache/struts2/interceptor/parameter/ParametersInterceptor.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/parameter/ParametersInterceptor.java
@@ -20,6 +20,7 @@ package org.apache.struts2.interceptor.parameter;
 
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.ModelDriven;
 import com.opensymphony.xwork2.inject.Inject;
 import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
 import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
@@ -348,7 +349,7 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
         }
 
         long paramDepth = name.codePoints().mapToObj(c -> (char) 
c).filter(NESTING_CHARS::contains).count();
-        if (requireAnnotationsTransitionMode && paramDepth == 0) {
+        if (!(action instanceof ModelDriven) && 
requireAnnotationsTransitionMode && paramDepth == 0) {
             return true;
         }
 
@@ -365,29 +366,38 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
      * save computation by checking this last.
      */
     protected boolean hasValidAnnotatedMember(String rootProperty, Object 
action, long paramDepth) {
+        final String property = action instanceof ModelDriven ? "model" : 
rootProperty;
+        LOG.debug("Using [{}] as a root property of [{}]", property, 
action.getClass().getSimpleName());
+
         BeanInfo beanInfo = getBeanInfo(action);
         if (beanInfo == null) {
-            return hasValidAnnotatedField(action, rootProperty, paramDepth);
+            return hasValidAnnotatedField(action, property, paramDepth);
         }
 
         Optional<PropertyDescriptor> propDescOpt = 
Arrays.stream(beanInfo.getPropertyDescriptors())
-                .filter(desc -> 
desc.getName().equals(rootProperty)).findFirst();
+                .filter(desc -> desc.getName().equals(property)).findFirst();
         if (propDescOpt.isEmpty()) {
-            return hasValidAnnotatedField(action, rootProperty, paramDepth);
+            return hasValidAnnotatedField(action, property, paramDepth);
         }
 
         if (hasValidAnnotatedPropertyDescriptor(action, propDescOpt.get(), 
paramDepth)) {
             return true;
         }
 
-        return hasValidAnnotatedField(action, rootProperty, paramDepth);
+        return hasValidAnnotatedField(action, property, paramDepth);
     }
 
     protected boolean hasValidAnnotatedPropertyDescriptor(Object action, 
PropertyDescriptor propDesc, long paramDepth) {
-        Method relevantMethod = paramDepth == 0 ? propDesc.getWriteMethod() : 
propDesc.getReadMethod();
+        Method relevantMethod = paramDepth == 0
+                ? action instanceof ModelDriven<?> ? propDesc.getReadMethod() 
: propDesc.getWriteMethod()
+                : propDesc.getReadMethod();
+
         if (relevantMethod == null) {
+            LOG.debug("No such property [{}] in action [{}]", 
propDesc.getName(), action.getClass().getSimpleName());
             return false;
         }
+
+        LOG.debug("Using [{}] as a property of [{}]", 
relevantMethod.getName(), action.getClass().getSimpleName());
         if (getPermittedInjectionDepth(relevantMethod) < paramDepth) {
             String logMessage = format(
                     "Parameter injection for method [%s] on action [%s] 
rejected. Ensure it is annotated with @StrutsParameter with an appropriate 
'depth'.",
@@ -400,6 +410,9 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
             }
             return false;
         }
+        if (action instanceof ModelDriven) {
+            allowlistClass(propDesc.getPropertyType());
+        }
         if (paramDepth >= 1) {
             allowlistClass(relevantMethod.getReturnType());
         }
@@ -439,18 +452,22 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
 
     protected boolean hasValidAnnotatedField(Object action, String fieldName, 
long paramDepth) {
         Field field;
+        LOG.debug("Checking if field [{}] of [{}] is annotated with [{}]",
+                fieldName, action.getClass().getSimpleName(), 
StrutsParameter.class.getSimpleName());
         try {
             field = action.getClass().getDeclaredField(fieldName);
         } catch (NoSuchFieldException e) {
+            LOG.debug("Field [{}] not found", fieldName);
             return false;
         }
         if (!Modifier.isPublic(field.getModifiers())) {
+            LOG.debug("Field [{}] is not public", fieldName);
             return false;
         }
         if (getPermittedInjectionDepth(field) < paramDepth) {
             String logMessage = format(
                     "Parameter injection for field [%s] on action [%s] 
rejected. Ensure it is annotated with @StrutsParameter with an appropriate 
'depth'.",
-                    fieldName,
+                    field.getName(),
                     action.getClass().getName());
             if (devMode) {
                 notifyDeveloperOfError(LOG, action, logMessage);
@@ -591,9 +608,9 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
         if (!matchLength) {
             if (devMode) {
                 LOG.warn("Parameter [{}] is too long, allowed length is [{}]. 
Use Interceptor Parameter Overriding " +
-                        "to override the limit, see more at\n" +
-                        
"https://struts.apache.org/core-developers/interceptors.html#interceptor-parameter-overriding";,
-                    name, paramNameMaxLength);
+                                "to override the limit, see more at\n" +
+                                
"https://struts.apache.org/core-developers/interceptors.html#interceptor-parameter-overriding";,
+                        name, paramNameMaxLength);
             } else {
                 LOG.warn("Parameter [{}] is too long, allowed length is [{}]", 
name, paramNameMaxLength);
             }
@@ -621,8 +638,8 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
         if (result.isExcluded()) {
             if (devMode) {
                 LOG.warn("Parameter [{}] matches excluded pattern [{}]! See 
Accepted / Excluded patterns at\n" +
-                        
"https://struts.apache.org/security/#accepted--excluded-patterns";,
-                    paramName, result.getExcludedPattern());
+                                
"https://struts.apache.org/security/#accepted--excluded-patterns";,
+                        paramName, result.getExcludedPattern());
             } else {
                 LOG.debug("Parameter [{}] matches excluded pattern [{}]!", 
paramName, result.getExcludedPattern());
             }
@@ -640,8 +657,8 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
             if (excludedValuePattern.matcher(value).matches()) {
                 if (devMode) {
                     LOG.warn("Parameter value [{}] matches excluded pattern 
[{}]! See Accepting/Excluding parameter values at\n" +
-                            
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values";,
-                        value, excludedValuePatterns);
+                                    
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values";,
+                            value, excludedValuePatterns);
                 } else {
                     LOG.debug("Parameter value [{}] matches excluded pattern 
[{}]", value, excludedValuePattern);
                 }
@@ -663,8 +680,8 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
         }
         if (devMode) {
             LOG.warn("Parameter value [{}] didn't match accepted pattern [{}]! 
See Accepting/Excluding parameter values at\n" +
-                    
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values";,
-                value, acceptedValuePatterns);
+                            
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values";,
+                    value, acceptedValuePatterns);
         } else {
             LOG.debug("Parameter value [{}] was not accepted!", value);
         }
@@ -734,7 +751,7 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
             LOG.debug("Sets accepted value patterns to [{}], note this may 
impact the safety of your application!", patterns);
         } else {
             LOG.warn("Replacing accepted patterns [{}] with [{}], be aware 
that this may impact safety of your application!",
-                acceptedValuePatterns, patterns);
+                    acceptedValuePatterns, patterns);
         }
         acceptedValuePatterns = new HashSet<>(patterns.size());
         try {
@@ -759,7 +776,7 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
             LOG.debug("Setting excluded value patterns to [{}]", patterns);
         } else {
             LOG.warn("Replacing excluded value patterns [{}] with [{}], be 
aware that this may impact safety of your application!",
-                excludedValuePatterns, patterns);
+                    excludedValuePatterns, patterns);
         }
         excludedValuePatterns = new HashSet<>(patterns.size());
         try {
diff --git 
a/core/src/test/java/org/apache/struts2/interceptor/parameter/StrutsParameterAnnotationTest.java
 
b/core/src/test/java/org/apache/struts2/interceptor/parameter/StrutsParameterAnnotationTest.java
index 53fa14717..eb44fc6fe 100644
--- 
a/core/src/test/java/org/apache/struts2/interceptor/parameter/StrutsParameterAnnotationTest.java
+++ 
b/core/src/test/java/org/apache/struts2/interceptor/parameter/StrutsParameterAnnotationTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.struts2.interceptor.parameter;
 
+import com.opensymphony.xwork2.ModelDriven;
 import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
 import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
 import org.apache.commons.lang3.ClassUtils;
@@ -80,7 +81,7 @@ public class StrutsParameterAnnotationTest {
         }
     }
 
-    private Set<Class<?>> getParentClasses(Class<?> ...clazzes) {
+    private Set<Class<?>> getParentClasses(Class<?>... clazzes) {
         Set<Class<?>> set = new HashSet<>();
         for (Class<?> clazz : clazzes) {
             set.add(clazz);
@@ -258,8 +259,14 @@ public class StrutsParameterAnnotationTest {
         testParameter(new MethodAction(), "publicStrNotAnnotated", true);
     }
 
+    @Test
+    public void publicModelPojo() {
+        
parametersInterceptor.setRequireAnnotationsTransitionMode(Boolean.TRUE.toString());
+        testParameter(new ModelAction(), "name", true);
+        
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Object.class,
 Pojo.class));
+    }
 
-    class FieldAction {
+    static class FieldAction {
         @StrutsParameter
         private String privateStr;
 
@@ -275,7 +282,7 @@ public class StrutsParameterAnnotationTest {
         public Pojo publicPojoDepthZero;
 
         @StrutsParameter(depth = 1)
-        public Pojo publicPojoDepthOne ;
+        public Pojo publicPojoDepthOne;
 
         @StrutsParameter(depth = 2)
         public Pojo publicPojoDepthTwo;
@@ -290,7 +297,7 @@ public class StrutsParameterAnnotationTest {
         public Map<String, Pojo> publicPojoMapDepthTwo;
     }
 
-    class MethodAction {
+    static class MethodAction {
 
         @StrutsParameter
         private void setPrivateStr(String str) {
@@ -343,6 +350,23 @@ public class StrutsParameterAnnotationTest {
         }
     }
 
-    class Pojo {
+    static class ModelAction implements ModelDriven<Pojo> {
+
+        @StrutsParameter
+        public Pojo getModel() {
+            return new Pojo();
+        }
+    }
+
+    static class Pojo {
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
     }
 }
diff --git 
a/plugins/junit/src/main/java/org/apache/struts2/junit/StrutsRestTestCase.java 
b/plugins/junit/src/main/java/org/apache/struts2/junit/StrutsRestTestCase.java
index 6f55eefde..3610f0c84 100644
--- 
a/plugins/junit/src/main/java/org/apache/struts2/junit/StrutsRestTestCase.java
+++ 
b/plugins/junit/src/main/java/org/apache/struts2/junit/StrutsRestTestCase.java
@@ -75,7 +75,7 @@ public class StrutsRestTestCase<T> extends 
StrutsJUnit4TestCase<T> {
         ActionMapping mapping = getActionMapping(request);
 
         assertNotNull(mapping);
-        Dispatcher.getInstance().serviceAction(request, response, mapping);
+        dispatcher.serviceAction(request, response, mapping);
 
         if (response.getStatus() != HttpServletResponse.SC_OK)
             throw new ServletException("Error code [" + response.getStatus() + 
"], Error: ["

Reply via email to