Fix: PATCH /api/components/{id} silently resets visibility to EVERYONE - #4070
Fix: PATCH /api/components/{id} silently resets visibility to EVERYONE #4070Aman-Cool wants to merge 4 commits into
Conversation
Context: Why this bug exists, and what's comingThis PR fixes one specific production bug, but it's worth documenting the broader picture for reviewers who want to understand the root cause more deeply and why a more comprehensive structural fix is on the horizon. What else is currently broken by the Thrift layerThis visibility reset is one symptom of a deeper architectural issue. The following production problems all trace directly to Apache Thrift and exist in the codebase today: Silent transport failures returned as 404 Not Found Wrong HTTP status codes for backend failures Null transport silently constructed on connection failure No connection pooling, a new HTTP connection per RPC call
~1,200 lines of Jackson MixIn boilerplate serving no business purpose How the GSoC 2026 migration fixes all of the aboveThe GSoC 2026 project Migrate SW360 from Apache Thrift to direct Spring Bean injection replaces the Thrift RPC layer with direct
|
| // that extra/unrecognised keys in the PATCH body (e.g. "invalid_property", legacy names) | ||
| // are silently ignored — matching the behaviour of every other convertValue call in the | ||
| // codebase. The shared bean is never mutated. | ||
| return objectMapper.copy() |
There was a problem hiding this comment.
Why involve objectMapper here? Spring already does it for us if we simply tell the parameter type???
There was a problem hiding this comment.
The Map is needed because we need to know which fields the caller actually included in the request body. If we declare @RequestBody ComponentDTO and let Spring deserialize directly, Thrift's generated no-arg constructor has already initialised visbility to EVERYONE before Jackson sets any fields; so after deserialization there's no way to tell "field was omitted" from "field was explicitly sent as EVERYONE". That's the root cause of the original bug.
As for the objectMapper usage; the shared bean is created as new ObjectMapper() with no FAIL_ON_UNKNOWN_PROPERTIES configuration, so it defaults to strict/true. The .copy().configure(FAIL_ON_UNKNOWN_PROPERTIES, false) is needed to avoid failing on any unrecognised key in the PATCH body. That would apply equally to @RequestBody JsonNode + treeToValue, so the objectMapper step can't be dropped either way.
@RequestBody Map<String, Object> is also the existing pattern across ProjectController, ReleaseController, and PackageController for the same field-presence reason, so I kept it consistent with those.
2e71e84 to
87ac671
Compare
…e with FAIL_ON_UNKNOWN=false
ec20843 to
1c3c65b
Compare
Any
PATCH /api/components/{id}request that omits thevisbilityfield silently overwrites the component's existing visibility toEVERYONE, regardless of what it was before. A component previously restricted toME_AND_MODERATORSorPRIVATEbecomes world-visible after any routine PATCH; updating a description, a homepage, anything; with no warning in the response.The fix follows the same pattern already used by
updateProject(): accept the rawMap<String, Object>request body, guard each field withreqBodyMap.containsKey(), and only apply fields the caller actually sent.optional Visibility visbility = sw360.Visibility.EVERYONEonComponentDTO. Thrift's Java generator initialises this field toVisibility.EVERYONEin the generated no-arg constructor, so after Jackson deserialises a PATCH body that omitsvisbility, the DTO already carriesEVERYONE; indistinguishable from an explicit"visbility": "EVERYONE". The merge loop inupdateComponent()checksfieldValue != null, andEVERYONEis not null, so it unconditionally overwrites the stored visibility on every PATCH. With a plain Java POJO the default would benull, the guard would hold, and the bug would not exist. This is a direct consequence of Thrift IDL default injection, and is part of the motivation behind the proposed GSoC 2026 project Migrate SW360 from Apache Thrift to direct Spring Bean injection.Issue: Fixes silent visibility reset on
PATCH /api/components/{id};visbilitysilently overwritten toEVERYONEwhenever the field is omitted from the request body.Changes
ComponentController.javapatchComponent()now takes@RequestBody Map<String, Object> reqBodyMap; deserialises toComponentDTOviaconvertToComponentDTO()using the sharedObjectMapper(preservesxssPreventionModule) withFAIL_ON_UNKNOWN_PROPERTIES=falseRestControllerHelper.javaupdateComponent()receivesreqBodyMapand skips any field not present viareqBodyMap.containsKey(field.getFieldName());convertToComponent()now unconditionally copiesvisbilityfrom the DTOComponentSpecTest.javaSuggest Reviewer
@GMishx @amritkv @rudra-superrr @bibhuti230185
How To Test?
visbilityfield:Before:
GETreturns"visbility": "EVERYONE"; restriction silently lost.After:
GETreturns"visbility": "ME_AND_MODERATORS"; preserved."visbility": "EVERYONE"; should update correctly (this case was also broken by the previous sentinel-based attempt at a fix and is now covered by the regression tests).Checklist
Must: