فصل ۱۴-۳: کنترل سطح دسترسی (ACL) در لاراول با استفاده از Gate و Policy

Access Control Level in Laravel

18 اسفند 1400
درسنامه درس 24 از سری لاراول
Laravel-Main-acl

مقدمه

پس از انجام و نصب احراز هویت کاربران در کنترلر یا به صورت کلی تر در لاراول، به مبحث کنترل سطح دسترسی یا ACL (مخفف عبارت Access Control Level) می‌پردازیم. در مستندات لاراول ممکن است با کلمه‌ای به نام Authorization برخورده باشید. این کلمه به معنای «اجازه» است. به عبارت دیگر این کلمه‌ معنی اجازه و ایجاد سطح دسترسی برای کاربران، می‌باشد. لاراول در این زمینه همچنان بسیار پیشتاز عمل کرده و ابزارهای مفیدی را در اختیار ما قرار می‌دهد تا به راحتی ممکن بتوانیم کاربران خود را کنترل کرده و متناسب با نقش‌هایی که در اختیار آنها قرار داده می‌شود، تنظیماتی صورت گیرد. در کنترل سطح دسترسی (ACL) لاراول دو واژه بکار گرفته می‌شود:‌ gates و policies (به صورت مفرد: gate, policy) که Gate به معنای دروازه و درگاه و Policy به معنای سیاست است.

این دو واژه از نظر ارتباط با یکدیگر دقیقا مشابه مسیرها (routes) و کنترلرها (controllers) عمل می‌کنند. Gates یا درگاه‌ها به عنوان یک Clouser یا تابع جهت اجازه و یا عدم اجازه ورود کاربران به یک صفحه عمل می‌کنند درحالیکه Policies یا سیاست‌ها همانند کنترلرها کارهای پردازشی و منطقی را متناسب با مدل یا ویو گروه‌بندی و دسته‌بندی می‌کنند. در ادامه به توضیح بیشتری خواهیم پرداخت.

یک تعریف جامع: در نظر داشته باشید که Policy و Gate در همکاری با یکدیگر فعالیت می‌کنند. تعریف خلاصه: سیاست یا Policy کارهای پردازشی را انجام می‌دهد و Gate یا دروازه اجازه‌ی عبور یا عدم عبور کاربران را متناسب با سیاستی که تعیین شده است، صادر می‌کند!

توجه داشته باشید که درگاه‌ها و سیاست‌ها به صورت مجزا مورد استفاده قرار نمی‌گیرند. در بسیاری از نرم‌افزارهای هوشمند این دو مفهوم در کنار یکدیگر ایفای نقش کرده و هوشمندسازی نرم‌افزار شما را تقویت می‌کنند. درگاه‌ها (Gates) اغلب برای انجام عملیات‌هایی که به Model یا View ارتباطی ندارند، مورد استفاده قرار می‌گیرند. اما سیاست‌ها یا Policies هنگامی بکار برده می‌شوند که انجام عملیات‌هایی برای سطح دسترسی در لاراول در Model یا View مورد نیاز باشد.

درگاه‌ها (Gates)

نوشتن درگاه‌ها

در واقع درگاه‌ها Clouserهایی هستند که تعیین می‌کنند اگر کاربر احراز هویت شده بود یک عمل خاص را می‌تواند انجام دهد. درگاه‌ها معمولا در کلاس App\Providers\AuthServiceProvider با استفاده از Gate Facade تعریف می‌شوند. همیشه درگاه‌‌ها یک نمونه‌ی user را به عنوان اولین آرگومان ورودی خود دریافت می‌کند. همچنین سایر آرگومان‌های آن بسته به نوع ارتباطی که برای بازیابی Model دارد به آن اضافه خواهد شد:

/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Gate::define('update-post', function ($user, $post) {
        return $user->id == $post->user_id;
    });
}

برای اجازه به دسترسی به یک اکشن خاص، درگاه‌ها از دو متد allows و denies استفاده می‌کنند. توجه داشته باشید که نیازی نیست کاربرانی که احراز هویت شدند به عنوان آرگومان به این متدها ارسال شوند چون لاراول به صورت خودکار دقت می‌کند که ابتدا کاربران احراز هویت شده‌اند؟ سپس اجازه عبور از درگاه را متناسب با سطح دسترسی‌ای که برای آنها تعریف شده است، می‌دهد:

