Repository navigation
Conversation
Saving, deleting or assigning a role compared the target role's stored grants with the actor's defined permission keys. Sites installed with the earlier default seeders still give every default role role_actionok, a key no permission defines and no update removes, so a role manager holding every real permission of such a role was refused it. An always-on permission (action_ok on those sites) stored on the target by a later save but not on the actor's role blocked the same way. RoleGuard::reachOf() returns a role's grants of defined permissions that a role may hold or not, and both reach checks (role save/delete, role assignment on user save) use it. Every real permission still counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Role reach is judged by defined, optional permissions only, so a legacy role_actionok grant no longer locks a default role away from a role manager (review of evolution-cms#2577). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for addressing the legacy-role regression. I would still hold the merge at adb4e31: the permission filtering used for role reach is not fully aligned with the effective permissions established at login and used at runtime. A grant omitted from the reach comparison can still matter to runtime authorization, so the delegated role-management boundary is not yet consistently enforced. Please align the actor and target reach checks with the effective runtime grants, while preserving support for custom permissions and safely handling genuinely obsolete grants. The same rules should apply to role editing, deletion and assignment. Please add regression tests covering fresh and upgraded installations, legacy roles and custom permissions. They should verify both legitimate delegated operations and rejection when the target's effective permissions exceed the actor's permissions. This is a static review finding; I have not reproduced it on a running system. SokilAI |
At login the session holds every stored grant of the role, defined by a permissions row or not, so a plugin's own key authorizes as much as a core one. adb4e31 compared only grants of defined permissions on both sides: a role carrying such a key could be saved, assigned or deleted by a role manager who does not hold it. Both sides now follow the runtime grants. RoleGuard::heldBy() asks the actor's hasPermission() for every defined key and every key any role is granted; RoleGuard::reachOf() takes the target's stored grants without the obsolete keys the earlier default seeders left (RoleGuard::OBSOLETE: role_actionok, action_ok, which nothing checks) and without the always-on permissions, which a role save stores on every role whoever saves it. Role save, delete and assignment use the same two helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, confirmed at runtime and fixed in 044ecd3. Confirmed: at login the session receives every stored grant of the role ( Fix: both sides now follow the runtime grants, through two helpers that role save, delete and assignment all use:
Anything else a role is granted, including any custom key, counts. Regression tests (
Full suite in |
…nto 3.6.x Both sides of the role reach check follow the grants that authorize at runtime, custom keys included; only the obsolete role_actionok/action_ok and the always-on permissions are left out (review of evolution-cms#2578). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, 044ecd3 addresses the custom-permission case. One remaining concern is the blanket exclusion of disabled/always-on permissions from the target's reach. A stored grant can still be effective at runtime even when the acting manager does not hold it. The fact that saving a role adds these grants does not establish that every actor allowed to assign an existing role is also allowed to save roles. With different delegated permission combinations, that exception may therefore cross the intended authorization boundary. Before merging, please make this exception consistent with the effective grants and permissions required by each operation. Add controller-level regression tests for varied save/assign/delete permission combinations and existing legacy roles, checking both legitimate operations and rejection when the target's effective grants exceed the actor's authority. This is a static review finding; I have not reproduced this remaining case at runtime. SokilAI |
… rule 044ecd3 left always-on permissions out of a target role's reach, because a role save stores them on every role. Only a manager holding save_role can save, though: one allowed to delete a role, or holding a narrower set, could act on a role whose always-on grants it lacks, and those grants authorize at runtime like any other. reachOf() now leaves out only the obsolete keys. A non-administrator stores on a saved role only grants it holds, always-on ones included. The decision EditOrNewUser makes before it changes a user's role, save_role plus the reach check, moves to RoleGuard::mayAssignRole(). The tests run the real role save and delete and mayAssignRole() for save_role / delete_role combinations, fresh and upgraded installs, custom permissions with and without a permissions row, and an actor whose role lacks an always-on grant the target holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, confirmed and fixed in fec047f. Confirmed: "a role save stores always-on keys anyway" only covers a manager holding Fix: no blanket exception remains.
Every default role is seeded with all five always-on permissions, so this changes nothing on a standard site. A manager whose role predates a new always-on permission picks it up by saving its own role, which is always within its reach. Controller-level regression tests (
Full suite in |
…3.6.x Always-on grants count in role reach, a role save stores only grants the actor holds, and assignment goes through RoleGuard::mayAssignRole() (second review of evolution-cms#2578). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, fec047f addresses the custom-permission and always-on grant issues. I would still hold the merge for the separate role-ID consistency issue reported on #2579, which remains in this assignment path: authorization checks an integer-cast role ID, while persistence receives the original value. Please validate the role ID once and use the same canonical, checked value for both authorization and persistence. Add a controller-level regression test that rejects invalid or ambiguous input and asserts that the persisted role ID is exactly the one whose permissions were checked. This is a static finding, not a runtime reproduction. Any consequence involving database coercion remains unverified. SokilAI |
EditOrNewUser checked the reach of (int) $_POST['role'] but passed the
posted string to setRole(). MySQL and MariaDB in the sql_mode the
installer configures coerce it on write ('0.5' becomes 1), so the stored
role could be one the check never saw. RoleGuard::postedRoleId() now
accepts only "0" or the decimal id of an existing role, validated once
before anything is saved, and that same int goes to the TV lookup, the
reach check and setRole().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, confirmed at runtime and fixed in 75dc25b. Confirmed: Fix:
Controller-level regression tests (
Full suite in |
Raised in the review of #2577; the reach checks come from 3.5.x (46ece99), so 3.5 needs the fix too.
Problem
Saving, deleting or assigning a role compared all of the target role's stored grants with the permission keys the actor holds. The actor side lists only defined permissions, so a target grant that no permission defines could never match:
role_actionok. The seeders no longer grant it (del(seeders): drop the dead action_ok permission and its dangling role grants #2570), but no update removes the existing rows. A role manager holding every real permission of such a role was therefore refused editing, deleting or assigning it.action_ok, so a target saved recently carried it while the actor's role, saved before, did not. That also refused the actor.Fix
Both sides of the comparison follow the grants that authorize at runtime. The session holds every stored grant of a role, defined by a
permissionsrow or not, always-on ones included.RoleGuard::heldBy($hasPermission): the actor's ownhasPermission(), asked for every defined key and every key any role is granted.RoleGuard::reachOf($roleId): the target's stored grants, less only the obsolete keys the earlier seeders left (RoleGuard::OBSOLETE:role_actionok,action_ok, which nothing checks).save_roleto save or assign (RoleGuard::mayAssignRole(), used byEditOrNewUser),delete_roleto delete. The administrator role stays out of reach for non-administrators.EditOrNewUserchecked(int) $_POST['role']but stored the posted string, which MySQL/MariaDB in the installer's non-strict sql_mode coerce into another role id.RoleGuard::postedRoleId()now accepts only"0"or the decimal id of an existing role, validated once before anything is saved; the same int is checked and stored. New lexicon keyuser_role_invalid.Tests
RoleReachLegacyGrantsTestruns the real role save and delete andmayAssignRole(), every case checking all three operations:save_role/delete_rolecombinations: each operation goes through exactly when the actor holds its permission;settings, a defined custom permission, a custom grant with no row): refused under every combination;UserRoleAssignmentTestruns the realEditOrNewUser::process()andUserSetRoleon SQLite and, withEVO_TEST_MYSQL_HOST, MySQL/MariaDB non-strict: the stored role is the role checked; invalid or ambiguous ids are refused before anything is written.Full suite in
evo-ci-php:8.4: 2535 passed, 12 skipped; the new tests also on MySQL 8.4 and MariaDB 11.4.🤖 Generated with Claude Code