This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-4858-json-parameter-filtering in repository https://gitbox.apache.org/repos/asf/struts.git
commit d2ebbedb218317b84e33b9de5b5b9752e16472f3 Author: Lukasz Lenart <[email protected]> AuthorDate: Wed Jul 8 20:48:09 2026 +0200 WW-4858 feat(json): opt-in applying excludeProperties/includeProperties to JSON input Co-Authored-By: Claude Opus 4.8 <[email protected]> --- .../org/apache/struts2/json/JSONInterceptor.java | 35 ++++++++++++++++++++++ .../apache/struts2/json/JSONInterceptorTest.java | 35 ++++++++++++++++++++++ 2 files changed, 70 insertions(+) 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 a3d919e9c..0d584d830 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 @@ -89,6 +89,7 @@ public class JSONInterceptor extends AbstractInterceptor { private int paramNameMaxLength = 100; private Set<Pattern> excludedValuePatterns; private Set<Pattern> acceptedValuePatterns; + private boolean applyPropertyFiltersToInput = false; @SuppressWarnings("unchecked") public String intercept(ActionInvocation invocation) throws Exception { @@ -291,6 +292,29 @@ public class JSONInterceptor extends AbstractInterceptor { LOG.debug("JSON body parameter [{}] rejected by ParameterNameAware action", fullPath); return false; } + if (applyPropertyFiltersToInput && !isAcceptedByPropertyFilters(fullPath)) { + LOG.debug("JSON body parameter [{}] rejected by excludeProperties/includeProperties on input", fullPath); + return false; + } + return true; + } + + private boolean isAcceptedByPropertyFilters(String fullPath) { + if (excludeProperties != null) { + for (Pattern pattern : excludeProperties) { + if (pattern.matcher(fullPath).matches()) { + return false; + } + } + } + if (includeProperties != null && !includeProperties.isEmpty()) { + for (Pattern pattern : includeProperties) { + if (pattern.matcher(fullPath).matches()) { + return true; + } + } + return false; + } return true; } @@ -767,6 +791,17 @@ public class JSONInterceptor extends AbstractInterceptor { this.acceptedValuePatterns = compileValuePatterns(commaDelim); } + /** + * When enabled, the interceptor's {@code excludeProperties}/{@code includeProperties} patterns — + * otherwise used only for serialization output — also gate which JSON keys are populated on input. + * Opt-in; defaults to {@code false} to preserve existing behavior. + * + * @param applyPropertyFiltersToInput true to apply property filters to deserialization + */ + public void setApplyPropertyFiltersToInput(boolean applyPropertyFiltersToInput) { + this.applyPropertyFiltersToInput = applyPropertyFiltersToInput; + } + private static Set<Pattern> compileValuePatterns(String commaDelim) { Set<String> raw = TextParseUtil.commaDelimitedStringToSet(commaDelim); Set<Pattern> compiled = new HashSet<>(raw.size()); 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 bfa0abf0c..89ec91dfb 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 @@ -831,6 +831,41 @@ public class JSONInterceptorTest extends StrutsTestCase { assertNull(action.getBar()); } + public void testExcludePropertiesAppliedToInputWhenEnabled() throws Exception { + this.request.setContent("{\"foo\":\"a\", \"bar\":\"b\"}".getBytes()); + this.request.addHeader("Content-Type", "application/json"); + + JSONInterceptor interceptor = createInterceptor(); + interceptor.setApplyPropertyFiltersToInput(true); + interceptor.setExcludeProperties("foo"); + TestAction action = new TestAction(); + + this.invocation.setAction(action); + this.invocation.getStack().push(action); + + interceptor.intercept(this.invocation); + + assertNull(action.getFoo()); + assertEquals("b", action.getBar()); + } + + public void testExcludePropertiesNotAppliedToInputByDefault() throws Exception { + this.request.setContent("{\"foo\":\"a\", \"bar\":\"b\"}".getBytes()); + this.request.addHeader("Content-Type", "application/json"); + + JSONInterceptor interceptor = createInterceptor(); + interceptor.setExcludeProperties("foo"); // configured, but flag off + TestAction action = new TestAction(); + + this.invocation.setAction(action); + this.invocation.getStack().push(action); + + interceptor.intercept(this.invocation); + + assertEquals("a", action.getFoo()); + assertEquals("b", action.getBar()); + } + @Override protected void setUp() throws Exception { super.setUp();