if (Gate::allows('update-post', $post)) {
    // The current user can update the post...
}

if (Gate::denies('update-post', $post)) {
    // The current user can't update the post...
}

اگر بخواهید برای یک کاربر خاص که احراز هویت شده است دسترسی خاصی قائل شوید باید از متد foruser به صورت ترکیبی با متد allows یا denies استفاده کنید:

if (Gate::forUser($user)->allows('update-post', $post)) {
    // The user can update the post...
}

if (Gate::forUser($user)->denies('update-post', $post)) {
    // The user can't update the post...
}

ایجاد سیاست‌ها (Policies)

سیاست‌ها یا Policies کلاس‌هایی هستند که وظیفه‌ی ساماندهی سطح دسترسی‌در لاراول را متناسب با منطقی که روی Model یا View مشخصی اعمال می‌شود، به عهده دارند. برای مثال اگر نرم‌افزار شما یک وبلاگ باشد، شما یک مدل به نام Post و یک PostPolicy برای تعیین سطح دسترسی کاربران و نحوه‌ی فعالیت آنها مثل ایجاد یا بروزرسانی پست‌ها خواهید داشت.

یک سیاست یا Policy را با استفاده از دستور make:policy در آرتیسن ایجاد می‌کنید. سیاست ایجاد شده در پوشه‌ی app/Policies قرار می‌گیرد. اگر این پوشه وجود ندارد با اجرای دستور زیر خود به خود بوجود می‌آید:

php artisan make:policy PostPolicy

دستور فوق یک کلاس خالی از نوع Policy را ایجاد می‌کند. اگر شما علاقه دارید که یک کلاس با ساختار CRUD ایجاد کنید بهتر است از دستور زیر استفاده کنید:

php artisan make:policy PostPolicy --model=Post

توجه داشته باشید: برای آشنایی با متدهای CRUD لطفا مقاله‌ی زیر را مطالعه بفرمایید:

ثبت و اعمال سیاست‌ها (Registering Policies)

اگر سیاستی وجود داشته باشد، طبیعتا باید به سیستم و نرم‌افزار شما اضافه شود. برای اضافه کردن هر سیاست برای هر مدل یا ویو باید ابتدا فایل AuthServiceProvider.php را از مسیر App\Providers باز کرده و سپس درون ویژگی policies مدل موردنظر را که سیاستی برای آن وضع کرده‌ایم، قرار دهیم:

<?php

namespace App\Providers;

use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    /**
     * Register any application authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        //
    }
}

همانطور که در مثال فوق مشاهده می‌کنید برای مدل Post یک سیاست ایجاد کرده و آن را به فایل AuthServiceProvider اضافه کرده‌ایم. ممکن است این سوال برای شما پیش بیاید که چرا در بخش قبلی (درگاه‌ها) یک سری دستور داخل متد boot نوشتیم و اکنون وجود ندارد؟ با نوشتن دستورهای درگاه‌ها داخل متد boot در AuthServiceProvider یک سیاست را وضع کردیم که بعدا در کنترلر با ترکیب متدهای allows یا denies استفاده کردیم. اما شما درنظر داشته باشید که برای هر مدل ده‌ها سیاست‌گذاری انجام دهید در نهایت این صفحه بسیار شلوغ شده و کدهای شما ناخوانا می‌شود بنابراین برای تمیز شدن کدها از یک Policy برای هر مدل استفاده می‌کنیم.

نوشتن سیاست‌ها (Writing Policies)

متدهای یک سیاست (Policy)

هر سیاستی که ثبت می‌شود شامل متدهایی برای هر اکشن درون کنترلر است که با استفاده از آن کنترل سطح دسترسی کاربران صورت می‌پذیرد. برای مثال، فرض کنید یک متد به نام update درون PostPolicy ایجاد می‌کنیم تا با استفاده از آن اجازه‌ی ویرایش پست‌های هر کاربر تنها برای خودش امکان‌پذیر باشد. درون این متد باید همواره کاربران و پست‌ها را از مدل موردنظر بازیابی کنیم. بنابراین داریم:

<?php

namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy
{
    /**
     * Determine if the given post can be updated by the user.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return bool
     */
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

