MoonShine Version
4.6.0
Laravel Version
12.49.0
PHP Version
8.4.18
Database Driver & Version
PostgreSQL 16.11
Description
vendor/sweet1s/moonshine-roles-permissions/src/Http/Controllers/MoonShineRBACController.php
23: $this->superAdminRoleId = config('moonshine.auth.model')::SUPER_ADMIN_ROLE_ID;
— в конструкторе кэшируется хардкод-константа (у нас App\Models\User::SUPER_ADMIN_ROLE_ID = 1).**
This is in the sweet1s/moonshine-roles-permissions RBAC package (v4.0.0), specifically Sweet1s\MoonshineRBAC\Http\Controllers\MoonShineRBACController.
Both attachPermissionsToRole() (route moonshine-rbac/role/{id}/permissions/sync) and attachRolesToUser() (route moonshine-rbac/user/{id}/roles/sync) authorize the write by comparing the role id of the acting user's roles against a hardcoded constant, config('moonshine.auth.model')::SUPER_ADMIN_ROLE_ID (defaults to 1 on the model). This implicitly assumes the "Super Admin" role always ends up with database id 1.
That assumption breaks whenever a seeder creates any other role before "Super Admin". In our project the seeder creates roles in this order: Content Manager, Category Moderator, Contact Manager, Super Admin — so Super Admin gets id 4, and Content Manager gets id 1.
Observed consequences:
- The real Super Admin gets rejected ("You cannot edit permissions of Super Admin role") when trying to save permissions for any role through the standard role form — even though they genuinely have the Super Admin role.
- The role that happens to have id 1 (in our case Content Manager, not Super Admin at all) passes the same id-based check and can save permissions for any role.
- Clearing a role's permissions (submitting the form with no permissions key) is executed with no authorization check at all — the id check only gates the case where the "permissions" array is non-empty.
- The mirror bug exists in attachRolesToUser(): a user whose role happens to have id 1 is "protected" from having their roles changed (treated as a pseudo-super-admin), while a user who genuinely has the Super Admin role can have their roles changed by anyone with role_priority delegation.
None of this is about the seeder order being "wrong" — role ids are assigned by insertion order/autoincrement, and nothing in the package (or in Laravel) guarantees the "Super Admin" role gets id 1. The authorization should be based on the role's name/flag, not its numeric id.
Steps To Reproduce
- Seed roles so that some role other than "Super Admin" is created first (gets id 1), and create "Super Admin" later (any other id).
- Log in as a user who genuinely has the Super Admin role.
- Open a role's edit form, check/uncheck a permission checkbox, submit — request goes to POST moonshine-rbac/role/{roleId}/permissions/sync.
- Observe: 302 redirect back, but the role's permissions in role_has_permissions are unchanged; log contains [MoonShineRBACController] attachPermissionsToRole: You cannot edit permissions of Super Admin role, even though the acting user is the real Super Admin.
- As a control: log in as a user whose role has id 1 (not actually Super Admin) and repeat step 3 — the save succeeds.
- Repeat with moonshine-rbac/user/{id}/roles/sync: assigning roles to a user whose role has id 1 is silently blocked ("protected"), while a user with the real Super Admin role can have their roles reassigned by a lower-privileged user via role_priority delegation.
Expected behavior: authorization for both endpoints should check the role by name (or a dedicated is_super_admin flag), not by comparing to a hardcoded SUPER_ADMIN_ROLE_ID constant that only happens to be correct when "Super Admin" is the very first role ever seeded.
MoonShine Version
4.6.0
Laravel Version
12.49.0
PHP Version
8.4.18
Database Driver & Version
PostgreSQL 16.11
Description
vendor/sweet1s/moonshine-roles-permissions/src/Http/Controllers/MoonShineRBACController.php
23: $this->superAdminRoleId = config('moonshine.auth.model')::SUPER_ADMIN_ROLE_ID;
— в конструкторе кэшируется хардкод-константа (у нас App\Models\User::SUPER_ADMIN_ROLE_ID = 1).**
This is in the sweet1s/moonshine-roles-permissions RBAC package (v4.0.0), specifically Sweet1s\MoonshineRBAC\Http\Controllers\MoonShineRBACController.
Both attachPermissionsToRole() (route moonshine-rbac/role/{id}/permissions/sync) and attachRolesToUser() (route moonshine-rbac/user/{id}/roles/sync) authorize the write by comparing the role id of the acting user's roles against a hardcoded constant, config('moonshine.auth.model')::SUPER_ADMIN_ROLE_ID (defaults to 1 on the model). This implicitly assumes the "Super Admin" role always ends up with database id 1.
That assumption breaks whenever a seeder creates any other role before "Super Admin". In our project the seeder creates roles in this order: Content Manager, Category Moderator, Contact Manager, Super Admin — so Super Admin gets id 4, and Content Manager gets id 1.
Observed consequences:
None of this is about the seeder order being "wrong" — role ids are assigned by insertion order/autoincrement, and nothing in the package (or in Laravel) guarantees the "Super Admin" role gets id 1. The authorization should be based on the role's name/flag, not its numeric id.
Steps To Reproduce
Expected behavior: authorization for both endpoints should check the role by name (or a dedicated is_super_admin flag), not by comparing to a hardcoded SUPER_ADMIN_ROLE_ID constant that only happens to be correct when "Super Admin" is the very first role ever seeded.