You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2026/05/04 11:22:43 UTC

(struts) 03/05: WW-5626 defensively skip non-String JSON keys in authorization filter

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

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

commit 2a0bcc5f572b5ef6d7d894fbd6529ee5edaa3dd9
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Mon May 4 12:50:26 2026 +0200

    WW-5626 defensively skip non-String JSON keys in authorization filter
    
    The (String) cast in filterUnauthorizedKeysRecursive threw ClassCastException
    for any custom JSONReader producing non-String keys. Replace with an instanceof
    pattern that debug-logs and skips entries whose key cannot be converted to a
    parameter path.
---
 .../org/apache/struts2/json/JSONInterceptor.java   |  9 +++++++-
 .../apache/struts2/json/JSONInterceptorTest.java   | 26 ++++++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java
index a0279f2f0..d0acfd4ce 100644
--- a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java
+++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java
@@ -215,7 +215,14 @@ public class JSONInterceptor extends AbstractInterceptor {
         Iterator<Map.Entry> it = json.entrySet().iterator();
         while (it.hasNext()) {
             Map.Entry entry = it.next();
-            String key = (String) entry.getKey();
+            if (!(entry.getKey() instanceof String key)) {
+                // Defensive: a custom JSONReader could produce non-String keys. Skip — we cannot
+                // construct a parameter path for authorization, and JSONPopulator wouldn't bind
+                // these to bean properties anyway.
+                LOG.debug("Skipping JSON entry with non-String key [{}] of type [{}] under prefix [{}]",
+                        entry.getKey(), entry.getKey() == null ? "null" : entry.getKey().getClass().getName(), prefix);
+                continue;
+            }
             String fullPath = prefix.isEmpty() ? key : prefix + "." + key;
 
             if (!parameterAuthorizer.isAuthorized(fullPath, target, action)) {
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
index 2203f6f29..ece2f1272 100644
--- a/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
+++ b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
@@ -583,6 +583,32 @@ public class JSONInterceptorTest extends StrutsTestCase {
         assertNull(action.getBar());
     }
 
+    public void testNonStringKeysAreSkippedByAuthorizationFilter() throws Exception {
+        // Simulate a custom JSON reader producing a Map with a non-String key.
+        // The authorizer should skip the entry rather than throw ClassCastException.
+        JSONInterceptor interceptor = new JSONInterceptor();
+        JSONUtil jsonUtil = new JSONUtil();
+        jsonUtil.setReader(new StrutsJSONReader());
+        jsonUtil.setWriter(new StrutsJSONWriter());
+        interceptor.setJsonUtil(jsonUtil);
+        interceptor.setParameterAuthorizer((parameterName, target, action) -> true);
+
+        java.util.Map<Object, Object> mixedKeyMap = new java.util.LinkedHashMap<>();
+        mixedKeyMap.put("validKey", "ok");
+        mixedKeyMap.put(42, "shouldBeSkipped"); // Integer key, not String
+
+        java.lang.reflect.Method method = JSONInterceptor.class.getDeclaredMethod(
+                "filterUnauthorizedKeys", java.util.Map.class, Object.class, Object.class);
+        method.setAccessible(true);
+
+        // Should not throw ClassCastException
+        method.invoke(interceptor, mixedKeyMap, new TestAction(), new TestAction());
+
+        // The non-String key entry should still be present (skipped, not removed)
+        assertTrue("non-String-key entry should remain (skipped, not removed)", mixedKeyMap.containsKey(42));
+        assertTrue("String-key entry should remain", mixedKeyMap.containsKey("validKey"));
+    }
+
     public void testParameterAuthorizerAllowsAllWhenPermissive() throws Exception {
         // Same JSON body, but authorizer allows all
         this.request.setContent("{\"foo\":\"value1\", \"bar\":\"value2\"}".getBytes());