13f36e8ec0
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>
111 lines
3.3 KiB
PHP
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(),
|
|
]);
|
|
}
|
|
}
|