Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
В этой статье мы рассмотрим, как реализовать Role-Based Access Control (RBAC) в Laravel 10 для эффективного управления доступом пользователей.
RBAC - это модель безопасности, в которой пользователям назначаются роли на основе их должностных обязанностей, а доступ к ресурсам приложения предоставляется этим ролям.
Этот подход гарантирует, что только авторизованные пользователи имеют доступ к определенным функциям и данным в приложении.
Для реализации RBAC мы будем использовать пакет "wnikk/laravel-access-rules" с Github, который упрощает создание ролей и разрешений.
Мы рассмотрим шаги по созданию ролей и разрешений, назначению их пользователям и защите конфиденциальной информации от несанкционированного доступа.
Одним из основных преимуществ реализации RBAC в Laravel является возможность ограничения и контроля доступа.
С помощью RBAC вы можете определить роли для различных должностей, которые ограничивают доступ к функциям и данным на сайте.
Например, вы можете создать роль "администратор" с полным доступом к приложению, в то время как роль "гость" может иметь доступ только к определенным страницам.
Вы также можете создавать настраиваемые роли, которые имеют доступ к определенным функциям, таким как "менеджер" или "модератор".
Таким образом, пользователи имеют доступ только к функциям, которые необходимы для выполнения их целевых задач.
Для создания RBAC в Laravel мы будем использовать пакет "wnikk/laravel-access-rules" из composer, который предоставляет простой и гибкий способ создания ролей и разрешений.
Этот пакет позволяет нам назначать роли пользователям, назначать разрешения ролям и назначать разрешения напрямую пользователям.
Мы рассмотрим шаги, необходимые для настройки пакета, определения ролей и разрешений, а также назначения их пользователям.
Следуя этому пошаговому руководству, вы сможете легко реализовать RBAC в своем приложении Laravel и обеспечить безопасность учетных записей ваших пользователей.
С чего начнём?
Чтобы упростить процесс реализации прав доступа в Laravel, мы начнём с:
User Management - Мы создадим управление пользователями с помощью Laravel 10. Это позволит легче применять права доступа в Laravel.
Rules Management - Кроме того, мы реализуем управление правилами, чтобы ограничить доступ к контенту, определив список правил для проекта.
Permits and inheritance Management - Управление разрешениями может использоваться для добавления ролей в учетные записи пользователей и назначения прав доступа в Laravel.
News Management - Наконец, мы можем реализовать управление новостями и применять права доступа в Laravel для каждой роли, назначенной пользователю.
Мы также будем использовать функциональность CRUD, которая обеспечивает возможности: создание, чтение, обновление и удаление правил.
Если вы ищете примеры концепций, обсуждаемых в этой статье, вы можете найти их в соответствующем репозитории на GitHub. Просто перейдите по ссылке в репозиторий и просмотрите исходный код, чтобы увидеть реализацию примера. Это позволит вам лучше понять, как работают концепции на практике и как применить их в своих собственных проектах.
Шаг 1: Создание приложения на Laravel
Для начала реализации Laravel 10 первым шагом создадим новое веб-приложение. Для этого откройте терминал или командную строку и инициируйте создание нового приложения на 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 с минимальными изменениями кода, обеспечивая безопасность и легкость обслуживания приложения.