From d4b01caded8453bc3d02bd6ecff72e264dc81dd0 Mon Sep 17 00:00:00 2001 From: tranquac Date: Mon, 6 Apr 2026 22:16:38 +0700 Subject: [PATCH] fix(rest-plugin): enforce @StrutsParameter in JacksonJsonHandler.toObject() JacksonJsonHandler.toObject() uses ObjectMapper.readerForUpdating(target) to merge JSON request body directly into the action object, bypassing the @StrutsParameter annotation check that ParametersInterceptor enforces for URL parameters. This allows mass assignment of unannotated properties via REST JSON request body. When struts.parameters.requireAnnotations is enabled, deserialize JSON into a map first, then filter properties by @StrutsParameter annotation on the target's setter methods before setting them. Only annotated setters are populated, consistent with ParametersInterceptor behavior. When requireAnnotations is disabled, preserve the original unrestricted readerForUpdating() merge for backwards compatibility. --- .../rest/handler/JacksonJsonHandler.java | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/JacksonJsonHandler.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/JacksonJsonHandler.java index d97af08e38..bd929247e6 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/JacksonJsonHandler.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/JacksonJsonHandler.java @@ -21,28 +21,82 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.SerializationFeature; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.struts2.ActionInvocation; import org.apache.struts2.inject.Inject; import org.apache.struts2.StrutsConstants; +import org.apache.struts2.interceptor.parameter.StrutsParameter; +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; import java.io.IOException; import java.io.Reader; import java.io.Writer; +import java.lang.reflect.Method; +import java.util.Map; /** * Handles JSON content using jackson-lib */ public class JacksonJsonHandler implements ContentTypeHandler { + private static final Logger LOG = LogManager.getLogger(JacksonJsonHandler.class); private static final String DEFAULT_CONTENT_TYPE = "application/json"; private String defaultEncoding = "ISO-8859-1"; private ObjectMapper mapper = new ObjectMapper(); + private boolean requireAnnotations = false; + + @Inject(value = StrutsConstants.STRUTS_PARAMETERS_REQUIRE_ANNOTATIONS, required = false) + public void setRequireAnnotations(String requireAnnotations) { + this.requireAnnotations = org.apache.commons.lang3.BooleanUtils.toBoolean(requireAnnotations); + } @Override public void toObject(ActionInvocation invocation, Reader in, Object target) throws IOException { mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); - ObjectReader or = mapper.readerForUpdating(target); - or.readValue(in); + if (requireAnnotations) { + // Deserialize into a map first, then filter by @StrutsParameter annotation + @SuppressWarnings("unchecked") + Map jsonMap = mapper.readValue(in, Map.class); + applyAnnotatedProperties(target, jsonMap); + } else { + ObjectReader or = mapper.readerForUpdating(target); + or.readValue(in); + } + } + + /** + * Sets only properties whose setter method is annotated with @StrutsParameter, + * consistent with ParametersInterceptor behavior for URL parameters. + */ + private void applyAnnotatedProperties(Object target, Map jsonMap) { + try { + BeanInfo info = Introspector.getBeanInfo(target.getClass()); + PropertyDescriptor[] props = info.getPropertyDescriptors(); + for (PropertyDescriptor prop : props) { + String name = prop.getName(); + if (!jsonMap.containsKey(name)) { + continue; + } + Method setter = prop.getWriteMethod(); + if (setter == null) { + continue; + } + if (setter.getAnnotation(StrutsParameter.class) == null) { + LOG.debug("REST JSON property '{}' rejected: setter [{}] missing @StrutsParameter annotation", + name, setter.getName()); + continue; + } + // Use Jackson to convert the value to the correct type and set it + Object value = jsonMap.get(name); + Object converted = mapper.convertValue(value, setter.getParameterTypes()[0]); + setter.invoke(target, converted); + } + } catch (Exception e) { + LOG.error("Error applying annotated properties from JSON", e); + } } @Override