RBAC Aditivo & Capacidades de Negocio
El modelo de autorización de Mañuti implementa un Control de Acceso Basado en Roles (RBAC) evolucionado hacia la arquitectura de Capacidades Atómicas (Capability-Based Security). A diferencia de esquemas rudimentarios que enlazan usuarios directamente a métodos de controlador o endpoints HTTP, Mañuti desacopla las intenciones del usuario de la infraestructura de enrutamiento.
1. Topología Relacional de Control de Acceso
La persistencia del subsistema de seguridad se fundamenta en cuatro tablas relacionales normalizadas en MariaDB/MySQL bajo Tercera Forma Normal (3NF), garantizando integridad referencial estricta y eliminación en cascada de privilegios huérfanos.
flowchart LR
U["usuarios"]
RU["role_user"]
R["roles"]
PR["permission_role"]
P["permissions"]
U <-->|"1"| RU
RU <-->|"N"| R
R <--> PR
PR <--> P
Diccionario de Datos del Modelo RBAC
Entidad roles
id(BIGINT UNSIGNED, PK, Auto-increment): Identificador único del perfil.name(VARCHAR(255), Unique, Not Null): Identificador legible (e.g.,Super Admin,Auditor de Seguridad,Oficial de Cumplimiento).description(VARCHAR(255), Nullable): Alcance funcional del rol.created_at/updated_at/deleted_at: Marcas de auditoría temporal con soporte nativo para Soft Deletes.
Entidad permissions
Representa una capacidad indivisible del dominio de negocio.
id(BIGINT UNSIGNED, PK, Auto-increment): Identificador de la capacidad.slug(VARCHAR(255), Unique, Not Null): Clave unívoca indexada para evaluación programática (e.g.,users.create,legal.publish,audit.search). Admite la convención estricta con puntosregex:/^[a-zA-Z0-9\.\-_]+$/.name(VARCHAR(255), Not Null): Etiqueta descriptiva para renderizado en matrices administrativas.description(VARCHAR(255), Nullable): Detalle técnico de la operación permitida.module(VARCHAR(255), Index, Not Null): Agrupador taxonómico para interfaces de usuario (e.g.,Usuarios,Auditoría,Legal y Compliance).auditable(TINYINT(1), Default0): Bandera booleana que instruye al Middleware de Auditoría si la ejecución de esta capacidad debe persistirse en MongoDB.
Tablas Pivote Intermedias
role_user: Materializa la relación Muchos a Muchos entreusuariosyroles. Posee restricción compuestaUNIQUE('usuario_id', 'role_id').permission_role: Asigna capacidades atómicas a los perfiles funcionales. Posee restricción compuestaUNIQUE('role_id', 'permission_id').
2. Paradigma de Permisos Aditivos
Mañuti descarta la asignación restrictiva de un único rol por usuario. Mediante la relación belongsToMany en App\Models\Usuario, los privilegios operan bajo el principio de Unión de Conjuntos:
$$\text{Capacidades Totales} = \bigcup_{i=1}^{n} \text{Permisos}(\text{Rol}_i)$$
Si el Usuario $U$ posee los roles $\text{Rol}_A$ (sin permiso de exportación) y $\text{Rol}_B$ (con permiso audit.export), el sistema concede el acceso de forma aditiva. Esto erradica la proliferación de roles híbridos artificiales en la organización.
3. Integración con el Kernel de Laravel: Gates & Traversal
La intercepción de seguridad se centraliza en el AppServiceProvider utilizando el gancho global Gate::before, eliminando consultas redundantes tipo $N+1$ mediante eager loading.
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Gate::before(function ($user, string $ability) {
if (method_exists($user, 'hasPermission')) {
return $user->hasPermission($ability) ?: null;
}
});
}
}
En el modelo App\Models\Usuario, la resolución de capacidades recorre la jerarquía en memoria:
public function hasPermission(string $permissionSlug): bool
{
return $this->roles()
->with('permissions')
->get()
->pluck('permissions')
->flatten()
->contains('slug', $permissionSlug);
}
4. Implementación en Capas (Defensa en Profundidad)
Capa de Enrutamiento (routes/web.php y routes/api.php)
Las peticiones HTTP no autenticadas o no autorizadas son abortadas antes de instanciar controladores, invocando el alias can:
Route::middleware(['auth', 'can:users.view'])->group(function () {
Route::get('/usuarios', [UserController::class, 'index'])->name('admin.users.index');
});
Capa de Presentación (Blade UI Adaptativa)
La interfaz oculta condicionalmente nodos del DOM. Los elementos no concedidos no se imprimen en el HTML, evitando vectores de inspección:
@can('users.create')
<button class="btn btn-primary" onclick="openCreateModal()">Nuevo Usuario</button>
@endcan
@canany(['roles.view', 'permissions.view', 'users.view'])
{{-- Renderizado del nodo agrupador Sistema en el Sidebar --}}
<x-backend.sidebar-accordion title="Sistema" icon="ki-setting-2" />
@endcanany