همانطور که مشاهده می‌کنید یک سیاست به نام update ایجاد کردیم که متناسب با اطلاعات کاربران و پست‌هایشان مقدار true یا false را بر می‌گردانیم. در نظر داشته باشید که برای ایجاد متدهای دیگر نیز می‌توانید در ادامه‌ی همین فایل آنها را اضافه کنید. مثلا می‌توان سیاست‌هایی برای view یا delete ایجاد کرد. نکته‌ای که باید توجه داشته باشید این است: برای هر متد درون یک سیاست می‌توانید اسامی متفاوتی را انتخاب کنید.

توجه: در صورتیکه از دستور model-- برای ساخت یک سیاست در Artisan استفاده کنید همواره ۴ متد view, create, update و delete به صورت خودکار ایجاد خواهند شد.

متدهایی بدون مدل

برخی متدهای Policy فقط کاربری که در حال حاضر احراز هویت شده است را برای سطح دسترسی مورد ارزیابی قرار می‌دهند و نیازی به فراخوانی سایر مدل‌ها ندارند. فرض کنید برای اکشن create در کنترل خود باید یک قانون و سیاست بگذارید که به فرض مثال کاربر با سطح دسترسی مدیر می‌تواند فقط و فقط یک پست را ایجاد کند در این صورت باید تنها کاربری که احراز هویت شده است را مورد بررسی قرار دهید. به مثال زیر توجه کنید:

/**
 * Determine if the given user can create posts.
 *
 * @param  \App\User  $user
 * @return bool
 */
public function create(User $user)
{
    //
}

در مثال فوق تنها مدل User درگیر شده و مورد استفاده قرار می‌گیرد. در این حالت می‌توان سیاست‌ها را برای کاربرانی که مثلا سمت مدیریت دارند اعمال کرد.

فیلترهای یک سیاست (Policy Filters)

همواره سیاست‌گذاری‌های یک نرم‌افزار به گونه‌ایست که مدیر کل یا administrator یک وب سایت تمام سیاست‌ها را زیر پا گذاشته و وارد هر بخشی می‌شود و هر کاری که می‌خواهد می‌تواند انجام دهد! بنابراین برای صدور این سطح دسترسی یک متد به نام before در اختیار شما قرار می‌گیرد که قبل از هر متد دیگری در یک کلاس سیاست اجرا می‌شود و متناسب با آن می‌توان هر فعالیتی در وب سایت انجام داد:

public function before($user, $ability)
{
    if ($user->isSuperAdmin()) {
        return true;
    }
}

برای کنترل به تمام بخش‌ها همواره در این متد عبارت true بازگردانده خواهد شد و برای عدم دسترسی به تمام بخش می‌توان عبارت false را بازگرداند. اگر مقدار null توسط متد before بازگردانده شود، این متد سیاست‌گذاری را حذف می‌کند!

کنترل سطح دسترسی در لاراول با اعمال سیاست‌گذاری‌ها روی اکشن‌ها

کنترل و اعمال سیاست‌گذاری‌ها همواره به ۴ شیوه‌ی متفاوت ارائه می‌شود:

  • مدل User
  • میان‌افزار‌ یا Middleware
  • Helperهای کنترلر
  • موتور قالب Blade

مدل User

همواره درنظر داشته باشید که مدل User دو متد بسیار ارزشمند can و cant رو در اختیار شما قرار می‌دهد. متد can به کاربری که مطابق با سیاست وضع‌شده، سطح دسترسی آن تایید شود اجازه می‌دهد که یک اکشن را انجام دهد. آرگومان اول این متد نام متد سیاست است و آرگومان دوم آن معمولا نام درخواستی است که برای ویرایش یا ایجاد و یا حذف یک مدل مورد استفاده قرار می‌گیرد:

if ($user->can('update', $post)) {
    //
}

