Cargando…

Esto está tardando más de lo esperado.

Volver al centro de ayuda

Roles y permisos

Cómo se nombran, siembran y conceden los permisos, y cómo proteger tus pantallas.

El acceso dentro de la aplicación lo deciden permisos agrupados en roles, sobre el paquete laravel-permission de Spatie. Esta guía explica la convención de nombres, lo que el seeder crea por sí solo, las claves que puedes cambiar en config/app.php, cómo se administran los roles desde el panel y cómo proteger el código que escribas.

Las piezas

Los modelos son App\Models\Role y App\Models\Permission, sustituidos a través del bloque models de config/permission.php. Ambos extienden los modelos de Spatie, ambos hacen borrado suave, y ambos registran sus cambios en el registro de actividad. Las tablas son las del paquete por omisión (roles, permissions, model_has_roles, model_has_permissions, role_has_permissions), configuradas en config/permission.php. Todo permiso pertenece al guard web. El name de un rol se guarda como slug — Support Team se vuelve support-team — y el panel lo muestra como título, Support Team, pasado por el traductor.

Las revisiones de permisos se guardan en caché 24 horas. El paquete vacía la caché cuando un rol o permiso se guarda por sus propios métodos, y el panel la vacía otra vez después de editar o clonar un rol.

La convención de nombres

Cada permiso son dos palabras: {acción} {recurso}. La acción es una de create, retrieve, update, delete; el recurso es el singular de la tabla del modelo. Los permisos que trae una instalación nueva:

Recurso Permisos
user create user, retrieve user, update user, delete user
role create role, retrieve role, update role, delete role
permission los cuatro, aunque la política sólo permite leer
announcement los cuatro
communication_log retrieve communication_log, delete communication_log y el par crear/actualizar, que la política nunca concede
activity retrieve activity
tracking_event_type los cuatro
tracking_session retrieve tracking_session
tracking_event los cuatro

El panel parte un permiso por su espacio: la primera palabra elige la columna de la matriz de la pantalla de roles, la segunda la fila.

Lo que crea el seeder

php artisan db:seed corre, en orden, PermissionSeeder, RoleSeeder, AdminSeeder (las cuentas sudo), UserSeeder (las cuentas default_users) y AddOnSeeder (cobros). Puedes correr cada uno por separado:

php artisan db:seed --class=PermissionSeeder
php artisan db:seed --class=RoleSeeder

PermissionSeeder arma su lista desde dos fuentes, ambas configuradas en el bloque Permissions de config/app.php:

Clave Por omisión Qué hace
discover_front_permissions true Lee cada clase de recurso en app/Front/Resources/ y deriva sus permisos de las $actions que el recurso declara: create cuando la lista tiene create o store, retrieve cuando tiene show o index, update cuando tiene edit o update, delete cuando tiene destroy. Un recurso de sólo lectura con $actions = ['index', 'show'] recibe sólo retrieve.
permissions [] Permisos extra tuyos, como 'nombre del permiso' => 'guard'. Cualquier nombre sirve; la forma de dos palabras es una convención, no una regla.
allow_permisisons_deletion true Sí, con la falta de ortografía: esa es la clave real. Cuando es verdadera, un permiso en la base de datos que no está en ninguna de las dos fuentes se borra en suave en la siguiente siembra. Ponla en false si insertas permisos fuera del seeder y quieres que sobrevivan.

Los permisos se insertan o actualizan por nombre y guard, y un permiso borrado en suave que reaparece en las fuentes se restaura. El descubrimiento lee el mapa de clases de Composer, así que corre composer dump-autoload después de agregar un recurso y antes de sembrar.

// config/app.php
'permissions' => [
    'export reports' => 'web',
    'dashboard user' => 'web',
],

RoleSeeder luego se asegura de que existan tres roles:

Clave Por omisión Qué hace
admin_role 'admin' El rol superior. Se crea si falta y, en cada corrida, recibe todos los permisos de la tabla. El middleware de /admin revisa este rol por su nombre literal admin, así que cambiar sólo la clave deja el panel cerrado.
default_role null Cuando está definido, el rol se crea si falta y, sólo al crearse, recibe los permisos listados en default_role_permissions. Nada en el kit lo asigna a cuentas nuevas: existe para que tengas listo un rol inicial que repartir, y RolePolicy se niega a eliminarlo.
default_role_permissions [] Nombres de permisos concedidos a default_role la primera vez que se crea. Editar esta lista después no toca un rol que ya existe; concédelos desde el panel.
'client' Siempre se crea un rol client. El registro se lo asigna a cada cuenta nueva. No trae permisos salvo que le concedas alguno.

AdminSeeder asigna admin_role a cada correo de config('app.sudo'). Vuelve a correr los seeders cada vez que agregues un recurso o un permiso propio; son idempotentes.

Administrar roles desde el panel

