Implementación de Permisos en Laravel
Introducción
Laravel proporciona políticas para definir la lógica de autorización en tu aplicación. Estas políticas establecen las reglas que determinan qué usuarios están autorizados para realizar acciones específicas en los modelos de tu aplicación. En esta guía, discutiremos los pasos necesarios para implementar permisos detallados en una aplicación de Laravel utilizando un esquema de base de datos, un trait y una política.
Prerrequisitos
Antes de continuar, asegúrate de tener una aplicación Laravel configurada y en funcionamiento. También debes estar familiarizado con las migraciones, modelos y políticas de Laravel.
Esquema de Base de Datos
Para implementar permisos en una aplicación Laravel, crea un esquema de base de datos que incluya tablas para roles, permisos y sus relaciones. El siguiente ejemplo muestra cómo crear estas tablas utilizando migraciones de Laravel:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateRolesTable extends Migration
{
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('roles');
}
}
class CreatePermissionsTable extends Migration
{
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('permissions');
}
}
class CreateRolePermissionsTable extends Migration
{
public function up()
{
Schema::create('role_permissions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('role_id');
$table->unsignedBigInteger('permission_id');
$table->timestamps();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('role_permissions');
}
}
En este ejemplo, creamos tablas para roles, permisos y sus relaciones. La tabla roles
incluye un ID, un nombre, una descripción y marcas de tiempo. La tabla permissions
consta de un ID, un nombre, una descripción y marcas de tiempo. La tabla role_permissions
contiene un ID, un role_id
, un permission_id
y marcas de tiempo. Las columnas role_id
y permission_id
son claves externas que hacen referencia a las columnas id
en las tablas roles
y permissions
, respectivamente.
Esquemas de Modelos
Para proporcionar sugerencias de tipo y mejorar la legibilidad del código al interactuar con los modelos Role
, Permission
y User
, creamos clases <Model>Schema
correspondientes. Estas clases definen constantes que representan los nombres de las columnas en las tablas de la base de datos correspondientes.
Por ejemplo, aquí está la clase PermissionSchema
:
<?php
namespace App\Schemas;
class PermissionSchema
{
const id = 'id';
const name = 'name';
const description = 'description';
const created_at = 'created_at';
const updated_at = 'updated_at';
const deleted_at = 'deleted_at';
}
En el ejemplo anterior, estamos definiendo constantes para cada una de las columnas en la tabla de permisos. Luego, podemos usar estas constantes para proporcionar sugerencias de tipo y mejorar la legibilidad del código al interactuar con el modelo Permission
y sus datos asociados.
Más adelante utilizaremos estos esquemas para definir el nombre de las columnas al interactuar con los modelos. Por ejemplo, en el trait HasPermissions
que crearemos más adelante, usamos la constante PermissionSchema::name
para hacer referencia a la columna name
en la tabla de permisos:
use App\Schemas\PermissionSchema;
// ...
public function hasPermission(string $permission): bool
{
return $this->roles->some(function ($role) use ($permission) {
return $role->permissions->contains(PermissionSchema::name, $permission);
});
}
Al usar PermissionSchema::name
en lugar de la cadena en bruto "name", estamos haciendo que nuestro código sea más legible y menos propenso a errores. Además, si alguna vez cambiáramos el nombre de la columna name
en la tabla de permisos, simplemente podríamos actualizar la constante PermissionSchema::name
para reflejar el nuevo nombre, y todas las referencias a esa constante en nuestro código se actualizarían automáticamente también.
Al utilizar esquemas de modelos de esta manera, podemos hacer que nuestro código sea más fácil de mantener y trabajar con el tiempo.
Modelos
Para implementar permisos detallados en una aplicación Laravel, crea tres modelos: Role
, Permission
y User
. Estos modelos interactúan con las tablas de la base de datos que almacenan información sobre roles, permisos y relaciones usuario/rol.
Ejemplo del modelo Role
:
namespace App\Models;
use App\Schemas\RolePermissionSchema;
use App\Schemas\UserRoleSchema;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Role extends Model
{
use HasFactory;
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, UserRoleSchema::table)->withTimestamps();
}
public function permissions(): BelongsToMany
{
return $this->belongsToMany(Permission::class, RolePermissionSchema::table)->withTimestamps();
}
}
En el ejemplo del modelo Role
anterior, utilizamos la relación BelongsToMany
para definir la relación entre los modelos Role
y User
, así como la relación entre los modelos Role
y Permission
.
Ejemplo del modelo Permission
:
namespace App\Models;
use App\Schemas\RolePermissionSchema;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Permission extends Model
{
use HasFactory;
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class, RolePermissionSchema::table)->withTimestamps();
}
}
En el ejemplo del modelo Permission
anterior, utilizamos la relación BelongsToMany
para definir la relación entre los modelos Permission
y Role
.
Finalmente, ejemplo del modelo User
:
namespace App\Models;
use App\Schemas\UserRoleSchema;
use App\Traits\HasPermissions;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, HasPermissions;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class, UserRoleSchema::table)->withTimestamps();
}
}
En el ejemplo del modelo User
anterior, utilizamos la relación BelongsToMany
para definir la relación entre los modelos User
y Role
. Además, utilizamos varios traits y propiedades para manejar la autenticación, las notificaciones y los permisos.
Trait de Verificación de Permisos
Para simplificar la verificación de permisos en una política, crea un trait que agregue un método hasPermission()
a la clase que lo utiliza. El siguiente ejemplo muestra cómo crear este trait:
<?php
namespace App\Traits;
use App\Schemas\PermissionSchema;
trait HasPermissions
{
public function hasPermission(string $permission): bool
{
return $this->roles->some(function ($role) use ($permission) {
return $role->permissions->contains(PermissionSchema::name, $permission);
});
}
}
Este método simplifica el proceso de verificar si un usuario tiene un permiso específico.
El método hasPermission
toma un parámetro de cadena, $permission
, y devuelve un valor booleano. Comprueba si el usuario actual tiene el permiso especificado examinando todos los roles asociados con el usuario.
Si ninguno de los roles posee el $permission
requerido, la función some
devuelve false
, lo que indica que el usuario no tiene el permiso necesario.
Al incorporar este trait en tu modelo User
, puedes verificar fácilmente si un usuario tiene un permiso específico.
Implementación de Permisos Detallados con Políticas
Para aplicar permisos detallados usando el modelo User
, crea una clase de política para cada modelo al que deseas controlar el acceso. En este ejemplo, crearemos una PostPolicy
para gestionar el acceso al modelo Post
.
Primero, crea la clase PostPolicy
:
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\HandlesAuthorization;
class PostPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return $user->hasPermission('viewAny-posts');
}
public function view(User $user, Post $post)
{
return $user->hasPermission('view-posts') && $post->user_id === $user->id;
}
public function create(User $user)
{
return $user->hasPermission('create-posts');
}
public function update(User $user, Post $post)
{
return $user->hasPermission('update-posts') && $post->user_id === $user->id;
}
public function delete(User $user, Post $post)
{
return $user->hasPermission('delete-posts') && $post->user_id === $user->id;
}
}
Ten en cuenta que utilizamos el trait hasPermission
agregado al modelo User
.
A continuación, registra la política en el AuthServiceProvider
. Aquí hay un ejemplo de registro de la PostPolicy
:
namespace App\Providers;
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Post::class => PostPolicy::class,
];
public function boot()
{
$this->registerPolicies();
}
}
En este ejemplo, registramos la PostPolicy
para el modelo Post
. Esto indica a Laravel que utilice la PostPolicy
para controlar el acceso a las instancias del modelo Post
.
Ahora, puedes usar esta política en tus controladores para autorizar acciones. Aquí hay un ejemplo de uso de la PostPolicy
en un controlador:
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function edit(Post $post)
{
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// Update the post...
}
}
En este ejemplo, utilizamos el método authorize()
para verificar si el usuario autenticado tiene permiso para actualizar la publicación dada, empleando el método update
de la PostPolicy
. Si el usuario está autorizado, le permitimos editar o actualizar la publicación. Si no, devolvemos un error HTTP 403 Forbidden.
Al utilizar políticas para controlar el acceso a tus modelos, te aseguras de que solo los usuarios autorizados puedan realizar acciones sensibles en tu aplicación.