У Laravel ролі та дозволи були однією з найбільш заплутаних тем протягом багатьох років. Здебільшого тому, що про це немає жодної документації: ті самі речі «ховаються» під іншими термінами у фреймворку, як-от «ворота», «поліси», «охорони» тощо. У цій статті я спробую пояснити їх усе в «людська мова».
Ворота – те саме, що і дозвіл
Одною з найбільших плутанини, на мою думку, є термін «ворота». Я думаю, що розробники б уникли багато плутанини, якби їх називали такими, якими вони є.
Ворота — це Дозволи, які називаються іншим словом.
Які типові дії нам потрібно виконувати з дозволами?
- Define the permission, ex. "manage_users"
- Check the permission on the front-end, ex. show/hide the button
- Check the permission on the back-end, ex. can/can't update the data
Так, замініть слово "дозвіл" на "ворота", і ви все зрозумієте.
Простим прикладом Laravel може бути такий:
app/Providers/AppServiceProvider.php:
use App\Models\User;
use Illuminate\Support\Facades\Gate;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Should return TRUE or FALSE
Gate::define('manage_users', function(User $user) {
return $user->is_admin == 1;
});
}
}
resources/views/navigation.blade.php:
<ul>
<li>
<a href="{{ route('projects.index') }}">Projects</a>
</li>
@can('manage_users')
<li>
<a href="{{ route('users.index') }}">Users</a>
</li>
@endcan
</ul>
routes/web.php:
Route::resource('users', UserController::class)->middleware('can:manage_users');
Тепер я знаю, що технічно Gate може означати більше одного дозволу.Отже, замість «manage_users» ви можете визначити щось на кшталт «admin_area». Але в більшості прикладів, які я бачив, ворота є синонімом дозволу.
Також, в деяких випадках, дозволи називаються "здібностями", як у пакеті Bouncer. Це також означає те саме – здатність/дозвіл на якусь дію.Ми розглянемо пакети пізніше в цій статті.
Різні способи перевірки дозволу воріт
Іншим джерелом плутанини є те, як/де перевірити ворота. Він настільки гнучкий, що ви можете знайти дуже різні приклади. Давайте пройдемося по них:
Варіант 1. Маршрути: проміжне програмне забезпечення('can:xxxxxx')
Це приклад зверху.Безпосередньо на маршруті/групі ви можете призначити проміжне програмне забезпечення:
Route::post('users', [UserController::class, 'store'])
->middleware('can:create_users');
Варіант 2.
Варіант 3.Gate::allows() або Gate::denies() Інший спосіб – використовувати фасад воріт: Або навпаки: Або коротший спосіб переривання за допомогою помічників: Варіант 4. Контролер: authorize() Ще коротший і мій улюблений варіант – використовувати Варіант 5. Клас запиту форми: Я помітив, що багато розробників створюють класи запиту форми лише для визначення правила перевірки, повністю ігноруючи перший метод цього класу, який є Ви також можете використовувати його для перевірки воріт. Таким чином, ви досягаєте поділу проблем, що є гарною практикою для твердого коду, тому контролер не піклується про перевірку, оскільки це зроблено у спеціальному класі Form Request. А потім у запиті форми: Якщо ваші дозволи можна призначити моделі Eloquent, у типовому контролері CRUD ви можете створити навколо них клас політики. Якщо ми запустимо цю команду: Це створить файл app/Policies/UserPolicy.php із методами за замовчуванням, які мають коментар, що пояснює їх призначення: У кожному з цих методів ви визначаєте умову повернення істини/неправди. Отже, якщо ми наслідуємо ті самі приклади, що й раніше Гейтс, ми зможемо зробити це: Тоді ви можете перевірити Політику дуже подібно до Gates: Отже, ви вказуєте назву методу та назву класу політики. Іншими словами, політики – це лише інший спосіб групувати дозволи замість Gates. Якщо ваші дії здебільшого стосуються CRUD моделей, тоді політики, ймовірно, є більш зручним і краще структурованим варіантом, ніж Gates. Давайте обговоримо ще одну плутанину: у документах Laravel ви не знайдете жодного розділу про ролі користувачів.
З точки зору платформи, не існує «ролей», є лише ворота/політики, за якими ви можете групувати будь-яким способом. Іншими словами, роль є об’єктом ПОЗА межами фреймворка Laravel, тож нам потрібно створити рольову структуру самостійно.Це може бути частиною загальної плутанини з аутентифікацією, але це цілком сенс, оскільки ми повинні контролювати, як визначаються ролі: Отже, функція Role – це ще один рівень вашої програми Laravel. Ось де ми потрапляємо до пакетів Laravel, які можуть допомогти. Але ми також можемо створити ролі без жодного пакету: Останній біт є найважливішим. Отже, замість: Ви б зробили щось на кшталт: Знову ж таки, тут у вас є кілька варіантів перевірки ролей. У наведеному вище прикладі ми припускаємо, що від користувача до ролі існує відношення Але якщо ви віддаєте перевагу бути гнучким, ви можете щоразу запитувати базу даних: Але не забудьте завантажити "рольові" відносини, інакше ви можете легко зіткнутися з N Проблема із запитом +1 тут. З мого особистого досвіду, звичайна модель побудови всього разом така: Іншими словами, ми не перевіряємо доступ за ролями. Роль — це лише «штучний» рівень, набір дозволів, який трансформується у Gates протягом життєвого циклу програми. О, ці. Вони викликають так багато плутанини протягом багатьох років.Багато розробників подумали, що Guards — це ролі, і почали створювати окремі таблиці БД, як-от «адміністратори», а потім призначати їх як Guards. Частково тому, що в документації ви можете знайти фрагменти коду, наприклад Отже, охоронці – це більш глобальне поняття, ніж ролі.
Looks complicated?
public function store(Request $request)
{
if (!$request->user()->can('create_users'))
abort(403);
}
}
public function store(Request $request)
{
if ($request->user()->cannot('create_users'))
abort(403);
}
}
public function create()
{
if (!auth()->user()->can('create_users'))
abort(403);
}
}
public function store(Request $request)
{
if (!Gate::allows('create_users')) {
abort(403);
}
}
authorize()
у контролерах.У разі невдачі він автоматично повертає сторінку 403.public function store(Request $request)
{
if (Gate::denies('create_users')) {
abort(403);
}
}
public function store(Request $request)
{
abort_if(Gate::denies('create_users'), 403);
}
authorize()
.public function store(Request $request)
{
$this->authorize('create_users');
}
Політика: набір дозволів на основі моделі
public function store(StoreUserRequest $request)
{
// No check is needed in the Controller method
}
class StoreProjectRequest extends FormRequest
{
public function authorize()
{
return Gate::allows('create_users');
}
public function rules()
{
return [
// ...
];
}
}
php artisan make:policy ProductPolicy --model=Product
use App\Models\Product;
use App\Models\User;
class ProductPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user)
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Product $product)
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user)
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Product $product)
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Product $product)
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Product $product)
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Product $product)
{
//
}
}
class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}
Роль: універсальний набір дозволів
public function store(Request $request)
{
$this->authorize('create', Product::class);
}
belongsTo
, а також є константи в моделі ролі, наприклад < code>ADMIN = 1, як EDITOR = 2
, щоб уникнути зайвих запитів до бази даних.class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}
class ProductPolicy
{
public function create(User $user)
{
return $user->role_id == Role::ADMIN;
}
Гнучкість: дозволи, збережені в БД
class ProductPolicy
{
public function create(User $user)
{
return $user->role->name == 'Administrator';
}
Auth::guard('admin') ->спроба($credentials))
foreach
loop from all permissions from DB, and run a Gate::define()
statement for each of them, returning true/false based on the role;@can('permission_name')
and $this->authorize('permission_name')
, like in the examples above.$roles = Role::with('permissions')->get();
$permissionsArray = [];
foreach ($roles as $role) {
foreach ($role->permissions as $permissions) {
$permissionsArray[$permissions->title][] = $role->id;
}
}
// Every permission may have multiple roles assigned
foreach ($permissionsArray as $title => $roles) {
Gate::define($title, function ($user) use ($roles) {
// We check if we have the needed roles among current user's roles
return count(array_intersect($user->roles->pluck('id')->toArray(), $roles)) > 0;
});
}
$user->givePermissionTo('edit articles');
$user->assignRole('writer');
$role->givePermissionTo('edit articles');
$user->can('edit articles');
Bouncer::allow($user)->to('create', Post::class);
Bouncer::allow('admin')->to('ban-users');
Bouncer::assign('admin')->to($user);