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

lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 871b1bd6875e7222ff350913c1328a04f8f0b06e
Author: Emil Huseynov <[email protected]>
AuthorDate: Sun Nov 1 15:49:47 2026 +0400

    WW-5820 Reject class before the model check so a Map model cannot exempt it
    
    Review follow-up. The class guard sat after the model's takesParameter
    check, and a Map-backed model takes any depth-0 name through map.put, so
    class on a Map model was exempted where the non-ModelDriven path rejects
    it. Move the class check to the top of isAuthorizedOnModelDrivenAction so
    it is rejected before the model is consulted, matching main whatever the
    model is. Pin it with modelDriven_mapModel_classAtDepthZero_rejected.
---
 .../interceptor/parameter/StrutsParameterAuthorizer.java | 16 +++++++++-------
 .../interceptor/parameter/ParameterAuthorizerTest.java   | 14 ++++++++++++++
 2 files changed, 23 insertions(+), 7 deletions(-)

diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/parameter/StrutsParameterAuthorizer.java
 
b/core/src/main/java/org/apache/struts2/interceptor/parameter/StrutsParameterAuthorizer.java
index 2d53378bf..93f0936f1 100644
--- 
a/core/src/main/java/org/apache/struts2/interceptor/parameter/StrutsParameterAuthorizer.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/parameter/StrutsParameterAuthorizer.java
@@ -175,20 +175,22 @@ public class StrutsParameterAuthorizer implements 
ParameterAuthorizer {
      * accessor on the model that introspection here cannot see. That fallback 
guarantees only that the name reaches
      * no object this code can model, which is no more permissive than the 
blanket exemption this scoping replaces.
      * <p>
-     * {@code class} is the exception to that fallback: it is invisible to 
introspection here rather than absent, so
-     * it is rejected instead of taking the fallback, which keeps a 
ModelDriven action from handing OGNL a
-     * {@code class} path that the ordinary non-ModelDriven path would have 
rejected for want of an annotation.
+     * {@code class} is rejected up front, before the model is even consulted: 
it is invisible to introspection here
+     * rather than absent, so it would otherwise slip through either the 
custom-accessor fallback or a Map-backed
+     * model's catch-all (a {@code Map} model takes any depth-0 name). 
Rejecting it first keeps a ModelDriven action
+     * from handing OGNL a {@code class} path that the ordinary 
non-ModelDriven path would have rejected for want of
+     * an annotation, matching main's behaviour exactly whatever the model is.
      */
     protected boolean isAuthorizedOnModelDrivenAction(String rootProperty, 
Object model, Object action, long paramDepth) {
+        if (CLASS_PROPERTY.equals(rootProperty)) {
+            LOG.debug("Property [class] is not an unknown property but 
Object.getClass() on every object alike, so neither the custom-accessor 
fallback nor a Map-backed model's catch-all applies; rejecting");
+            return false;
+        }
         if (takesParameter(model, rootProperty, paramDepth)) {
             LOG.debug("Property [{}] is taken by the ModelDriven model, 
exempting from @StrutsParameter annotation requirement",
                     rootProperty);
             return true;
         }
-        if (CLASS_PROPERTY.equals(rootProperty)) {
-            LOG.debug("Property [class] is not an unknown property but 
Object.getClass() on every object alike, so the fallback for a custom accessor 
does not apply; rejecting");
-            return false;
-        }
         if (takesParameter(action, rootProperty, paramDepth)) {
             LOG.debug("Property [{}] is taken by the ModelDriven action 
itself, applying the @StrutsParameter annotation requirement",
                     rootProperty);
diff --git 
a/core/src/test/java/org/apache/struts2/interceptor/parameter/ParameterAuthorizerTest.java
 
b/core/src/test/java/org/apache/struts2/interceptor/parameter/ParameterAuthorizerTest.java
index 3313f7001..c88393165 100644
--- 
a/core/src/test/java/org/apache/struts2/interceptor/parameter/ParameterAuthorizerTest.java
+++ 
b/core/src/test/java/org/apache/struts2/interceptor/parameter/ParameterAuthorizerTest.java
@@ -491,6 +491,20 @@ public class ParameterAuthorizerTest {
         assertThat(authorizer.isAuthorized("localizedTextProvider", 
action.getModel(), action)).isFalse();
     }
 
+    @Test
+    public void modelDriven_mapModel_classAtDepthZero_rejected() {
+        // A Map-backed model takes any depth-0 name through map.put, "class" 
included, so without checking
+        // "class" before the model it would be exempted where the 
non-ModelDriven path rejects it. "class" is
+        // Object.getClass() on everything, never request surface, so it is 
rejected whatever the model is.
+        var action = new MapModelAction();
+        var valueStack = new StubValueStack();
+        valueStack.push(action);
+        valueStack.push(action.getModel());
+        ActionContext.of().withValueStack(valueStack).bind();
+
+        assertThat(authorizer.isAuthorized("class", action.getModel(), 
action)).isFalse();
+    }
+
     // --- Inner test classes ---
 
     public static class PreviousAction {

Reply via email to