Advanced resource authorization system in Laravel. Part 2. Gateways, Policies

Introduction


Hello dear Khabrovchane.
I continue my article series on advanced resource authorization in Laravel. To better understand what will be discussed in this article - you need to read the first part .


To begin with, I will briefly repeat the statement of the problem: There are a large number of models. It is necessary to design a flexible and easily extensible system for authorizing user actions depending on his role.
In this part, we will talk about configuring the link Policy <=> Gateway. And also one of the options for writing user rights to the database is proposed.


And of course, I’ll immediately clarify that the material is designed for practicing programmers, and will be difficult for a novice developer to understand.



Part 3. Policies, Gateways


Theoretical part


Quite a lot of material on the Internet can be found on the topic - β€œWhat to choose Policy or Gate ?”. For a certain time, I also arrived in a similar error that you need to choose something. But in the end I came to the conclusion - these are two links of the same chain, which are most effectively used in conjunction.
In Laravel, there are two main directions for determining this bundle - manual and automatic. I will not consider manual, since support of this type of bundle will require disproportionately more programmer efforts than setting up an automatic one.


β€” , () . (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) (. ).


That's all for now. Hope this article has been helpful. And if someone is interested in even finer tuning of the rights and restrictions of users, namely, the user's right to watch / change a specific attribute of the model - this is
discussed in the third part .


All Articles