Skip to content

sweet1s/moonshine-roles-permissions: real Super Admin cannot save role permissions / user roles when RoleSeeder assigns id=1 to a non-Super-Admin role #2053

Description

@andrei-kiselev

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. Seed roles so that some role other than "Super Admin" is created first (gets id 1), and create "Super Admin" later (any other id).
  2. Log in as a user who genuinely has the Super Admin role.
  3. Open a role's edit form, check/uncheck a permission checkbox, submit — request goes to POST moonshine-rbac/role/{roleId}/permissions/sync.
  4. 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.
  5. As a control: log in as a user whose role has id 1 (not actually Super Admin) and repeat step 3 — the save succeeds.
  6. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions