WW-5642 fix(rest): authorize @StrutsParameter on record/creator-bound REST body properties#1774
Open
g0w6y wants to merge 1 commit into
Open
Conversation
…dy properties ParameterAuthorizingModule enforces @StrutsParameter on REST/JSON body deserialization by wrapping each property's deserializeAndSet/ deserializeSetAndReturn. Jackson never calls either method for creator-bound properties (Java records, @JsonCreator constructors, @ConstructorProperties) — it calls SettableBeanProperty#deserialize directly, which is declared final and bypasses the wrapper entirely. With struts.parameters.requireAnnotations enabled, any record-typed field anywhere in a REST action's request body was populated with no authorization check at all. Add AuthorizingValueDeserializer, which wraps the property's value deserializer instead of the property itself, and install it from AuthorizingSettableBeanProperty#withValueDeserializer — scoped to CreatorProperty so ordinary setter/field/builder properties, already authorized via the existing wrapper, aren't checked twice.
Member
|
Thanks for the PR but please create a JIRA ticket to cover this work 🙏 |
Contributor
Author
|
Created WW-5642 to track this: https://issues.apache.org/jira/browse/WW-5642. Ready for review/merge whenever you get a chance — thanks! |
Contributor
Author
|
Given this fixes an authorization bypass where unauthorized REST body fields could be set, should we handle this as a security advisory instead of a normal fix? Happy to follow whichever process you prefer, just let me know. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes WW-5642
Summary
ParameterAuthorizingModuleenforces@StrutsParameteron REST/JSON body deserialization by wrapping each Jackson property'sdeserializeAndSet/deserializeSetAndReturn(AuthorizingSettableBeanProperty). Jackson never calls either method for creator-bound properties — Javarecords,@JsonCreatorconstructors,@ConstructorProperties— it instead callsSettableBeanProperty#deserializedirectly, which Jackson declaresfinal, so the existing wrapper cannot intercept it.Practical effect: with
struts.parameters.requireAnnotationsenabled, anyrecord-typed field anywhere in a REST action's request body (top-level or nested) is populated with no@StrutsParametercheck at all, silently defeating the protection for that entire subtree. This is easy to hit unintentionally —recordis the idiomatic way to model immutable REST DTOs on the Java versions Struts now targets.Verified with a live reproduction before writing the fix: a
recordfield with an unauthorized component was populated anyway; after the fix, the same component is correctly rejected while an authorized sibling component still passes through.Fix
Add
AuthorizingValueDeserializer, which wraps the property's value deserializer rather than theSettableBeanProperty— the value deserializer is what the finaldeserialize()method delegates to, so wrapping it is the only available interception point for the creator-bound path. It is installed fromAuthorizingSettableBeanProperty#withValueDeserializer, scoped toCreatorPropertyspecifically, so ordinary setter/field/builder-pattern properties — already correctly authorized via the existingdeserializeAndSet/deserializeSetAndReturnoverrides — are not checked a second time (which would also double-push the path-authorization stack and produce incorrect nested paths).Test plan
testRecordComponentAuthorizedByPathinParameterAuthorizingModuleTest: arecord-typed nested property with one authorized and one unauthorized component — confirms the unauthorized component is rejected and the authorized one still deserializes.ParameterAuthorizingModuleTestsuite (setter, builder, nested, collection, map, array authorization paths) passes unchanged — confirms no regression from scoping the new check to creator-bound properties only.struts2-rest-pluginmodule test suite passes (106/106).