Skip to content

WW-5626 cleanup follow-ups for @StrutsParameter JSON/REST enforcement#1673

Open
lukaszlenart wants to merge 5 commits intomainfrom
WW-5626-cleanup
Open

WW-5626 cleanup follow-ups for @StrutsParameter JSON/REST enforcement#1673
lukaszlenart wants to merge 5 commits intomainfrom
WW-5626-cleanup

Conversation

@lukaszlenart
Copy link
Copy Markdown
Member

Fixes WW-5626.

Part 1 of 2. Approach C — handler-level per-property authorization that replaces ContentTypeInterceptor's two-phase deserialize-then-copy with a new AuthorizationAwareContentTypeHandler interface — will follow in a separate PR. This PR addresses only the three concerns from the WW-5624 review that survive that refactor:

Changes

  • Centralized ModelDriven target resolution. ParametersInterceptor.isParameterAnnotatedAndAllowlist previously did the value-stack peek itself, then StrutsParameterAuthorizer.isAuthorized independently checked target != action && action instanceof ModelDriven. New ParameterAuthorizer.resolveTarget(action) consolidates the logic. Kept as a default method to preserve the interface as a SAM (lambda-based test stubs continue to work).

  • Defensive guard against non-String JSON keys. JSONInterceptor.filterUnauthorizedKeysRecursive did String key = (String) entry.getKey(); on a raw Map. Safe with the built-in StrutsJSONReader, but a custom reader producing non-String keys would throw ClassCastException. Replaced with instanceof String key pattern that debug-logs and skips.

  • Real REST integration tests. ContentTypeInterceptorTest.testRequireAnnotationsEnabled_* use com.mockobjects mocks for ContentTypeHandler, so toObject is a no-op — the tests prove intercept() returns SUCCESS but assert nothing about which properties were actually filtered. New ContentTypeInterceptorIntegrationTest uses a real JacksonJsonHandler and a real StrutsParameterAuthorizer end-to-end with a mixed-annotation SecureRestAction fixture.

Finding for follow-up PR

Building the REST integration tests surfaced a real semantic divergence (documented inline in SecureRestAction.java): REST's recursive copy authorizes EACH path level independently, so @StrutsParameter(depth=1) on getAddress is not enough to bind address.city — the setter setAddress also needs @StrutsParameter to authorize the top-level address at depth 0. ParametersInterceptor only requires the getter annotation. This is captured for the Approach C PR.

Out of scope

ContentTypeInterceptor deep-copy/scrub complexity, the isNestedBeanType package-name heuristic, and the two-phase architectural smell — all to be replaced by Approach C.

Test plan

  • mvn test -DskipAssembly -pl core — 2926 tests, zero regressions
  • mvn test -DskipAssembly -pl plugins/json — 125 tests, zero regressions
  • mvn test -DskipAssembly -pl plugins/rest — 81 tests, zero regressions
  • mvn test -DskipAssembly -pl '!plugins/bean-validation,!plugins/tiles' — full multi-module BUILD SUCCESS (the two excluded modules fail identically on main for unrelated reasons: AnnotationFormatError from Hibernate Validator on a test model, and missing Velocity dependencies in the tiles plugin)

…iven resolution

Move the ValueStack peek logic that derives the target object from action+ModelDriven
state out of ParametersInterceptor and into ParameterAuthorizer. Callers that need
both authorization and the resolved target (for downstream OGNL allowlisting) can
now call resolveTarget once and reuse the result.
Replace the inline ValueStack peek in ParametersInterceptor#isParameterAnnotatedAndAllowlist
with a call to ParameterAuthorizer#resolveTarget. The ModelDriven import is no longer
needed in this class.
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.
…meter filtering

The existing ContentTypeInterceptorTest uses mock ContentTypeHandlers, so its
requireAnnotations=true tests verify only that intercept() returns SUCCESS — they
assert nothing about which properties were actually filtered. These integration
tests use a real JacksonJsonHandler + a real StrutsParameterAuthorizer to verify
end-to-end property-level filtering for top-level annotated/unannotated properties
and nested properties at varying authorized depths.

The SecureRestAction fixture documents a semantic divergence: REST's recursive
copy authorizes each path level independently, so depth-0 authorization on the
top-level property requires @StrutsParameter on the setter even when nested
field access is the actual goal. ParametersInterceptor only requires the getter
annotation. This divergence is tracked for the Approach C refactor.
…eserve SAM

Making resolveTarget abstract broke ParameterAuthorizer as a functional interface,
which the existing JSON and REST plugin tests rely on for lambda-based stubs:
  interceptor.setParameterAuthorizer((parameterName, target, action) -> true);

The default returns the action unchanged — adequate for lambda test stubs whose
authorization decisions don't depend on the resolved target. The production
implementation (StrutsParameterAuthorizer) overrides this with the proper
ModelDriven value-stack peek.
@lukaszlenart lukaszlenart marked this pull request as ready for review May 4, 2026 11:25
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 4, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant