Files
construprogress/app/Livewire/RolePermissionManager.php
T
javier 13f36e8ec0 feat(authz): per-route permission gating for /admin (granular admin roles)
Finishes Phase 2: the /admin route group no longer requires 'manage all'
globally. Each route is gated by its specific permission so a non-super-admin
role can be granted partial admin access:
- /admin/users (+show) -> can:view users; create -> can:create users;
  edit -> can:edit users
- /admin/roles, roles/*, permissions -> can:manage roles
- Aligned the role screens' mount checks (RoleForm/RoleView/RolePermissionManager)
  from 'manage all' to 'manage roles'.
- Nav 'Administrator' link now shows on can('view users').
Admins keep full access via Gate::before (manage all). Closure routes
(users/roles lists) are now protected at the route level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:15:58 +02:00

111 lines
3.3 KiB
PHP

<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\Attributes\Layout;
use Illuminate\Support\Facades\Auth;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\PermissionRegistrar;
#[Layout('layouts.app')]
class RolePermissionManager extends Component
{
public string $newRole = '';
public string $newPermission = '';
/** Roles that must not be deleted or stripped of core powers. */
private const PROTECTED_ROLES = ['Admin'];
private const CORE_PERMISSION = 'manage all';
public function mount(): void
{
abort_unless(Auth::user()?->can('manage roles'), 403);
}
private function flushCache(): void
{
app(PermissionRegistrar::class)->forgetCachedPermissions();
}
public function togglePermission(int $roleId, string $permissionName): void
{
$role = Role::findOrFail($roleId);
if ($role->hasPermissionTo($permissionName)) {
// Admin must always keep the core permission
if ($role->name === 'Admin' && $permissionName === self::CORE_PERMISSION) {
$this->dispatch('notify', "El rol Admin no puede perder '" . self::CORE_PERMISSION . "'.");
return;
}
$role->revokePermissionTo($permissionName);
} else {
$role->givePermissionTo($permissionName);
}
$this->flushCache();
$this->dispatch('notify', 'Permisos actualizados');
}
public function addRole(): void
{
$this->validate([
'newRole' => 'required|string|max:50|unique:roles,name',
], [], ['newRole' => 'nombre de rol']);
Role::create(['name' => trim($this->newRole)]);
$this->newRole = '';
$this->flushCache();
$this->dispatch('notify', 'Rol creado');
}
public function deleteRole(int $roleId): void
{
$role = Role::findOrFail($roleId);
if (in_array($role->name, self::PROTECTED_ROLES, true)) {
$this->dispatch('notify', "El rol '{$role->name}' está protegido y no se puede borrar.");
return;
}
$role->delete();
$this->flushCache();
$this->dispatch('notify', 'Rol eliminado');
}
public function addPermission(): void
{
$this->validate([
'newPermission' => 'required|string|max:50|unique:permissions,name',
], [], ['newPermission' => 'nombre de permiso']);
Permission::create(['name' => trim($this->newPermission)]);
$this->newPermission = '';
$this->flushCache();
$this->dispatch('notify', 'Permiso creado');
}
public function deletePermission(int $permissionId): void
{
$permission = Permission::findOrFail($permissionId);
if ($permission->name === self::CORE_PERMISSION) {
$this->dispatch('notify', "El permiso '" . self::CORE_PERMISSION . "' está protegido y no se puede borrar.");
return;
}
$permission->delete();
$this->flushCache();
$this->dispatch('notify', 'Permiso eliminado');
}
public function render()
{
return view('livewire.role-permission-manager', [
'roles' => Role::with('permissions')->orderBy('name')->get(),
'permissions' => Permission::orderBy('name')->get(),
]);
}
}