همانطور که در جریان هستید در صورتیکه یک سیاست را با استفاده از PostPolicy تعریف کرده باشید پردازش ها به صورت خودکار متناسب با آن سیاست (که درون فایل PostPolicy.php قرار دارد) انجام می‌شود و نتیجه به صورت true یا false نمایش داده خواهد شد و اگر سیاست را درون فایل AuthServiceProvider.php اعمال کرده باشید مجددا پردازش به صورت خودکار از طریق این فایل انجام می‌شود و نتیجه به صورت true یا false در اختیار قرار می‌گیرد.

اکشن‌هایی که نیازی به مدل ندارند:

به یاد دارید که در مطالب فوق ذکر کردیم برای اکشن‌هایی مانند create نیازی به سایر مدل‌ها نیست و اگر بخواهیم قوانین و سیاست‌ها را روی این اکشن‌ها اعمال کنیم همواره باید سیاست آن را نوشته و فقط با مدل User کار کنیم. حال در اینجا نیز می‌توان از متد can استفاده کرد و سیاست دلخواه را روی اکشن create اعمال نمود:

use App\Post;

if ($user->can('create', Post::class)) {
    // Executes the "create" method on the relevant policy...
}

توجه دارید که آرگومان دوم دیگر به عنوان یک متغییر از کلاس Post‌ نمی‌باشد بلکه نمونه‌ی خود کلاس Post::class است.


میان‌افزار

توجه دارید که لاراول راه های مختلفی را برای کنترل سطح دسترسی در لاراول در اختیار شما قرار می‌دهد. یکی از این راه‌ها استفاده از میان‌افزار است. وظیفه‌ی میان‌افزارها بگونه‌ای‌ست که وقتی روی یک مسیر اعمال می‌شوند قبل از اینکه درخواستی از طریق مسیر دریافت شود، فعال شده و قیودی را روی مسیر پیاده‌سازی می‌کنند. به صورت پیش‌فرض میان‌افزار Illuminate\Auth\Middleware\Authorize کلیدی به عنوان can را به کلاس کرنل App\Http\Class انتساب می‌دهد. به عبارت دیگر با استفاده از میان‌افزار can می‌توان کنترل سطح دسترسی برای یک مسیر را متناسب با سیاست وضع‌شده‌ی آن به صورت زیر انجام داد:

use App\Post;

Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
})->middleware('can:update,post');

در مثال فوق کاربری که مجاز به بروزرسانی پست است می‌تواند این کار را انجام دهد. در صورتیکه مجاز نباشد هرگز این صفحه را مشاهده نخواهد کرد.

در این مثال به میان‌افزار can دو آرگومان ارسال شده است. اولین آرگومان نام اکشنی‌ست که می‌خواهیم کنترل سطح دسترسی روی آن انجام شود و دومین آرگومان پارامتر مسیری‌ست که به متد سیاست ارسال می‌شود. درنظر دارید که اینجا از یک Implicit model binding استفاده کردیم. یعنی نام post را که در آرگومان اول put و متغییر تابع بازگشتی post$ یکسان است به عنوان ورودی به متد سیاست ارسال کرده‌ایم.

اکشن‌هایی که نیازی به مدل ندارند:

در صورتیکه بخواهیم برای این دسته از اکشن‌ها سیاست‌هایی را با استفاده از میان‌افزارها اعمال کنیم باید همواره نام کلاس را به عنوان آرگومان دوم به کلید can ارسال کنیم:

Route::post('/post', function () {
    // The current user may create posts...
})->middleware('can:create,App\Post');

Helperهای کنترلر

همانند متدهای مفیدی که توسط مدل User ایجاد شده است (can و cant)، یک هلپر یا تابع کمکی قدرتمند به نام authorize وجود دارد که در کنترلر‌ها برای تعیین سطح دسترسی در لاراول و وضع سیاست‌ها مورد استفاده قرار می‌گیرد. آرگومان‌های این متد دقیقا مشابه متد can می‌باشد که آرگومان اول شامل نام متد سیاست و آرگومان دوم شامل پارامترهای هر اکشن است. در صورتیکه کنترل سطح دسترسی با خطا مواجه شود یک پیغام از کلاس Illuminate\Auth\Access\AuthorizationException ارسال خواهد شد.

