You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@struts.apache.org by "Lukasz Lenart (Jira)" <ji...@apache.org> on 2026/09/11 14:46:00 UTC

[jira] [Created] (WW-5726) Merged polymorphic property with a non-null initial value is not authorized in the REST plugin

Lukasz Lenart created WW-5726:
---------------------------------

             Summary: Merged polymorphic property with a non-null initial value is not authorized in the REST plugin
                 Key: WW-5726
                 URL: https://issues.apache.org/jira/browse/WW-5726
             Project: Struts 2
          Issue Type: Bug
            Reporter: Lukasz Lenart
             Fix For: 7.4.0


h2. Summary

A property that is mergeable ({{@JsonMerge}}, or merge enabled through configuration), carries a value type deserializer ({{@JsonTypeInfo}} on the property or its declared type), and has a non-null initial value is deserialized by Jackson through a path that reaches neither {{AuthorizingSettableBeanProperty}} nor {{AuthorizingValueDeserializer}}. The property is assigned without an authorization check, and its members are authorized against the enclosing prefix instead of their own.

Sibling of WW-5725, which covers the other {{set()}} and value-deserializer entry points; this one cannot be closed from the property wrapper.

h2. Current behaviour

{{MergingSettableBeanProperty}} is constructed by {{BeanDeserializerBase.resolve()}} around the already-wrapped property, so {{BeanDeserializer}} calls its {{deserializeAndSet}}, which never reaches {{AuthorizingSettableBeanProperty.deserializeAndSet}}. With a non-null current value it calls the {{final}} {{SettableBeanProperty.deserializeWith(p, ctxt, oldValue)}}. When {{_valueTypeDeserializer}} is set, that method derives the subtype from {{oldValue.getClass()}}, resolves a fresh contextual deserializer for it, and deserializes in place into {{oldValue}} — bypassing the property's {{_valueDeserializer}} (where {{AuthorizingValueDeserializer}} sits). Because the merge is in place, {{newValue == oldValue}} and {{set()}} is not called either.

Consequences: the property itself is never authorized, and no path is pushed, so the merged bean's members are authorized as {{member}} at the enclosing prefix rather than {{property.member}}. A grant on a sibling property of the enclosing bean with the same member name therefore authorizes the nested write. No type id is required in the body; the subtype comes from the existing value.

The non-polymorphic merge (no type deserializer) goes through {{_valueDeserializer.deserialize(p, ctxt, oldValue)}} and is covered by WW-5725.

Applies to JSON and XML bodies alike. 6.x is unaffected: the REST plugin there has no Jackson authorization module.

h2. Proposed change

Both facts are known when {{ParameterAuthorizingModule.updateBuilder}} runs: {{property.getMetadata().getMergeInfo() != null}} and {{property.hasValueTypeDeserializer()}}. Refuse the combination there, so the definition fails closed rather than binding unchecked — either by reporting a bad definition or by stripping the merge for that property so it takes the ordinary authorized path.

The alternative of intercepting the in-place typed deserialize from {{RedactionAwareDeserializer.createContextual}} would keep the merge working, but it has to avoid a second path push on the normal path and is the more fragile of the two.

Add a test in the same direction as the WW-5725 tests: a {{@JsonMerge}} {{@JsonTypeInfo}} property with an initialised field, an authorizer granting the enclosing bean's sibling member of the same name, asserting the nested member is not assigned.

h2. Compatibility notes

An application that relies on merging into a polymorphic property with a non-null initial value through the REST plugin will see that definition rejected (or merge disabled for it) until the property is given a null initial value or the type deserializer is dropped. No other property kind is affected.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)