Files
struts/plugins/rest
Lukasz Lenart 09d03286f8 WW-5626 per-property authorization for Jackson REST handlers (#1674)
* WW-5626 spike: validate Jackson per-property authorization mechanism

Validates that the Approach C design is feasible before committing to a detailed
implementation plan. Wraps each SettableBeanProperty via BeanDeserializerModifier;
intercepts deserializeAndSet to authorize against a path built from a ThreadLocal
Deque; uses skipChildren() to discard unauthorized values; uses [0] suffix for
collection/map/array elements to match ParametersInterceptor depth semantics.

Findings:
- Delegating base class via 'protected delegate' field is the right pattern
- addOrReplaceProperty(prop, true) is the correct builder API
- Reject-at-parent skips all nested deserialization (better security than two-phase
  copy: setter side effects on unauthorized properties never fire)
- JavaType#isCollectionLikeType/isMapLikeType/isArrayType detects the indexed-path case

Spike is kept under .../spike/ as a learning artifact; it will be replaced by
production code + tests in subsequent commits.

* WW-5626 add ParameterAuthorizationContext for deserializer-level authorization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* WW-5626 address review feedback on ParameterAuthorizationContext

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* WW-5626 add AuthorizationAwareContentTypeHandler marker interface

* WW-5626 add AuthorizingSettableBeanProperty for Jackson per-property authorization

* WW-5626 add ParameterAuthorizingModule installing the property wrapper on Jackson mappers

* WW-5626 register ParameterAuthorizingModule on default Jackson REST handlers

* WW-5626 use AuthorizationAwareContentTypeHandler path when handler supports it

* WW-5626 add integration tests proving the new Jackson authorization path is used

* WW-5626 deprecate XStreamHandler in favor of JacksonXmlHandler

* WW-5626 remove Jackson auth spike; replaced by production tests

* WW-5626 make JuneauXmlHandler authorization-aware via post-parse walk

Implements AuthorizationAwareContentTypeHandler. When ParameterAuthorizationContext
is active (set by ContentTypeInterceptor when requireAnnotations=true), the handler
walks the parsed result tree and copies only authorized properties to the target,
descending into nested beans/collections/maps/arrays with indexed-path semantics
([0] suffix) for parity with ParametersInterceptor.

Note: Juneau parses the entire result tree before our walk runs, so setter side
effects on transient nested objects can fire even for unauthorized properties —
those transient objects are then discarded. This is functionally equivalent to the
legacy two-phase copy in ContentTypeInterceptor; only the Jackson handlers achieve
the stronger guarantee where unauthorized subtrees are never instantiated at all
(they use Jackson's BeanDeserializerModifier + skipChildren).

When no context is bound (default config), behavior is unchanged: parser.parse +
BeanUtils.copyProperties.

* WW-5626 add JuneauXmlHandler integration tests for @StrutsParameter authorization

* WW-5626 test(rest): cover JuneauXmlHandler post-parse walk for collections, maps, arrays

Sonar reported 51 uncovered new lines in JuneauXmlHandler (48.8% coverage on the
post-parse authorization walk — the security-critical code path the branch exists
to introduce). Add integration coverage for the previously-uncovered branches:

- collection-of-scalars (List<String> tags)
- collection-of-beans (List<Address> addresses)
- map-of-scalars (Map<String,String> attributes)
- array-of-scalars (String[] aliases)
- empty collection
- malformed XML wrapped as IOException

Also drop two unnecessary casts (Sonar S1905) on lines 243/252 — the unchecked
conversion happens at the return statement, the explicit casts were redundant
under the existing @SuppressWarnings("unchecked").

Add @Override on the inline AnyConstraintMatcher.matches override (Sonar S1161).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* WW-5626 test(rest): cover AuthorizingSettableBeanProperty builder-path deserialization

Sonar reported 11 uncovered new lines on AuthorizingSettableBeanProperty (66.7%
coverage). All 11 are in deserializeSetAndReturn — the alternate Jackson entry
point used for builder-pattern deserialization, never triggered by setter-based
fixtures like Person.

Add an @JsonDeserialize(builder=...) fixture (ImmutablePerson) that forces
Jackson to use BuilderBasedDeserializer, which dispatches property writes
through deserializeSetAndReturn. Three new tests exercise the path:
inactive-context pass-through, top-level authorization, and full rejection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* WW-5626 refactor(rest): extract helpers from ContentTypeInterceptor.intercept

Sonar S3776 flagged intercept() at cognitive complexity 16 (limit 15). Extract
the body-handling branches into named helpers:

- openBodyReader: encoding-aware reader from the request InputStream
- applyRequestBody: dispatcher between requireAnnotations on/off paths
- applyWithAuthorizationContext: bind + delegate + unbind for AuthorizationAware handlers
- applyTwoPhaseDeserialize: legacy fresh-instance + copyAuthorizedProperties path

intercept() drops to ~12 lines and reads as a flat sequence: resolve target,
delegate body application, invoke. Each helper carries the comment that
explains the security model for its branch.

Add @Override on the inline AnyConstraintMatcher.matches override (Sonar S1161).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 15:01:00 +02:00
..

Struts 2 REST plugin

The REST Plugin provides high level support for the implementation of RESTful resource based web applications with the Convention Plugin. You will find more details in documentation.

Installation

Just drop this plugin JAR into WEB-INF/lib folder or add it as a Maven dependency.