unbridled-41 opened a new pull request, #3342:
URL: https://github.com/apache/rocketmq-dashboard/pull/3342

   ### Problem / Evidence
   
   Editing an ACL user and clearing the **Associated clusters** tag select (or 
clearing a rule's actions through `POST /api/acl/rules/update` with `actions: 
[]`) reports success — the UI even shows the cleared list — but the next reload 
brings the old values back: the update was never written to the database.
   
   Trigger path (user update): ACL page user modal (clusters `Select 
mode="tags" allowClear`, sends `clusters: []`) → `POST /api/acl/users/update` → 
`AclService.updateUser` merges `[]` as the explicit new value → 
`MybatisPlusAclRepository.replaceUser` → `toUserEntity` maps `[]` to `clusters 
= null` via `joinNormalizedCsv` → MyBatis-Plus default `NOT_NULL` update 
strategy omits the column from `updateById`, so the old CSV survives.
   
   The regression test fails before the fix with:
   
   ```
   Wanted but not invoked:
   userMapper.update(isNull(), <Capturing argument: UpdateWrapper>);
   userMapper.updateById(RmqAclUser(id=1, ..., clusters=null, ...))
   ```
   
   ### Root cause / Fix
   
   `replaceUser`/`replaceRule` rely on `updateById` with a null field for a 
"clear" intent, which MyBatis-Plus silently skips. This exact trap is already 
known in the same class: `createAndUpdatePlainAccessConfig` assigns 
`white_remote_address` explicitly for the cleared case ("MyBatis-Plus omits 
null entity fields from updateById"). The user/rule list columns got no such 
handling.
   
   Fix: in `replaceUser`, after a successful `updateById`, issue an explicit 
`UpdateWrapper.set("clusters", null)` when the incoming VO carries a non-null 
list that normalizes to empty. Same for `replaceRule` and `actions`. Null still 
means "keep existing", so partial updates are unaffected, and the plain-access 
path that intentionally passes `clusters = null` (relying on the skip) is 
untouched.
   
   ### Priority & scoring
   
   - Impact 34/40 — silent data loss masked as success on a core admin flow (a 
user believed to be cluster-unbound keeps its bindings).
   - Scope 14/20 — ACL user update (UI-reachable) and rule update 
(API-reachable), one repository.
   - Reproducibility 20/20 — deterministic; UI-only trigger for the user case.
   - Maintenance value 16/20 — follows an existing in-repo workaround pattern; 
low-risk addition.
   - **PRIORITY = 84, FIX_CONFIDENCE = 85** (≥70/≥80 per the contribution bar).
   
   ### Tests
   
   - New `MybatisPlusAclRepositoryTest` cases: 
`replaceUserShouldExplicitlyClearClusterBindingsWhenListIsEmpty`, 
`replaceUserShouldKeepClusterBindingsWhenNoneProvided`, 
`replaceRuleShouldExplicitlyClearActionsWhenListIsEmpty`. The two clear-cases 
failed before the fix and pass after; the keep-case pins the 
null-keeps-existing semantics.
   - `mvn -B -ntp test 
-Dtest='MybatisPlusAclRepositoryTest,AclServiceTest,AclControllerTest'` → 
22/22, 64/64, 27/27 passing.
   - Full `mvn -B -ntp test` on this branch: 2038 tests (pristine baseline 2035 
+ 3 new). Failures: `AuthCorsIntegrationTest` ×2 and 
`AliyunInstanceProviderTest.getGroupProgressShouldMapLagRowsTest` — identical 
to the recorded pristine baseline — plus one load-flaky 
`OpenAiCompatibleLlmGatewayTest` case ("AI chat capacity is temporarily 
exhausted") that passes 9/9 in isolation and touches no ACL code. Zero new 
failures.
   
   ### Risk
   
   Low. The extra statement runs only when a non-null list normalizes to empty 
(the clear case); every other update path keeps the previous behavior. The 
explicit set uses the same `UpdateWrapper` shape as the existing 
`white_remote_address` workaround.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to