Sistema avanzado de autorización de recursos en Laravel. Parte 2. Puertas de enlace, políticas

Introducción


Hola querido Khabrovchane.
Continúo mi serie de artículos sobre autorización avanzada de recursos en Laravel. Para comprender mejor lo que se discutirá en este artículo, debe leer la primera parte .


Para empezar, repetiré brevemente el enunciado del problema: hay una gran cantidad de modelos. Es necesario diseñar un sistema flexible y fácilmente extensible para autorizar las acciones del usuario en función de su función.
En esta parte, hablaremos sobre la configuración del enlace Policy <=> Gateway. Y también se propone una de las opciones para escribir derechos de usuario en la base de datos.


Y, por supuesto, aclararé de inmediato que el material está diseñado para programadores en ejercicio y que será difícil de entender para un desarrollador novato.



Parte 3. Políticas, pasarelas


Parte teórica


Se puede encontrar una gran cantidad de material en Internet sobre el tema: "¿Qué elegir Política o Puerta ?". Durante un cierto tiempo, también llegué con un error similar en el que debes elegir algo. Pero al final llegué a la conclusión: estos son dos eslabones de la misma cadena, que se usan de manera más efectiva en conjunto.
En Laravel, hay dos direcciones principales para determinar este paquete: manual y automático. No consideraré el manual, ya que el soporte de este tipo de paquete requerirá desproporcionadamente más esfuerzos del programador que configurar uno automático.


— , () . (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) (. ).


Eso es todo por ahora. Espero que este artículo haya sido útil. Y si alguien está interesado en un ajuste aún más preciso de los derechos y restricciones de los usuarios, a saber, el derecho del usuario a ver / cambiar un atributo específico del modelo , esto se
discute en la tercera parte .


All Articles