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