
В этой статье мы рассмотрим, как реализовать Role-Based Access Control (RBAC) в Laravel 12 для эффективного управления доступом пользователей.
RBAC - это модель безопасности, в которой пользователям назначаются роли на основе их должностных обязанностей, а доступ к ресурсам приложения предоставляется этим ролям.
Этот подход гарантирует, что только авторизованные пользователи имеют доступ к определенным функциям и данным в приложении.
Для реализации RBAC мы будем использовать пакет "wnikk/laravel-access-rules" с Github, который упрощает создание ролей и разрешений.
Мы рассмотрим шаги по созданию ролей и разрешений, назначению их пользователям и защите конфиденциальной информации от несанкционированного доступа.
Одним из основных преимуществ реализации RBAC в Laravel является возможность ограничения и контроля доступа.
С помощью RBAC вы можете определить роли для различных должностей, которые ограничивают доступ к функциям и данным на сайте.
Например, вы можете создать роль "администратор" с полным доступом к приложению, в то время как роль "гость" может иметь доступ только к определенным страницам.
Вы также можете создавать настраиваемые роли, которые имеют доступ к определенным функциям, таким как "менеджер" или "модератор".
Таким образом, пользователи имеют доступ только к функциям, которые необходимы для выполнения их целевых задач.

Для создания RBAC в Laravel мы будем использовать пакет "wnikk/laravel-access-rules" из composer, который предоставляет простой и гибкий способ создания ролей и разрешений.
Этот пакет позволяет нам назначать роли пользователям, назначать разрешения ролям и назначать разрешения напрямую пользователям.
Мы рассмотрим шаги, необходимые для настройки пакета, определения ролей и разрешений, а также назначения их пользователям.
Следуя этому пошаговому руководству, вы сможете легко реализовать RBAC в своем приложении Laravel и обеспечить безопасность учетных записей ваших пользователей.
С чего начнём?
Чтобы упростить процесс реализации прав доступа в Laravel, мы начнём с:
User Management - Мы создадим управление пользователями с помощью Laravel. Это позволит легче применять права доступа в Laravel.
Rules Management - Кроме того, мы реализуем управление правилами, чтобы ограничить доступ к контенту, определив список правил для проекта.
Permits and inheritance Management - Управление разрешениями может использоваться для добавления ролей в учетные записи пользователей и назначения прав доступа в Laravel.
News Management - Наконец, мы можем реализовать управление новостями и применять права доступа в Laravel для каждой роли, назначенной пользователю.
Мы также будем использовать функциональность CRUD, которая обеспечивает возможности: создание, чтение, обновление и удаление правил.
Если вы ищете примеры концепций, обсуждаемых в этой статье, вы можете найти их в соответствующем репозитории на GitHub. Просто перейдите по ссылке в репозиторий и просмотрите исходный код, чтобы увидеть реализацию примера. Это позволит вам лучше понять, как работают концепции на практике и как применить их в своих собственных проектах.
Шаг 1: Создание приложения на Laravel
Для начала реализации Laravel первым шагом создадим новое веб-приложение. Для этого откройте терминал или командную строку и инициируйте создание нового приложения на Laravel:
composer create-project laravel/laravel rules-example
Шаг 2: Установка пакетов
Далее нам нужно установить необходимый пакет Wnikk для Access Control Rules (ACR) и пакет визуального контроля. Это можно легко сделать, открыв терминал и выполнить указанные ниже команды:
composer require wnikk/laravel-access-rules composer require wnikk/laravel-access-ui
Чтобы внести изменения в пакет Wnikk, нам нужно выполнить команду, которая создаст конфигурационные файлы, файлы миграции и файлы представлений. Следуя этому шагу, вы сможете настроить пакет, чтобы он соответствовал конкретным требованиям вашего приложения:
php artisan vendor:publish --provider="Wnikk\\LaravelAccessRules\\AccessRulesServiceProvider" php artisan vendor:publish --provider="Wnikk\\LaravelAccessUi\\AccessUiServiceProvider"
Шаг 3: Обновление модели User
Теперь мы будем интегрировать ACR с нашей существующей моделью пользователя. Этот шаг важен, чтобы гарантировать, что в нашем приложении правильно настроены механизмы контроля доступа. Нам нужно только добавить trait HasPermissions в модель:
use Wnikk\LaravelAccessRules\Traits\HasPermissions; class User extends Model { // The User model requires this trait use HasPermissions;
Шаг 4: Настройка соединения с базой данных
Для целей этого примера мы будем использовать файловую базу данных SQLite. Чтобы начать, создайте пустой файл с именем "./database/database.sqlite" и настройте соединение с базой данных, как показано в приведенном примере.
Файл .env:
DB_CONNECTION=sqlite
На этом этапе мы готовы выполнить команду миграции. Выполнение этой команды позволит создать необходимые таблицы в нашей SQLite-базе данных.
php artisan migrate
Теперь, когда мы собрали работающую систему контроля доступа с помощью пакета ACR, следующим шагом будет добавление разрешений (permissions) для моделей в нашем приложении. Разрешения определяют, какие действия пользователь может выполнять для определенного ресурса.
Шаг 5: Создание миграции для новостей
Переходим к следующему шагу: созданию миграции для таблицы news. Чтобы выполнить это задание, выполните следующую команду, которая сгенерирует необходимый файл и позволит определить схему таблицы:
php artisan make:migration create_news_table
Это создаст новый файл миграции в директории database/migrations web-приложения. Ниже вы найдете полный код, необходимый для определения структуры таблицы, включая различные поля и их соответствующие типы:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('news', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->integer('user_id'); $table->string('name', 70); $table->string('description', 320)->nullable(); $table->text('body'); $table->softDeletes(); }); } public function down(): void { Schema::dropIfExists('news'); } };
Теперь запустим миграцию повторно:
php artisan migrate
Шаг 6: Создание модели
Теперь создадим модель для новостей. Чтобы сгенерировать модель новостей, необходимо выполнить следующую команду Artisan. Это создаст модель новостей в каталоге app\Models.
php artisan make:model News
Пример кода для модели новостей:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; /** * Class News * * @property $id * @property $user_id * @property $name * @property $description * @property $body */ class News extends Model { use HasFactory, SoftDeletes; protected $fillable = [ 'user_id', 'name', 'description', 'body', ]; }
Шаг 7: Создание Seeder
Теперь, когда у нас есть все необходимые таблицы в базе данных, настало время заполнить их тестовыми данными и настроить правила для них.
Для этого мы создадим Seeder - класс, которые позволят нам заполнить таблицы начальными данными.
1. Создадим несколько пользователей:
php artisan make:seeder CreateUserSeeder
Исходник файла database\seeders\CreateUserSeeder.php:
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; class CreateUserSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { DB::table('users')->insert([ 'id' => 1, 'name' => 'Test user 1', 'email' => 'root@mail.com', 'password' => Hash::make('12345'), ]); DB::table('users')->insert([ 'id' => 2, 'name' => 'Test user 2', 'email' => 'test@mail.com', 'password' => Hash::make('password'), ]); DB::table('users')->insert([ 'name' => 'Test user 3', 'email' => Str::random(10).'@mail.com', 'password' => Hash::make(Str::random(10)), ]); DB::table('users')->insert([ 'name' => 'Test user 4', 'email' => Str::random(10).'@mail.com', 'password' => Hash::make(Str::random(10)), ]); DB::table('users')->insert([ 'name' => 'Test user 5', 'email' => Str::random(10).'@mail.com', 'password' => Hash::make(Str::random(10)), ]); } }
2. Создаем несколько записей новостей.
php artisan make:seeder NewsTableSeeder
Исходник файла database\seeders\NewsTableSeeder.php:
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use App\Models\News; class NewsTableSeeder extends Seeder { public function run(): void { News::create([ 'user_id' => 1, 'name' => 'First news', 'description' => 'Description of first news', 'body' => 'Body content 1...', ]); News::create([ 'user_id' => 1, 'name' => 'Second news', 'description' => 'Description of second test news', 'body' => 'Body content 2...', ]); News::create([ 'user_id' => 2, 'name' => 'News of test user', 'body' => 'Body content 3...', ]); } }
3. Множество правил для тестирования.
php artisan make:seeder CreateRulesSeeder
Правила сами по себе:
Исходник файла database\seeders\CreateRulesSeeder.php:
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use Wnikk\LaravelAccessRules\AccessRules; class CreateRulesSeeder extends Seeder { public function run(): void { // example #1 - route middleware AccessRules::newRule('example1.viewAny', 'View all users on example1'); // example #2 - check in action AccessRules::newRule('example2.view', 'View data of user on example2'); // example #3 - check on action options AccessRules::newRule([ 'guard_name' => 'example3.update', 'title' => 'Changing different user data on example3', 'options' => 'required|in:name,email,password' ]); // example #4 - global resource AccessRules::newRule('viewAny', 'Global rule "viewAny" for example4'); AccessRules::newRule('view', 'Global rule "view" for example4'); AccessRules::newRule('create', 'Global rule "create" for example4'); AccessRules::newRule('update', 'Global rule "update" for example4'); AccessRules::newRule('delete', 'Global rule "delete" for example4'); // example #5 - resource for controller AccessRules::newRule('Examples.Example5.viewAny', 'Rule for one Controller his action "viewAny" example5'); AccessRules::newRule('Examples.Example5.view', 'Rule for one Controller his action "view" example5'); AccessRules::newRule('Examples.Example5.create', 'Rule for one Controller his action "create" example5'); AccessRules::newRule('Examples.Example5.update', 'Rule for one Controller his action "update" example5'); AccessRules::newRule('Examples.Example5.delete', 'Rule for one Controller his action "delete" example5'); // example #6 - magic self AccessRules::newRule( 'example6.update', 'Rule that allows edit all news', 'An example of how to use a magic suffix ".self" on example6' ); AccessRules::newRule('example6.update.self', 'Rule that allows edit only where user is author'); // example #7 - Policy AccessRules::newRule('Example7News.test', 'Rule event "test" example7'); // Final example, add control to the Access user interface $id = AccessRules::newRule('Examples.UserRules.main', 'View all rules, permits and inheritance'); AccessRules::newRule('Examples.UserRules.rules', 'Working with Rules', null, $id, 'nullable|in:index,store,update,destroy'); AccessRules::newRule('Examples.UserRules.roles', 'Working with Roles', null, $id, 'nullable|in:index,store,update,destroy'); AccessRules::newRule('Examples.UserRules.inherit', 'Working with Inherit', null, $id, 'nullable|in:index,store,destroy'); AccessRules::newRule('Examples.UserRules.permission', 'Working with Permission', null, $id, 'nullable|in:index,update'); } }
4. Мы теперь создадим роль супер-администратора.
От которой будут наследоваться другие пользовательские роли. В этом шаге установлено три типа моделей, которые могут иметь разрешения в файле настроек по умолчанию (config/access.php): группы (groups), роли (roles) и пользователи (users). Для супер-администратора мы будем использовать роли.
php artisan make:seeder CreateRootAdminRoleSeeder
Вся логика заложена в том что у разрешений есть владелец, владелец это абстракция, которая закрепляется за любой моделью.
Исходник файла database\seeders\CreateRootAdminRoleSeeder.php:
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use Wnikk\LaravelAccessRules\AccessRules; class CreateRootAdminRoleSeeder extends Seeder { public function run(): void { $acr = new AccessRules; $acr->newOwner('Role', 'root', 'RootAdmin role'); // For example #1 $acr->addPermission('example1.viewAny'); // For example #2 $acr->addPermission('example2.view'); // For example #3 $acr->addPermission('example3.update', 'name'); $acr->addPermission('example3.update', 'email'); $acr->addPermission('example3.update', 'password'); // For example #4 $acr->addPermission('viewAny'); $acr->addPermission('view'); $acr->addPermission('create'); $acr->addPermission('update'); $acr->addPermission('delete'); // For example #5 $acr->addPermission('Examples.Example5.viewAny'); $acr->addPermission('Examples.Example5.view'); $acr->addPermission('Examples.Example5.create'); $acr->addPermission('Examples.Example5.update'); $acr->addPermission('Examples.Example5.delete'); // For example #6 //For all - $acr->addPermission('example6.update'); $acr->addPermission('example6.update.self'); // For example #7 $acr->addPermission('Example7News.test'); // For final example $acr->addPermission('Examples.UserRules.index'); $acr->addPermission('Examples.UserRules.rules'); $acr->addPermission('Examples.UserRules.rules', 'index'); $acr->addPermission('Examples.UserRules.rules', 'store'); $acr->addPermission('Examples.UserRules.rules', 'update'); $acr->addPermission('Examples.UserRules.rules', 'destroy'); $acr->addPermission('Examples.UserRules.roles'); $acr->addPermission('Examples.UserRules.roles', 'index'); $acr->addPermission('Examples.UserRules.roles', 'store'); $acr->addPermission('Examples.UserRules.roles', 'update'); $acr->addPermission('Examples.UserRules.roles', 'destroy'); $acr->addPermission('Examples.UserRules.inherit'); $acr->addPermission('Examples.UserRules.inherit', 'index'); $acr->addPermission('Examples.UserRules.inherit', 'store'); $acr->addPermission('Examples.UserRules.inherit', 'destroy'); $acr->addPermission('Examples.UserRules.permission'); $acr->addPermission('Examples.UserRules.permission', 'index'); $acr->addPermission('Examples.UserRules.permission', 'update'); } }
5. И, наконец-то, добавим наследование прав от супер-администратора ко всем пользователям.
php artisan make:seeder AddRoleToAllUserSeeder
Исходник файла database\seeders\AddRoleToAllUserSeeder.php:
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use App\Models\User; class AddRoleToAllUserSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { $all = User::all(); foreach ($all as $one) { $one->inheritPermissionFrom('Role', 'root'); } // or // $acr = new AccessRules; // $acr->setOwner('Role', 'root'); // foreach ($all as $one) {$one->inheritPermissionFrom($acr);} // or // $mainUser = User::find(1); // foreach ($all as $one) {$one->inheritPermissionFrom($mainUser);} } }
Перейдем к импорту всех инструкций, созданных на этом шаге:
php artisan db:seed --class=CreateUserSeeder php artisan db:seed --class=NewsTableSeeder php artisan db:seed --class=CreateRulesSeeder php artisan db:seed --class=CreateRootAdminRoleSeeder php artisan db:seed --class=AddRoleToAllUserSeeder
Все те же манипуляции с контролем доступа и наследованием, можно выполнить используя интерфейс, который был добавлен в начале этой статьи.
Вы можете получить доступ к интерфейсу, открыв адрес "/accessui/" в вашем проекте, при базовых настройках когда он включен:
Список всех ролей, групп и пользователей:

Список всех правил:

Давайте перейдем к самой интересной части ☕
Различным методам проверки доступа и связанных с ними правил.
В дальнейшем контроллеры, использованные в примерах, будут возвращать JSON данные, как для SPA Frontend.
Таким образом, не требуется в этих примерах создавать еще шаблоны.
Пример 1
В этом примере мы используем middleware в маршрутизации, чтобы ограничить доступ к контроллеру.
Добавим в файл routes\web.php:
Route::get('/example1', [Example1Controller::class, 'index'])->middleware('can:example1.viewAny');
Контроллер стандартный и не используется для проверок, в данном примере.
Вариация файла ...Example1Controller.php:
<?php namespace App\Http\Controllers\Examples; use Illuminate\Support\Facades\Response; use App\Http\Controllers\Controller; use App\Models\User; class Example1Controller extends Controller { public function index() { return Response::json(User::all(), 200); } }
Что происходит в результате:

Пример 2
Давайте попробуем проверить разрешение в самом контроллере.
Добавим в файл routes\web.php:
Route::get('/example2', [Example2Controller::class, 'show']);
Исходник файла ...Example2Controller.php:
<?php namespace App\Http\Controllers\Examples; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Gate; use App\Http\Controllers\Controller; class Example2Controller extends Controller { public function show() { Gate::authorize('example2.view'); return Response::json(Auth::user()->toArray(), 200); } }
Пример, как можно использовать фасад Laravel Gate.
Пример 3
Этот пример очень похож на предыдущий, но с использованием параметра опции.
Добавим в файл routes\web.php:
Route::any('/example3/{frm}', [Example3Controller::class, 'update']);
Исходник файла ...Example3Controller.php:
<?php namespace App\Http\Controllers\Examples; use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Hash; use App\Http\Controllers\Controller; use App\Enum\UserProfileFormEnum; class Example3Controller extends Controller { public function update(UserProfileFormEnum $frm, Request $request) { // Add the check by indicating after the point of the [Option] field Gate::authorize('example3.update.'.$frm->value); $user = Auth::user(); switch ($frm) { case(UserProfileFormEnum::Name): if($request->name) { $user->fill( $request->only(['name']) ); } break; case(UserProfileFormEnum::Password): if($request->password) { $user->password = Hash::make($request->password); } break; case(UserProfileFormEnum::Email): $validator = Validator::make($request->all(), [ 'email' => 'required|email', ]); if ($validator->fails()) { abort('403', $validator->messages()); } $user->email = $request->email; break; } return Response::json($user->save(), 200); } }
Почему возникает такое поведение и в чем отличие поля "Option" от стандартного определения правил?
Стоит отметить, что поле "Option" не предназначено не для правила, а к самим разрешением.
Это сделано для того, чтобы позволить создавать несколько разрешений в рамках одного правила.
Например, можно получить отдельный список записей по ID, к которым необходимо организовать доступ, не создавая отдельных таблиц или полей.
Пример 4
В этом примере мы используем встроенную функцию $this->authorizeResource(), которая поставляется вместе с функцией ресурсов (resource). Эта функция очень удобна, так как автоматически создает проверки для следующих правил: "viewAny", "view", "create", "update" и "delete".
Добавим в файл routes\web.php:
Route::apiResource('example4', Example4Controller::class)->parameters([ 'example4' => 'news' ]);
Исходник файла ...Example4Controller.php:
<?php namespace App\Http\Controllers\Examples; use Illuminate\Http\Request; use Illuminate\Support\Facades\Response; use App\Http\Controllers\Controller; use App\Models\News; class Example4Controller extends Controller { /** * Create the controller instance. */ public function __construct() { $this->authorizeResource(News::class, 'News'); } /** * Display a listing of the resource. */ public function index() { return Response::json(News::all()); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $news = News::create($request->toArray()); return Response::json($news->id, 201); } /** * Display the specified resource. */ public function show(News $news) { return Response::json($news->toArray()); } /** * Update the specified resource in storage. */ public function update(Request $request, News $news) { $news->fill($request->toArray()); return Response::json($news->save); } /** * Remove the specified resource from storage. */ public function destroy(News $news) { return Response::json($news->delete()); } }
Пример 5
В предыдущем примере мы использовали глобальные правила, что не очень удобно. Чтобы это исправить, мы можем создать trait который позволит создавать правила для каждого контроллера отдельно.
Исходник файла App\Http\Traits\GuardedController.php:
<?php namespace App\Http\Traits; use App\Http\Controllers\Controller; trait GuardedController { /** * Map of resource methods to ability names * @example ['index' => 'viewAny'] * * @var string[] */ //abstract protected $guardedMethods = []; /** * Do not automatically scan all available methods. * * @var bool */ //abstract protected $disableAutoScanGuard = true; /** * List of resource methods which do not have model parameters. * @example ['index'] * * @var string[] */ //abstract protected $methodsWithoutModels = ['index']; /** * Get the map of resource methods to ability names. * * @return array */ protected function resourceAbilityMap() { if (empty($this->disableAutoScanGuard)) { $methods = array_diff( get_class_methods($this), get_class_methods(Controller::class) ); $map = array_combine($methods, $methods); } else { $map = []; } $map = array_merge($map, parent::resourceAbilityMap()); $map = array_merge($map, $this->guardedMethods??[]); // Replace name for class App\Http\Controllers\Examples\Example1Controller // to guard prefix "Examples.Example1." $name = $this->getClassNameGate(); // Replace standard rule "viewAny" to "Examples.Example1.viewAny" foreach ($map as &$item) {$item = $name.$item;} unset($item); return $map; } /** * Get the list of resource methods which do not have model parameters. * * @return array */ protected function resourceMethodsWithoutModels() { $base = parent::resourceMethodsWithoutModels(); return array_merge($base, $this->methodsWithoutModels??[]); } /** * Get name off class witch namespace for guard * * @param string|null $action * @return string */ protected static function getClassNameGate(?string $action = null): string { // Replace name for class App\Http\Controllers\Examples\Example1Controller // to guard prefix "Examples.Example1." $name = str_replace([ 'App\\Http\\Controllers\\', '\\', 'Controller' ], [ '', '.', '.' ], static::class); return $name.$action; } }
Контроллер и его пример остаются точно такими же, как в предыдущем примере.
Только добавляется трейт (файл ...Example5Controller.php):
<?php namespace App\Http\Controllers\Examples; ... use App\Http\Traits\GuardedController; class Example5Controller extends Controller { use GuardedController; public function __construct() { $this->authorizeResource(News::class, 'News'); } ...
Просмотр уже дает другую ошибку:

Таким образом, с минимальными изменениями в существующем коде
можно легко включить возможности динамического управления доступом.
Пример 6
Ниже представлен достаточно простой пример, похожий на второй.
Здесь присутствует использование "магиеской" проверки.
Добавим в файл routes\web.php:
Route::any('/example6/{news}', [Example6Controller::class, 'update']);
Исходник файла ...Example6Controller.php:
<?php namespace App\Http\Controllers\Examples; use Illuminate\Http\Request; use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Gate; use App\Http\Controllers\Controller; use App\Models\News; class Example6Controller extends Controller { public function update(Request $request, News $news) { Gate::authorize('example6.update', $news); $news->fill($request->toArray()); return Response::json($news->save?1:0); } }
Давайте внимательнее рассмотрим это. Если у нас есть правило "example6.update.self",
нам необходимо проверять правило "example6.update", система сама добавит ".self",
если есть объект записи для проверки внутри ACR.
Другии словами работа ACR будет выглядеть так:
if ( 'example6.update' === $ability && Gate::allows('example6.update.self') && $user->id === $news->user_id ) { return true; }
Кроме того, стоит отметить, что если мы не проверяем пользователя, а другую модель, например, модератор.
ACR отслеживает это, и проверка будет выглядеть, примерно так, внутри системы:
$moderator = App\Models\Moderator::find('...'); if ( 'example6.update' === $ability && Gate::forUser($moderator)->allows('example6.update.self') && $moderator->uuid === $news->moderator_uuid ) { return true; }
За счет таких возможностей больше не нужно выполнять проверки на владельца, поскольку магический метод будет обрабатывать их автоматически.
Пример 7
Хотя это не ABAC, необходимая функциональность контроля доступа на основе атрибутов, может быть достигнута, путем механизма политик Laravel.
Все предыдущие примеры сосредоточены на проверке доступа к контроллеру, но мы можем использовать тот же подход в "Policy" для реализации контроля доступа через атрибуты со всеми их вариациями.
Для этого необходимо сгенерировать политику для нашей модели:
php artisan make:policy NewsPolicy –model=News
Исходник файла ...NewsPolicy.php:
<?php namespace App\Policies; use App\Models\News; use App\Models\User; use Illuminate\Auth\Access\Response; class NewsPolicy { public function availableUpdateOnSomeTime(User $user, News $news): ?bool { if( $user->can('Example7News.allowedEditLast24Hours', $news) && stripos($user->name, 'author') !== false && ($news->created_at->isToday() || $news->created_at->isYesterday()) ) { return true; } return null; } }
После этого необходимо обновить значение $policies в классе AuthServiceProvider, как написано ниже:
protected $policies = [ News::class => NewsPolicy::class, ];
Теперь проверим политику в контроллере:
Исходник файла ...Example7Controller.php:
<?php namespace App\Http\Controllers\Examples; use Illuminate\Http\Request; use Illuminate\Support\Facades\Response; use App\Http\Controllers\Controller; use App\Models\News; class Example7Controller extends Controller { public function index(News $news) { $this->authorize('availableUpdateOnSomeTime', $news); return Response::json($news->toArray()); } }
Таким образом, теперь мы можем проверять не только правила индивидуально, но также проверять атрибуты модели или пользователя.
Важно отметить, что если вы добавите правило "availableUpdateOnSomeTime" и разрешение для пользователя, то политика не будет проверена.
Финальный пример
По умолчанию интерфейс AccessUi не включает проверку уровня предоставленного доступа.
Чтобы исправить это, мы можем создать прокси-контроллер, который будет проверять все разрешения перед любой манипуляцией с данными.
Сначала отключим стандартные маршруты AccessUi.
Для этого нужно отредактировать файл config/accessUi.php:
/** * Panel Register * * This manages if routes used for the admin panel should be registered. * Turn this value to false if you don't want to use admin panel */ 'register' => false,
Затем мы создадим 2 контроллера: "UserRulesController" и "UserProfileController", которые используют трейт "RunsAnotherController" для запуска других контроллеров от AccessUi.
Также добавим представление в файлах "user-rules.blade.php" и "user-profile.blade.php".
Файлы немного длинные для статьи, но их можно просмотреть отдельно в репозитории.
Как результат, у нас будет отдельные страницы в нашем стиле с проверкой прав доступа
Страница профиля авторизованного пользователя:

Список правил и только ролей (скрыт список пользователей и групп):

Заключение, с помощью "wnikk/laravel-access-rules" (ACR, ACL, RBAC) в проекте на Laravel - можно создать, мощный способ обеспечения доступа, пользователей только к тем ресурсам, к которым они авторизованы.
С помощью встроенных в Laravel функций middleware и авторизации, можно легко создавать и управлять сложными политиками контроля доступа как на глобальном, так и на уровне конкретного контроллера или модели.
Используя Access-Control-Rules, разработчик может добавлять возможности динамического контроля доступа в свои приложения Laravel с минимальными изменениями кода, обеспечивая безопасность и легкость обслуживания приложения.
От автора - отличия от spatie или других похожих решений:
Роли предоставлены как абстракция. "Разрешения" можно присваивать на прямую пользователям, другим моделям, группам, не используя роли как таковые.
Модель пользователя/группы/роли, может быть, не привязана к базе данных. Достаточно уникального идентификатора, чтобы назначать разрешения.
К примеру: REST API или AD пользователям, база которых и структура в другом месте.
Простая проверка на владельца, через "магические" разрешения "something-rule.self", хорошо показано в 6 примере.
Неограниченная система наследования, разрешения могут наследоваться не только от ролей или групп, но и от пользователей или других моделей.
Вслед за наследованием "разрешений" также есть возможность наследования "запрета". Так можно пользователю унаследовать большой список разрешений и запретить какое-то отдельные правила.
Пример: Роль "Admin root" имеет все доступы, роль "Test admin root" которая наследует все разрешения от первой и имеет запреты, к личным данным на продакшене.
Тестировщику в данном примере будет достаточно унаследовать только роль "Test admin root".
Чтобы иметь возможность доступа к всему, кроме персональной информации.