/admin/roles lista los roles. Create pide un nombre y, ya que el rol existe, Edit muestra el selector de permisos:

  • Una matriz con una fila por recurso y las columnas Create, See, Update, Delete. Clic en el encabezado de una columna para alternar la columna entera, clic en el nombre de una fila para alternar la fila entera. Cada casilla muestra el nombre exacto del permiso al pasar el cursor.
  • Una lista Permissions debajo, con cada permiso que no sigue la forma de dos palabras, como los que agregaste en config/app.php.
  • Los recursos cuya política declara un $requiredConfig que está apagado quedan fuera de la matriz, para que a un rol no se le pueda conceder un módulo que no existe.

La acción Clone de un rol lo copia con todos sus permisos bajo el nombre {name}-copia (luego -copia-2, y así) y abre la copia para editarla.

Candados integrados en RolePolicy: el rol de administrador no se puede editar ni eliminar, y el default_role no se puede eliminar. Todo lo demás sigue los permisos de role.

Los roles se asignan a las cuentas en /admin/users: el campo Role del formulario de usuario es un select múltiple, visible sólo para editores que tienen a su vez el rol de administrador. Al guardar, la cuenta recibe una Role Update Notification que nombra los roles agregados y quitados. UserPolicy prohíbe eliminar una cuenta sudo.

Políticas

Cada recurso está protegido por una política en app/Policies/ que extiende App\Policies\BasePolicy. La clase base necesita una propiedad y mapea los métodos estándar de política a permisos:

class PlanPolicy extends BasePolicy
{
    protected string $name = 'plan';
    protected $requiredConfig = 'features.plans_enabled';
}
Método de la política Permiso revisado
viewAny, view retrieve {name}
create create {name}
update update {name}
delete, restore, forceDelete, viewDeleted delete {name}

Dos ganchos afinan eso:

  • $requiredConfig nombra una clave de configuración. Cuando es falsa, before() devuelve false y toda revisión falla, también para el rol de administrador. AnnouncementPolicy usa features.announcements; las políticas de seguimiento usan features.tracking_enabled.
  • extraValidation() se combina con AND en cada revisión. Sobrescríbelo para agregar una condición que aplique a todo el recurso.

Laravel resuelve la política por convención: App\Models\Plan queda protegido por App\Policies\PlanPolicy. Un modelo que vive en otro lado se registra a mano en AppServiceProvider::boot() con Gate::policy(Model::class, ModelPolicy::class), como el modelo del registro de actividad.

Las políticas incluidas y en qué se apartan de la tabla:

Política Diferencia
UserPolicy delete exige además que el objetivo no sea sudo.
RolePolicy update niega el rol de administrador; delete niega el rol de administrador y el rol por omisión.
PermissionPolicy viewAny y view siempre son verdaderos; toda escritura es falsa.
CommunicationLogPolicy create y update son falsos.
AnnouncementPolicy $requiredConfig = 'features.announcements'.
TrackingEventTypePolicy, TrackingSessionPolicy $requiredConfig = 'features.tracking_enabled'.
TrackingEventPolicy Misma bandera; viewAny, view y create exigen el rol de administrador en vez de un permiso; update y delete son falsos.
ActivityPolicy No extiende BasePolicy. viewAny exige el rol de administrador y un correo sudo; view el rol de administrador; toda escritura es falsa.

Proteger tu propio código

Todo lo que construyas fuera de Laravel Front usa los mismos permisos. Elige la capa que encaje:

Una pantalla Livewire. Autoriza en mount(); el fallo se vuelve un 403.

public function mount(?Plan $plan)
{
    $this->authorize($plan?->exists ? 'update' : 'create', $plan ?? Plan::class);
}

Una ruta. bootstrap/app.php registra los middleware de Spatie bajo los alias role, permission y role_or_permission, junto al can propio de Laravel:

Route::middleware('permission:export reports')->group(function () {
    Route::livewire('/reports', App\Reports::class)->name('reports');
});

Route::middleware('role_or_permission:admin|retrieve plan')->group(function () {
    // ...
});

Una vista. Oculta lo que la persona no puede hacer en vez de dejar que se tope con un 403.

@can('create plan')
    <x-button href="{{ route('admin.front.plans.create') }}">
        {{ __('New plan') }}
    </x-button>
@endcan

@can('update', $plan)
    <x-button href="{{ route('admin.front.plans.edit', $plan) }}">
        {{ __('Edit') }}
    </x-button>
@endcan

PHP plano. $user->can('delete plan'), Gate::allows('update', $plan), o $user->hasPermissionTo('export reports') para un permiso sin política detrás. Reserva $user->hasRole(config('app.admin_role')) para decisiones de presentación, como si dibujar el enlace al panel; la autorización pasa por permisos para que un rol que crees después pueda recibir la misma capacidad.

Dónde entra sudo

sudo no es un rol y no tiene permisos. Es un atributo calculado del usuario, verdadero cuando el correo está en config('app.sudo'). El seeder le da a esas cuentas el rol de administrador, que es lo que de verdad concede el acceso; el atributo en sí sólo se consulta donde el código lo nombra: la Dev Zone, el registro de actividad, la protección contra eliminar un usuario sudo y la entrada de la barra lateral hacia la Dev Zone. Quitar un correo de la lista quita esos extras pero deja el rol en su lugar. Mira /help/sudo-and-super-administrators.