Skip to content

Implementação de Permissões no Laravel

Introdução

O Laravel fornece políticas para definir a lógica de autorização em sua aplicação. Essas políticas estabelecem as regras que determinam quais usuários estão autorizados a realizar ações específicas nos modelos de sua aplicação. Neste guia, discutiremos as etapas necessárias para implementar permissões detalhadas em uma aplicação Laravel usando um esquema de banco de dados, um trait e uma política.

Pré-requisitos

Antes de continuar, verifique se você tem uma aplicação Laravel configurada e funcionando. Você também deve estar familiarizado com migrações, modelos e políticas do Laravel.

Esquema do Banco de Dados

Para implementar permissões em uma aplicação Laravel, crie um esquema de banco de dados que inclua tabelas para funções, permissões e seus relacionamentos. O exemplo a seguir mostra como criar essas tabelas usando migrações do Laravel:

php
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');
    }
}

Neste exemplo, criamos tabelas para funções, permissões e seus relacionamentos. A tabela roles inclui um ID, um nome, uma descrição e timestamps. A tabela permissions consiste em um ID, um nome, uma descrição e timestamps. A tabela role_permissions contém um ID, um role_id, um permission_id e timestamps. As colunas role_id e permission_id são chaves estrangeiras que fazem referência às colunas id nas tabelas roles e permissions, respectivamente.

Esquemas de Modelos

Para fornecer dicas de tipo e melhorar a legibilidade do código ao interagir com os modelos Role, Permission e User, criamos classes <Model>Schema correspondentes. Essas classes definem constantes que representam os nomes das colunas nas tabelas de banco de dados correspondentes.

Por exemplo, aqui está a classe PermissionSchema:

php
<?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';
}

No exemplo acima, estamos definindo constantes para cada uma das colunas na tabela de permissões. Então, podemos usar essas constantes para fornecer dicas de tipo e melhorar a legibilidade do código ao interagir com o modelo Permission e seus dados associados.

Posteriormente, usaremos esses esquemas para definir o nome das colunas ao interagir com os modelos. Por exemplo, no trait HasPermissions que criaremos mais adiante, usamos a constante PermissionSchema::name para fazer referência à coluna name na tabela de permissões:

php
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);
    });
}

Ao usar PermissionSchema::name em vez da string crua "name", estamos tornando nosso código mais legível e menos propenso a erros. Além disso, se alguma vez mudássemos o nome da coluna name na tabela de permissões, poderíamos simplesmente atualizar a constante PermissionSchema::name para refletir o novo nome, e todas as referências a essa constante em nosso código também seriam atualizadas automaticamente.

Ao usar esquemas de modelos dessa maneira, podemos facilitar a manutenção e o trabalho com nosso código ao longo do tempo.

Modelos

Para implementar permissões detalhadas em uma aplicação Laravel, crie três modelos: Role, Permission e User. Esses modelos interagem com as tabelas de banco de dados que armazenam informações sobre funções, permissões e relacionamentos usuário/função.

Exemplo do modelo Role:

php
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();    
    }
}

No exemplo do modelo Role acima, usamos o relacionamento BelongsToMany para definir o relacionamento entre os modelos Role e User, bem como o relacionamento entre os modelos Role e Permission.

Exemplo do modelo Permission:

php
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();
    }
}

No exemplo do modelo Permission acima, usamos o relacionamento BelongsToMany para definir o relacionamento entre os modelos Permission e Role.

Finalmente, exemplo do modelo User:

php
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();
    }
}

No exemplo do modelo User acima, usamos o relacionamento BelongsToMany para definir o relacionamento entre os modelos User e Role. Além disso, usamos vários traits e propriedades para lidar com autenticação, notificações e permissões.

Trait de Verificação de Permissões

Para simplificar a verificação de permissões em uma política, crie um trait que adicione um método hasPermission() à classe que o usa. O exemplo a seguir mostra como criar esse trait:

php
<?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 o processo de verificar se um usuário tem uma permissão específica.

O método hasPermission recebe um parâmetro de string, $permission, e retorna um valor booleano. Ele verifica se o usuário atual tem a permissão especificada examinando todas as funções associadas ao usuário.

Se nenhuma das funções possuir a $permission necessária, a função some retorna false, indicando que o usuário não tem a permissão necessária.

Ao incorporar esse trait em seu modelo User, você pode verificar facilmente se um usuário tem uma permissão específica.

Implementando Permissões Detalhadas com Políticas

Para aplicar permissões detalhadas usando o modelo User, crie uma classe de política para cada modelo ao qual você deseja controlar o acesso. Neste exemplo, criaremos uma PostPolicy para gerenciar o acesso ao modelo Post.

Primeiro, crie a classe PostPolicy:

php
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;  
    }
}

Observe que usamos o trait hasPermission adicionado ao modelo User.

Em seguida, registre a política no AuthServiceProvider. Aqui está um exemplo do registro da PostPolicy:

php
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();
    }
}

Neste exemplo, registramos a PostPolicy para o modelo Post. Isso diz ao Laravel para usar a PostPolicy para controlar o acesso às instâncias do modelo Post.

Agora, você pode usar essa política em seus controladores para autorizar ações. Aqui está um exemplo de uso da PostPolicy em um controlador:

php
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...
    }
}

Neste exemplo, usamos o método authorize() para verificar se o usuário autenticado tem permissão para atualizar a postagem fornecida, usando o método update da PostPolicy. Se o usuário estiver autorizado, permitimos que ele edite ou atualize a postagem. Caso contrário, retornamos um erro HTTP 403 Forbidden.

Ao usar políticas para controlar o acesso aos seus modelos, você garante que apenas usuários autorizados possam executar ações confidenciais em sua aplicação.