Roles y permisos
Cómo se crean los roles y se les dan permisos en la pantalla Roles, cómo llega un rol a una persona y qué puede hacer siempre el rol de administrador.
Lo que cada persona puede hacer dentro de la aplicación lo deciden sus roles, y lo que puede hacer un rol lo deciden los permisos marcados en él. Esta guía explica la pantalla Roles del panel de administración, cómo llega un rol a una persona y qué roles existen desde el inicio.
Los roles que existen desde el inicio
| Rol | Quién lo tiene | Qué puede hacer |
|---|---|---|
| Administrador | Los dueños del producto, desde que el producto se instala. | Todo. No se puede editar ni eliminar. |
| Client | Cada cuenta que se registra. | Nada más allá de lo que puede hacer cualquier persona con sesión iniciada, hasta que le marques permisos. |
Tu producto también puede traer un rol inicial pensado para que tú lo repartas. Ese rol no se puede eliminar.
La pantalla Roles
Abre Usuarios → Roles en el panel de administración. La lista muestra cada rol por su nombre. Crear pide un nombre: minúsculas y guiones, como "support-team", que la pantalla muestra como "Support Team". Ya que el rol existe, Editar muestra sus permisos:
- Una cuadrícula con una fila por cada tipo de registro —Usuario, Rol, Anuncio, Registro de comunicación y los demás— y las columnas Crear, Ver, Actualizar y Eliminar. Marca una casilla para dar esa acción sobre ese tipo de registro. Haz clic en el encabezado de una columna para marcar la columna completa, o en el nombre de una fila para marcar la fila completa.
- Debajo, la lista Permisos, con los interruptores que no caben en la cuadrícula, como Contestar ticket o Explorar carpetas de otros usuarios, y los que agregue tu producto.
- Un módulo que está apagado en el producto no aparece ni en la cuadrícula ni en la lista, así que a un rol no se le puede dar algo que no existe. Las filas de planes, complementos y medidores son la excepción: se quedan mientras esos módulos están apagados, y marcarlas no cambia nada hasta que el módulo se enciende.
La acción Clonar de un rol lo copia con todos sus permisos bajo el mismo nombre seguido de "-copia" (luego "-copia-2", y así) y abre la copia para editarla.
Algunas casillas no tienen efecto, porque hay registros que nadie puede crear ni cambiar: los permisos sólo se pueden consultar, y un registro de comunicación sólo se puede consultar o eliminar.
Qué hace cada columna
| Columna | Qué deja hacer al rol con ese tipo de registro |
|---|---|
| Crear | Agregar nuevos. |
| Ver | Abrir la lista y la página de cada registro. |
| Actualizar | Editarlos. |
| Eliminar | Eliminarlos, ver los eliminados y restaurarlos. |
El panel de administración sólo se abre para quien tiene el rol Administrador, salvo sus pantallas de soporte: Contestar ticket abre la bandeja de soporte y los chats del equipo, y Usar asistente el asistente, para cualquier rol. Quien llega a ellas sin el rol Administrador ve sólo el grupo Soporte del menú del panel. Dentro del panel, algunas pantallas piden más que un permiso: el historial de actividad sólo lo pueden abrir los dueños del producto, y los registros de Evento de seguimiento sólo el rol Administrador.
Darle un rol a una persona
Los roles se dan a las cuentas en Usuarios → Usuarios: el campo Rol del formulario de usuario acepta uno o varios roles, y sólo lo ve un editor que tiene el rol Administrador. Al guardar, la persona recibe una Notificación de actualización de rol que nombra los roles agregados y quitados.
La cuenta de uno de los dueños del producto no se puede eliminar desde esa pantalla.
Los dueños del producto
Los dueños del producto son una lista corta de cuentas escrita dentro del propio producto, no un rol. Reciben el rol Administrador cuando se instala el producto, que es lo que les da su acceso; estar en la lista sólo agrega algunos extras: la Dev Zone, la pantalla Diseño, el historial de actividad, actuar como otro de los dueños y la protección contra ser eliminados. Quitar una cuenta de esa lista quita los extras pero deja el rol Administrador en su lugar. Ve Sudo y superadministradores.
Para desarrolladores
Las piezas
Los roles y permisos están construidos sobre el paquete laravel-permission de Spatie. 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. El panel parte un permiso por su espacio: la primera palabra elige la columna de la cuadrícula, la segunda la fila, y cada casilla muestra el nombre exacto del permiso al pasar el cursor. Un permiso cuya primera palabra no es una de esas cuatro acciones, como answer ticket o browse media_folder, va a la lista Permisos. 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_log |
retrieve activity_log |
tracking_event_type |
los cuatro |
tracking_session |
retrieve tracking_session |
tracking_event |
los cuatro |
media |
los cuatro, aunque la política decide ver, editar y eliminar un archivo según su carpeta, ve abajo |
media_folder |
los cuatro, más browse media_folder, que deja al rol abrir y manejar las carpetas y archivos de todas las cuentas |
ticket |
answer ticket: abrir la bandeja de soporte y contestar tickets y chats |
assistant |
use assistant: hablar con el asistente de soporte |
billing_plan, billing_addon, billing_meter |
los cuatro, mientras Billing Core está cargado |
El primer bloque y las filas de cobro salen de los recursos del panel de administración; los permisos de medios, carpetas, tickets y asistente salen de la clave permissions de abajo. Esos once también los crea una migración, que se los da al rol de administrador, así que un deploy que corre php artisan migrate sin los seeders también los tiene.
Lo que crea el seeder
php artisan db:seed corre, en orden, PermissionSeeder, RoleSeeder, AdminSeeder (las cuentas sudo), UserSeeder (las cuentas default_users), y los tres seeders del catálogo de cobro AddOnSeeder, PlanSeeder y MeterSeeder. 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 recurso del panel de administración —las clases de app/Front/Resources/ y las que registra un paquete, como los planes, complementos y medidores de Billing Core— 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 |
Los once permisos de medios, carpetas, tickets y asistente | Permisos que ningún recurso declara, como 'nombre del permiso' => 'guard': create, retrieve, update y delete para media y para media_folder, browse media_folder, answer ticket y use assistant. Agrega los tuyos a la lista y conserva los que trae: con el borrado activo, un permiso que quites de ella se borra en suave en la siguiente siembra. 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' => [
'retrieve media' => 'web',
// ... el resto de los permisos que trae el kit
'use assistant' => 'web',
'export reports' => '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, pantallas de cobro incluidas, revisa el rol que nombra esta clave. |
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.
La pantalla Roles
La pantalla es /admin/roles; los roles se asignan en /admin/users. Un permiso queda fuera de la cuadrícula y de la lista cuando su recurso, la segunda palabra, tiene una política en app/Policies/ cuya configuración requerida está apagada. Las políticas de cobro viven en el paquete, no en app/Policies/, así que sus filas siempre se muestran. RolePolicy impide editar o eliminar el rol de administrador y eliminar el default_role; todo lo demás sigue los permisos de role. 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:
$requiredConfignombra una clave de configuración. Cuando es falsa,before()devuelvefalsey toda revisión falla, también para el rol de administrador.AnnouncementPolicyusafeatures.announcements; las políticas de seguimiento usanfeatures.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. |
MediaPolicy |
view, update y delete de un archivo permiten al dueño de su carpeta, a un miembro del equipo que maneja los medios del equipo, o browse media_folder; los permisos de media no cuentan para ellos. Un archivo fuera de una carpeta se rechaza. |
MediaFolderPolicy |
viewAny siempre es verdadero. view, create y update permiten al dueño de la carpeta o a un miembro del equipo que maneja los medios del equipo, y si no, necesitan browse media_folder. delete nunca permite una carpeta raíz, y si no, permite al dueño, al que maneja los medios del equipo o delete media_folder. Agrega browse(), que es browse media_folder. |
TicketPolicy |
Su recurso está encendido mientras los tickets o el chat en vivo lo están. viewAny necesita answer ticket; view permite al dueño del ticket o answer ticket; create siempre es verdadero. Agrega answer(). |
AssistantPolicy |
$requiredConfig = 'features.assistant.enabled'. Agrega use(), que necesita use assistant; view sólo permite la conversación propia de la persona, con use assistant. |
BillingPlanPolicy, BillingAddonPolicy, BillingMeterPolicy de Billing Core |
Viven en el paquete y extienden su propia Policy, que mapea los mismos cuatro permisos sin restore, forceDelete ni viewDeleted. Se apagan con features.plans_enabled (los planes también necesitan services.stripe.secret) o features.addons_enabled. view de un complemento o de un medidor es falso, y un plan, complemento o medidor que ya no se puede eliminar rechaza delete. |
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 y la pantalla Diseño con sus entradas de la barra lateral, el registro de actividad, la protección contra eliminar un usuario sudo, actuar como otro usuario sudo (un administrador puede actuar como cualquier otra persona, pero sólo una cuenta sudo puede actuar como una cuenta sudo), y mover archivos entre discos en la biblioteca de medios cuando MULTIMEDIA_DISK_CHANGE es sudo. Quitar un correo de la lista quita esos extras pero deja el rol en su lugar.