?php

namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PostController extends Controller
{
    /**
     * Update the given blog post.
     *
     * @param  Request  $request
     * @param  Post  $post
     * @return Response
     */
    public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post);

        // The current user can update the blog post...
    }
}

اکشن‌هایی که نیازی به مدل ندارند:

در صورتیکه بخواهیم برای این دسته از اکشن‌ها سیاست‌هایی را با استفاده از تابع کمکی authorize اعمال کنیم باید همواره نام کلاس را به عنوان آرگومان دوم به تابع کمکی authorize ارسال کنیم:

/**
 * Create a new blog post.
 *
 * @param  Request  $request
 * @return Response
 */
public function create(Request $request)
{
    $this->authorize('create', Post::class);

    // The current user can create blog posts...
}

موتور قالب Blade

هنگامیکه شما قالب وب سایت و نرم‌افزار تحت وب خود را طراحی می‌کنید می‌خواهید بخشی از این قالب و یا تگ‌ها برای یک سطح دسترسی خاص نمایش داده شود. فرض کنید می‌خواهید دکمه‌ی update post تنها برای یک کاربر خاص و یا یک کاربر با سطح دسترسی خاص نمایش داده شود. در این حالت می‌توانید از دستورها can@ و cannot@ به صورت زیر استفاده کنید:

@can('update', $post)
    <!-- The Current User Can Update The Post -->
@elsecan('create', $post)
    <!-- The Current User Can Create New Post -->
@endcan

@cannot('update', $post)
    <!-- The Current User Can't Update The Post -->
@elsecannot('create', $post)
    <!-- The Current User Can't Create New Post -->
@endcannot

دستورهایی که در بالا مشاهده کردید به عنوان میانبرهایی برای جلوگیری از نوشتن if@ و unless@ می‌باشد. بنابراین دستورهای فوق را می‌توان به صورت زیر هم نوشت:

@if (Auth::user()->can('update', $post))
    <!-- The Current User Can Update The Post -->
@endif

@unless (Auth::user()->can('update', $post))
    <!-- The Current User Can't Update The Post -->
@endunless

اکشن‌هایی که نیازی به مدل ندارند:

در صورتیکه بخواهیم برای این دسته از اکشن‌ها سیاست‌هایی را برای بخش view یا قالب وب سایت اعمال کنیم باید همواره نام کلاس را به عنوان آرگومان دوم به دستورهای can@ یا cannot@ ارسال کنیم:

@can('create', App\Post::class)
    <!-- The Current User Can Create Posts -->
@endcan

@cannot('create', App\Post::class)
    <!-- The Current User Can't Create Posts -->
@endcannot
تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری لاراول توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما (5 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

amini
26 مهر 1401
با سلام و خسته نباشید خدمت شما بابت آموزش مفید و آموزنده

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

mahdi
01 دی 1398
سلام با تشکر از زمانی که برای اموزش ها میزارید میخواستم بپرسم مرحله بعدی اموزش ها کی شروع میشه ؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مجید
05 بهمن 1396
سلام چرا این آموزش رو ادامه نمیدید ؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

محمد
02 شهریور 1396
سلام دستتون درد نکنه آموزشهاتون خیلی خوبه،الان 5 ماه بیشتره که ادامه نداده اید.قصد ندارید ادامه آموزش لاراول رو بزارید تو سایت ؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
04 شهریور 1396
سلام. حتما به زودی آموزش‌های جدید روی وب‌سایت قرار داده خواهند شد. ممنون از همراهیتون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مهدی
17 اردیبهشت 1396
سلام، خداقوت ممنون از مطالب آموزنده تون، من دنبال می کنم آموزش هاتون رو، می خواستم بپرسم ادامه نمیدید این قسمت رو؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
23 اردیبهشت 1396
سلام وقت شما بخیر دوست عزیز مطالب به صورت زنجیره وار ادامه دارند. مباحث پایه تا پیشرفته لاراول مدام و به صورت زمان بندی خاص بروزرسانی خواهند شد.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.