• Время чтения ~4 мин
  • 02.06.2022

В Laravel роли и разрешения были одной из самых запутанных тем на протяжении многих лет. В основном из-за отсутствия документации по этому поводу: те же самые вещи «прячутся» под другими терминами фреймворка, такими как «ворота», «полисы», «охранники» и т. д. В этой статье я постараюсь объяснить их все в «человеческий язык».

Gate — это то же самое, что и разрешение

Одна из самых больших путаниц, на мой взгляд, связана с термином "ворота". Я думаю, что разработчики избежали бы путаницы, если бы их называли так, как они есть.

Шлюзы – это разрешения, просто называемые другим словом.

Какие типичные действия нам нужно выполнять с разрешениями?

  • 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:

приложение/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>

маршруты/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()

public function store(Request $request)
{
	if (!$request->user()->can('create_users'))
        abort(403);
    }
}

Еще один способ — использовать фасад Gate:

public function store(Request $request)
{
	if ($request->user()->cannot('create_users'))
        abort(403);
    }
}

Или наоборот:

public function create()
{
	if (!auth()->user()->can('create_users'))
        abort(403);
    }
}

Или, более короткий способ прервать, с помощниками:

Вариант 4. Контроллер: авторизация()

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);
    }
}

Вариант 5. Класс запроса формы:

public function store(Request $request)
{
    abort_if(Gate::denies('create_users'), 403);
}

Я заметил, что многие разработчики создают классы запросов формы только для того, чтобы определить правила проверки, полностью игнорируя первый метод этого класса, который называется authorize().

Вы также можете использовать его для проверки шлюзов. Таким образом, вы достигаете разделения ответственности, что является хорошей практикой для надежного кода, поэтому контроллер не заботится о проверке, потому что это делается в своем специальном классе запроса формы.

public function store(Request $request)
{
    $this->authorize('create_users');
}

А затем в форме запроса:

Политика: набор разрешений на основе модели

Если ваши разрешения могут быть назначены модели Eloquent, в типичном CRUD-контроллере вы можете создать вокруг них класс политики.

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 [
            // ...
        ];
    }
}

Он создаст файл app/Policies/UserPolicy.php, с методами по умолчанию, которые имеют комментарий, поясняющий их назначение:

В каждом из этих методов вы определяете условие для возврата true/false. Итак, если мы будем следовать тем же примерам, что и Гейтс ранее, мы можем сделать следующее:

Затем вы можете проверить Политику очень похоже на Gates:

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)
    {
        //
    }
}

Другими словами, Политики — это просто еще один способ группировки разрешений, а не Gates. Если ваши действия в основном связаны с CRUD моделей, то политики, вероятно, будут более удобным и лучше структурированным вариантом, чем Gates.

class ProductPolicy
{
    public function create(User $user)
    {
        return $user->is_admin == 1;
    }

Роль: универсальный набор разрешений

public function store(Request $request)
{
    $this->authorize('create', Product::class);
}

Давайте обсудим еще одну путаницу: в документации Laravel вы не найдете ни одного раздела о ролях пользователей.

С точки зрения фреймворка не существует "ролей", есть только шлюзы/политики, которые вы можете группировать по своему усмотрению.


Другими словами, роль — это объект ВНЕ фреймворка Laravel, поэтому нам нужно самим построить структуру ролей.Это может быть частью общей путаницы с авторизацией, но в этом есть смысл, потому что мы должны контролировать, как определяются роли:

Итак, функциональность роли — это еще один уровень вашего приложения Laravel. Здесь мы переходим к пакетам Laravel, которые могут помочь. Но мы также можем создавать роли без каких-либо пакетов:

Последняя часть — самая важная.

Итак, вместо:

  • Is it one role or multiple roles?
  • Can a user have one role or multiple roles?
  • Who can manage roles in the system?
  • etc.

Вы должны сделать что-то вроде:

  1. Create "roles" DB table and Role Eloquent Model
  2. Add a relationship from User to Role: one-to-many or many-to-many
  3. Seed the default Roles and assign them to the existing Users
  4. Assign a default Role at the registration
  5. Change Gates/Policies to check the Role instead

Опять же, здесь у вас есть несколько вариантов проверки ролей. В приведенном выше примере мы предполагаем, что существует связь belongsTo между пользователем и ролью, а также в модели ролей есть константы, такие как < code>ADMIN = 1, как и EDITOR = 2, чтобы избежать слишком частых запросов к базе данных.

Но если вы предпочитаете гибкость, вы можете каждый раз запрашивать базу данных:

class ProductPolicy
{
    public function create(User $user)
    {
        return $user->is_admin == 1;
    }

Но не забывайте загружать «ролевые» отношения, иначе вы можете легко столкнуться с N +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';
    }

Другими словами, мы не проверяем доступ по ролям. Роль — это всего лишь «искусственный» слой, набор разрешений, который трансформируется в Gates в течение жизненного цикла приложения.


Ох уж эти. Они вызывают столько путаницы на протяжении многих лет.Многие разработчики думали, что Guards — это Роли, и начали создавать отдельные таблицы БД, такие как «administrators», а затем назначать их как Guards.Частично потому, что в документации вы можете найти фрагменты кода, такие как Auth::guard('admin') ->попытка($учетные данные))

Итак, охранники — более глобальное понятие, чем роли.

  • All permissions and roles are saved in the database, managed with some admin panel;
  • Relationships: roles many-to-many permissions, User belongs to Role (or many-to-many roles);
  • Then, in AppServiceProvider you make a foreach loop from all permissions from DB, and run a Gate::define() statement for each of them, returning true/false based on the role;
  • And finally, you check the permissions with @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;
    });
}

Looks complicated?


$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);

Comments

No comments yet
Sarah 3:34 PM

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.

Replies

Sarah 3:34 PM

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.

Sarah 3:34 PM

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.

Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297