Sistema avançado de autorização de recursos no Laravel. Parte 2. Gateways, Políticas

Introdução


Olá querido Khabrovchane.
Continuo minha série de artigos sobre autorização avançada de recursos no Laravel. Para entender melhor o que será discutido neste artigo, você precisa ler a primeira parte .


Para começar, repetirei brevemente a afirmação do problema: Há um grande número de modelos. É necessário projetar um sistema flexível e facilmente extensível para autorizar ações do usuário, dependendo de sua função.
Nesta parte, falaremos sobre a configuração do link Policy <=> Gateway. E também é proposta uma das opções para gravar direitos de usuário no banco de dados.


E, é claro, vou esclarecer imediatamente que o material foi projetado para a prática de programadores e será difícil para um desenvolvedor iniciante entender.



Parte 3. Políticas, Gateways


Parte teórica


Muito material na Internet pode ser encontrado no tópico - “O que escolher Policy ou Gate ?”. Por um certo tempo, também cheguei a um erro semelhante, que você precisa escolher. Mas, no final, cheguei à conclusão - esses são dois elos da mesma cadeia, que são mais efetivamente usados ​​em conjunto.
No Laravel, existem duas direções principais para determinar esse pacote - manual e automático. Não considerarei manual, pois o suporte a esse tipo de pacote exigirá esforços desproporcionalmente mais do programador do que a configuração automática.


— , () . (Gate) — . — , . , . , app. . app/Models.


Gate::guessPolicyNamesUsing('callback'). , boot() App\Providers\AuthServiceProvider( ).



Gate


(callback), guessPolicyNamesUsing() , . . : Policy . , , Laravel ( ). SuperAdmin , , — super-admin . — .
PHP 7.4 . 7.4 .


<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->defineSuperAdmin();
        $this->defineAutoDiscover();
    }

    private function defineAutoDiscover()
    {
        Gate::guessPolicyNamesUsing(function ($class) {
            return str_replace("\\Models\\", "\\Policies\\" , $class) . 'Policy';
        });
    }

    private function defineSuperAdmin()
    {
        Gate::before(function($user) {
            return $user->hasRole('super-admin') ? true : null;
        });
    }
}

Policy


, , . . :


    php artisan make:policy PostPolicy --model=Models/Post

. . , DRY,- , . , . , getModelClass() . , ( ). . (import export — )


<?php
protected function resourceAbilityMap()
{
    return [
        'index' => 'viewAny',
        'show' => 'view',
        'create' => 'create',
        'store' => 'create',
        'edit' => 'update',
        'update' => 'update',
        'destroy' => 'delete',
        'import' => 'import',
        'export' => 'export',
    ];
}

— , . , . . Spatie. laravel-permission. , — , , . , , . — , $user->can() $user->hasRole(), Spatie\Permission\Traits\HasRoles


(Models\User)

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use Notifiable;
    use HasRoles;

    /**     */
}

, (Admin User, ) — HasRoles, .
.


(ModelPolicy)

<?php

namespace App\Policies;

use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Database\Eloquent\Model;

abstract class ModelPolicy
{
    use HandlesAuthorization;

    abstract protected function getModelClass(): string;

    public function viewAny(User $user)
    {
        return $user->can('view-any-' . $this->getModelClass());
    }

    public function view(User $user, Post $model)
    {
        return $user->can('view-' . $this->getModelClass());
    }

    public function create(User $user)
    {
        return $user->can('create-' . $this->getModelClass());
    }

    public function update(User $user, Post $model)
    {
        return $user->can('update-' . $this->getModelClass());
    }

    public function delete(User $user, Post $model)
    {
        return $user->can('delete-' . $this->getModelClass());
    }
}

$user->can('ability') . . ''-' ', — ( ). (SoftDelete). , .
. Post getResourceName() .


(PostPolicy)

<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;

class PostPolicy extends ModelPolicy
{
    protected function getModelClass(): string
    {
        return Post::class;
    }

    public function import(User $user)
    {
        return $user->can('import-' . $this->getModelClass());
    }

    public function export(User $user, Post $model)
    {
        return $user->can('export-' . $this->getModelClass());
    }
}

, import() , . — . . , .


, , . .


4. (Seeding)


, . .
spatie/laravel-permission, . .
:


    composer require spatie/laravel-permission
    php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

(seeder)


    php artisan make:seeder PermissionsSeeder

run() database/seeds/DatabaseSeeder.php. PermissionsSeeder .


(DatabaseSeeder)

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
         $this->call(PermissionsSeeder::class);
    }
}

, — PermissionsSeeder. database/data php . :


    role => model => action

// (permissions_roles.php)

<?php

return [
    'admin' => [
        'App\\Models\\Post' => [
            'view',
            'view-any',
            'create',
            'update',
            'delete',
            'import',
            'export',
        ],
    ],
    'App\\Models\\Post' => [
        'post' => [
            'view',
            'view-any',
        ],
    ],
];

. , , — .


(PermissionsSeeder)


<?php

use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

class PermissionsSeeder extends Seeder
{
    private $data = [];

    public function run()
    {
        $this->loadData();
        $this->seedRoles();
    }

    public function loadData(): void
    {
        $this->data = require_once database_path("data/permissions_roles.php");
    }

    private function seedRoles(): void
    {
        Role::create(['name' => 'super-admin', 'guard_name' => 'api']);

        foreach ($this->data as $roleName => $perms) {
            $role = Role::create(['name' => $roleName, 'guard_name' => 'api']);
            $this->seedRolePermissions($role, $perms);
        }
    }

    private function seedRolePermissions(Role $role, array $modelPermissions): void
    {
        foreach ($modelPermissions as $model => $perms) {
            $buildedPerms = collect($perms)
                ->crossJoin($model)
                ->map(function ($item) {
                $perm = implode('-', $item); //view-post
                Permission::findOrCreate($perm, 'api');

                return $perm;
            })->toArray();

            $role->givePermissionTo($buildedPerms);
        }
    }
}

, - . , , . , , .
, :


  • run() .
  • loadData() database/data $data
  • seedRoles() . , $data, . 'super-admin' , , .. ( AuthServiceProvider).
  • seedRolePermissions() , , «» , (permission). . () — findOrCreate(), laravel-permissions. , , .

, . :


    php artisan migrate --seed

. ! , :


  1. (Model).
  2. (Controller) ModelController.
  3. (Policy) ModelPolicy.
  4. .

, (. 1) (. ).


É tudo por agora. Espero que este artigo tenha sido útil. E se alguém estiver interessado em ajustar ainda mais os direitos e restrições dos usuários, ou seja, o direito do usuário de assistir / alterar um atributo específico do modelo - isso é
discutido na terceira parte .


All Articles