• Czas czytania ~8 min
  • 02.06.2022

W Laravel role i uprawnienia były przez lata jednym z najbardziej mylących tematów. Przede wszystkim dlatego, że nie ma na ten temat dokumentacji: te same rzeczy „chowają się” pod innymi terminami we frameworku, np. „bramy”, „polityki”, „strażnicy” itp. W tym artykule postaram się je wszystkie wyjaśnić w „język ludzki”.

Brama jest taka sama jak Pozwolenie

Moim zdaniem jednym z największych nieporozumień jest termin „brama”. Myślę, że programiści uniknęliby wielu nieporozumień, gdyby nazywano ich tym, czym są.

Bramki to uprawnienia, nazywane po prostu innym słowem.

Jakie są typowe działania, które musimy wykonać z uprawnieniami?

  • 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

Tak, zamień słowo „pozwolenie” na „brama”, a wszystko zrozumiesz.

Prosty przykład Laravela to:

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

zasoby/widoki/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>

trasy/web.php:

Route::resource('users', UserController::class)->middleware('can:manage_users');

Teraz wiem, że technicznie Gate może oznaczać więcej niż jedno pozwolenie.Tak więc zamiast "manage_users" możesz zdefiniować coś takiego jak "admin_area". Ale w większości przykładów, które widziałem, Gate jest synonimem zezwolenia.

Ponadto, w niektórych przypadkach uprawnienia są nazywane „zdolnościami”, jak w pakietu Bouncer. Oznacza to również to samo - zdolność/pozwolenie na jakąś akcję.Przejdziemy do pakietów w dalszej części tego artykułu.


Różne sposoby sprawdzania uprawnień bramki

Kolejnym źródłem nieporozumień jest to, jak/gdzie sprawdzić Bramę. Jest tak elastyczny, że możesz znaleźć bardzo różne przykłady. Przeanalizujmy je:

Opcja 1. Trasy: oprogramowanie pośredniczące('can:xxxxxx')

To jest przykład z góry.Bezpośrednio na trasie/grupie możesz przypisać oprogramowanie pośredniczące:

Route::post('users', [UserController::class, 'store'])
    ->middleware('can:create_users');

Opcja 2.

Opcja 3.Gate::allows() lub Gate::denies()

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

Innym sposobem jest użycie fasady bramy:

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

Lub odwrotnie:

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

Lub krótsza droga do przerwania, z pomocnikami:

Opcja 4. Kontroler: autoryzuj()

public function store(Request $request)
{
	if (!Gate::allows('create_users')) {
        abort(403);
    }
}

Jeszcze krótszą opcją i moją ulubioną jest użycie authorize() w kontrolerach.W przypadku niepowodzenia automatycznie zwróci stronę 403.

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

Opcja 5. Klasa żądania formularza:

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

Zauważyłem, że wielu programistów generuje klasy Form Request tylko w celu zdefiniowania reguły walidacji, całkowicie ignorując pierwszą metodę tej klasy, którą jest authorize().

Możesz go użyć również do sprawdzenia bramek. W ten sposób uzyskujesz separację obaw, co jest dobrą praktyką w przypadku solidnego kodu, więc Kontroler nie zajmuje się walidacją, ponieważ jest skończona w dedykowanej klasie Form Request.

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

A następnie w formularzu Żądanie:

Zasady: zestaw uprawnień na podstawie modelu

Jeśli twoje uprawnienia mogą być przypisane do modelu Eloquent, w typowym kontrolerze CRUD, możesz zbudować wokół nich klasę Policy.

public function store(StoreUserRequest $request)
{
	// No check is needed in the Controller method
}

Jeśli uruchomimy to polecenie:

class StoreProjectRequest extends FormRequest
{
    public function authorize()
    {
        return Gate::allows('create_users');
    }
 
    public function rules()
    {
        return [
            // ...
        ];
    }
}

Wygeneruje plik app/Policies/UserPolicy.php, z domyślnymi metodami, które mają komentarz wyjaśniający ich cel:

W każdej z tych metod definiujesz warunek zwrotu prawda/fałsz. Tak więc, jeśli będziemy postępować zgodnie z tymi samymi przykładami, co wcześniej Gates, możemy to zrobić:

Następnie możesz sprawdzić Politykę w bardzo podobny sposób jak w Gates:

php artisan make:policy ProductPolicy --model=Product

Więc określasz nazwę metody i nazwę klasy polityki.

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

Innymi słowy, zasady to tylko kolejny sposób grupowania uprawnień, a nie Gates. Jeśli Twoje działania dotyczą głównie CRUDów lub Modeli, Zasady są prawdopodobnie wygodniejszą i lepiej ustrukturyzowaną opcją niż Gates.

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

Rola: uniwersalny zestaw uprawnień

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

Omówmy kolejne zamieszanie: w dokumentacji Laravela nie znajdziesz żadnej sekcji dotyczącej ról użytkowników.

Z punktu widzenia ramowego nie ma „roli”, tylko bramki/zasady, według których można grupować w dowolny sposób.


Innymi słowy, rola jest bytem POZA frameworkiem Laravel, więc musimy sami zbudować strukturę roli.Może to być częścią ogólnego zamieszania związanego z uwierzytelnianiem, ale ma to sens, ponieważ powinniśmy kontrolować sposób definiowania ról:

Tak więc funkcjonalność roli to kolejna warstwa twojej aplikacji Laravel. W tym miejscu dochodzimy do pakietów Laravela, które mogą pomóc. Ale możemy również tworzyć role bez żadnego pakietu:

Ostatni bit jest najważniejszy.

Więc zamiast:

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

Zrobiłbyś coś takiego:

  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

Ponownie, tutaj masz kilka opcji sprawdzenia ról.W powyższym przykładzie zakładamy, że istnieje relacja belongsTo od użytkownika do roli, a także istnieją stałe w modelu roli, takie jak < code>ADMIN = 1, jak EDITOR = 2, aby uniknąć zbyt wielu zapytań do bazy danych.

Ale jeśli wolisz być elastyczny, możesz za każdym razem przeszukiwać bazę danych:

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

Pamiętaj jednak, aby szybko załadować relację „roli”, w przeciwnym razie możesz łatwo natknąć się na N +1 problem z zapytaniem tutaj.

class ProductPolicy
{
    public function create(User $user)
    {
        return $user->role_id == Role::ADMIN;
    }

Uelastycznienie: uprawnienia zapisane w DB

Z mojego osobistego doświadczenia wynika, że ​​zwykły model budowania tego wszystkiego razem jest następujący:

class ProductPolicy
{
    public function create(User $user)
    {
        return $user->role->name == 'Administrator';
    }

Innymi słowy, nie sprawdzamy dostępu według ról. Rola to tylko „sztuczna” warstwa, zestaw uprawnień, które są przekształcane w Gates podczas cyklu życia aplikacji.


Och, te. Przez lata powodują tyle zamieszania.Wielu programistów uważało, że Strażnicy są rolami i zaczęli tworzyć oddzielne tabele bazy danych, takie jak „administratorzy”, a następnie przypisywać ich jako Strażników. Częściowo, ponieważ w dokumentacji można znaleźć fragmenty kodu, takie jak Auth::guard('admin') ->próba($credentials))

Tak więc strażnicy są bardziej globalną koncepcją niż role.

  • 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
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297