Aman-Mittal opened a new issue, #693:
URL: https://github.com/apache/fineract-backoffice-ui/issues/693

   Editing a role's permissions never works. Not for some roles, not for some 
permissions — **for every role, by every administrator, whatever is ticked.**
   
   ## Reproducing
   
   1. Sign in as `mifos` on a stock instance.
   2. **Admin → Roles → Create**, give it a name and description, Save. The 
form lands on the new role's permission matrix, as intended.
   3. Tick any permission. The impact panel updates correctly ("5 permission(s) 
added").
   4. Save, confirm the dialog.
   5. Nothing happens. The form stays put, the spinner clears, and no error is 
shown.
   
   The network tab:
   
   ```
   PUT /roles/153  → 200
   PUT /roles/153/permissions → 404
   {
     "developerMessage": "The requested resource is not available.",
     "httpStatusCode": "404",
     "userMessageGlobalisationCode": "error.msg.resource.not.found",
     "errors": [{ "developerMessage": "Permission with Code 
READ_STANDINGINSTRUCTION does not exist" ... }]
   }
   ```
   
   The description edit (the first PUT) succeeds, so the role is left 
half-saved.
   
   ## Cause
   
   Fineract's permission catalogue contains five codes with a **trailing 
space** — verified against the local e2e stack, `GET /v1/permissions`, 719 
codes:
   
   ```
   "CREATE_STANDINGINSTRUCTION "   "DELETE_STANDINGINSTRUCTION "
   "READ_STANDINGINSTRUCTION "     "UPDATE_STANDINGINSTRUCTION "
   "READ_ClientSummary "
   ```
   
   `READ_STANDINGINSTRUCTION` and `READ_ClientSummary` exist **only** in the 
padded form — there is no unpadded twin. The other three exist in both, so 
trimming them collides two genuinely distinct codes onto one key.
   
   `role-form.component.ts` trims each code as it loads the matrix:
   
   ```ts
   code: (row['code'] as string).trim(),
   ```
   
   That is **correct, and necessary**. `AuthService.hasPermission()` compares a 
route's declared code against the session's, and `DOCS/RBAC.md` already records 
that without the trim a gate on `READ_STANDINGINSTRUCTION` could never be 
satisfied by any role.
   
   The bug is that the same trimmed keys are then sent back. `PUT 
/roles/{id}/permissions` looks each key up literally, and because the payload 
is the entire code-to-boolean map — which it must be, since the endpoint 
applies it as a delta — the request fails regardless of what the user changed.
   
   So the two directions are different jobs: trimming on the way **in** is 
right, and trimming on the way **out** is the bug.
   
   ## Why no test caught it
   
   Three things, and they are the three ADR 0006 names:
   
   - `role-form.component.test.ts` mocks `putRolesRoleIdPermissions`, so the 
platform never sees the payload.
   - Its `PERMISSIONS` fixture contained no padded code, so no assertion could 
have noticed even in principle.
   - Every `rbac-*` e2e spec grants permissions over HTTP through 
`e2e/utils/seed-api.ts` rather than through this screen. The one place in the 
application that writes this endpoint had never been driven against a real 
Fineract.
   
   That last point is the generalisable one: a screen nothing exercises end to 
end can be completely broken while its unit tests stay green.
   
   ## Impact
   
   Role permission management is unusable through the UI. The workaround is 
`PUT /roles/{id}/permissions` by hand, with the padded codes spelled correctly 
— which also means any deployment that has configured roles did so outside this 
application.
   
   Severity is worth stating plainly: this is the screen an administrator uses 
to grant and revoke access. A silent failure here looks like "the permission 
did not take effect", which is indistinguishable from an RBAC bug elsewhere.
   
   ## Fix
   
   #692. `RolePermission` on the new `ROLE_API` contract carries both spellings 
and fixes which is used where:
   
   ```ts
   export interface RolePermission {
     /** Fineract's spelling, trailing space and all. The key setPermissions 
must be given. */
     readonly code: string;
     /** code trimmed. What the matrix shows, groups and filters on. */
     readonly label: string;
     …
   }
   ```
   
   `setPermissions` takes `readonly PermissionSelection[]` rather than a 
`Record<code, boolean>`, so the caller cannot pick a spelling at all — the rows 
come from what `permissions()` answered. Making the wrong spelling 
*unreachable* rather than merely discouraged is the point; a map keyed by 
string invites exactly this mistake, and the readable key is the wrong one.
   
   Trimming survives in one place, `destinationsFor()`, which asks 
`NavigationConfigService` what screens a selection opens — a comparison against 
the codes routes declare, so the trimmed form is correct there and is commented 
as the read side.
   
   Regression coverage, both halves:
   
   - `fineract-role.api.test.ts` — a padded code in the fixture, asserting the 
padded spelling goes out and the trimmed one does not.
   - `role-form.component.test.ts` — `READ_STANDINGINSTRUCTION ` added to its 
fixture, plus a case asserting the component hands the contract Fineract's 
spelling.
   - `e2e/loan-flows-non-admin-roles.spec.ts` — builds four roles through this 
screen against a real platform, which is what found it.


-- 
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