الترخيص (Authorization) في Laravel

من موسوعة حسوب

مقدمة

بالإضافة إلى توفير خدمات الاستيثاق المضمنة بإطار العمل Laravel، يوفر Laravel أيضًا طريقة بسيطة لترخيص عمليات المستخدم على مورد معين. مثلما هو الحال مع الاستيثاق، فإن أسلوب Laravel للترخيص بسيط، وهناك طريقتان أساسيتان لترخيص العمليات: البوابات والسياسات (gates and policies).

فكر في البوابات والسياسات مثل المسارات ووحدات التحكم. توفر البوابات أسلوبًا بسيطًا يعتمد على الإغلاق للترخيص، بينما تقوم السياسات، مثل وحدات التحكم، بتجميع منطقها حول نموذج معين أو مورد. سنستكشف البوابات أولاً ثم نفحص السياسات.

لست بحاجة إلى الاختيار بين استخدام البوابات فقط أو استخدام السياسات فقط عند إنشاء تطبيق. على الأغلب تحتوي معظم التطبيقات على مزيج من البوابات والسياسات، وهذا مثالي تمامًا! تكون البوابات أكثر قابلية للتطبيق على العمليات التي لا تتعلق بأي نموذج أو مورد، مثل عرض لوحة تحكم المستخدمين ذوي الصلاحيّات الإداريّة. في المقابل، يجب أن تستخدم السياسات عندما ترغب في ترخيص عملية لنموذج أو مورد معين.

البوابات

كتابة البوابات

البوابات عبارة عن نطاقات مغلقة تحدد ما إذا كان المستخدم مرخصًا له لتنفيذ عملية معينة وتُعرَّف عادةً في صنف App\Providers\AuthServiceProvider باستخدام واجهة Gate. تتلقى البوابات دائمًا نسخة مستخدم كمعامل أول، وقد يتلقى اختياريًا مزيدًا من المعاملات مثل نموذج Eloquent ذي الصلة:

/**
* تسجيل كل خدمات الاستيثاق/الترخيص
*
* @return void
*/

public function boot()
{

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

   });
}

قد يتم أيضًا تعريف البوابات باستخدام أسلوب سلسلة نصية لردّ النداء Class@method، مثل وحدات التحكم:

/**
* تسجيل كل خدمات الاستيثاق/الترخيص.
*
* @return void
*/

public function boot()

{
   $this->registerPolicies();
   Gate::define('update-post', 'PostPolicy@update');
}

بوابات الموارد

يمكنك أيضًا تحديد العديد من قدرات البوابة في وقت واحد باستخدام التابع resource:

Gate::resource('posts', 'App\Policies\PostPolicy');

هذا مطابق للتعريف يدويًا بتعريفات البوابة التالية:

Gate::define('posts.view', 'App\Policies\PostPolicy@view');
Gate::define('posts.create', 'App\Policies\PostPolicy@create');
Gate::define('posts.update', 'App\Policies\PostPolicy@update');
Gate::define('posts.delete', 'App\Policies\PostPolicy@delete');

ستعرف قدرات view، و create، و update، و delete تلقائيًا. يمكنك إعادة تعريف القدرات الافتراضية بتمرير مصفوفة كمعامل ثالث للتابع resource. تحدد مفاتيح المصفوفة أسماء القدرات بينما تحدد القيم أسماء التوابع. على سبيل المثال، ستُنشِئ التعليمة البرمجية التالية فقط تعريفين جديدين للبوابة - posts.image و posts.photo:

Gate::resource('posts', 'PostPolicy', [

   'image' => 'updateImage',
   'photo' => 'updatePhoto',

]);

تفويض العمليات

لترخيص عملية باستخدام البوابات، استخدم التابعين allows أو denies. لاحظ أنه لا يلزمك تمرير مستخدم الاستيثاق الحالي إلى هذين التابعين. سيُمرِّر Laravel المستخدم تلقائيًا إلى بوابة الإغلاق:

if (Gate::allows('update-post', $post)) {
  // المستخدم الحالي يستطيع تحديث المنشور...

}

if (Gate::denies('update-post', $post)) {
  // المستخدم الحالي لا يستطيع تحديث المنشور ...

}

إذا كنت ترغب في تحديد ما إذا كان هناك مستخدم معين مرخص له تنفيذ عملية ما، فيمكنك استخدام تابع forUser على واجهة البوابة:

if (Gate::forUser($user)->allows('update-post', $post)) {
  // المستخدم يستطيع تحديث المنشور post...

}

if (Gate::forUser($user)->denies('update-post', $post)) {
  // المستخدم لا يستطيع تحديث المنشور post...

}

اعتراض بوابة التحقيقات

في بعض الأحيان، قد ترغب في منح جميع القدرات لمستخدم معين. يمكن استخدام التابع before لتعريف رد النداء الذي شغل قبل جميع عمليات التحقق من الترخيص الأخرى:

Gate::before(function ($user, $ability) {

   if ($user->isSuperAdmin()) {
       return true;
   }

});

إذا كان رد نداء التابع before يعيد نتيجة غير معدومة، سيتم اعتبار هذه النتيجة هي نتيجة التحقق.

يجوز لك استخدام التابع after لتعريف رد النداء الذي يتم تشغيله بعد كل عملية تحقق من الترخيص:

ومع ذلك، لا يجوز لك تعديل نتيجة التحقق من الترخيص عبر رد نداء التابع after:

Gate::after(function ($user, $ability, $result, $arguments) {

  //

});

إنشاء السياسات

توليد السياسات

السياسات هي تصنيفات تنظم منطق الترخيص حول نموذج أو مورد معين. على سبيل المثال، إذا كان تطبيقك عبارة عن مدونة، فقد يكون لديك نموذج منشور Post وسياسة PostPolicy مقابلة لترخيص عمليات المستخدم مثل إنشاء أو تحديث المنشورات.

يمكنك إنشاء سياسة باستخدام الأمر make:policy. سيتم وضع السياسة التي تم إنشاؤها في المجلّد app/Policies. إذا كان هذا المجلد غير موجود في تطبيقك، سيقوم Laravel بإنشائه لك:

php artisan make:policy PostPolicy

سينشئ الأمر make:policy صنف سياسة فارغ. إذا كنت ترغب في إنشاء صنف يتضمن توابع السياسة الخاصة بالـ "CRUD"، فيمكنك تحديد ‎--model عند تنفيذ الأمر:

php artisan make:policy PostPolicy --model=Post

ملاحظة: يتم حل جميع السياسات عبر حاوي خدمات Laravel، مما يسمح لك بالتلميح عن نوع (type-hint) أيّة اعتماديّات ضرورية في التابع الباني للسياسة لحقنها تلقائيًا.

تسجيل السياسات

بمجرد وجود السياسة، يجب تسجيلها. يحتوي حاوي الخدمات AuthServiceProvider المضمن مع تطبيقات Laravel الجديدة على خاصية policies التي تربط نماذج Eloquent الخاصة بك مع سياساتها المقابلة. يخبر تسجيل سياسة Laravel بأي سياسة يستخدمها عند ترخيص العمليات على نموذج محدد:

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

{

   /**
    * تعيينات السياسة الخاصة بالتطبيق.
    *
    * @متغيير مصفوفة
    */

   protected $policies = [

       Post::class => PostPolicy::class,
   ];

   /**
    * سجل أيّ خدمات استيثاق / ترخيص خاصة بالتطبيق
    *
    * @أرجع فراغ
    */

   public function boot()

   {
       $this->registerPolicies();

      //
   }
}?>

كتابة السياسات

توابع السياسة

بمجرد تسجيل السياسة، يمكنك إضافة توابع لكل عملية ترخصها. على سبيل المثال، دعنا نحدد تابع تحديث على سياسة PostPolicy الخاصة بنا، والتي تحدد ما إذا كان مستخدم معين يمكنه تحديث نسخة منشور معين.

يتلقى تابع التحديث النسختين المستخدم والمنشور كمعاملات، ويرجع القيمة true أو false والتي تشير إلى ما إذا كان المستخدم مرخصًا له بتحديث المنشور أم لا. لذلك، في هذا المثال، دعنا نتحقق من أن معرف المستخدم يطابق قيمة user_id في المنشور:

<?php

namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy

{

   /**
    * تحديد هل بإمكان المستخدم تحديث المنشور المعطى.
    *
    * @param  \App\User $user
    * @param  \App\Post $post
    * @return bool
    */

   public function update(User $user, Post $post)

   {
       return $user->id === $post->user_id;
   }
}
?>

يمكنك الاستمرار في تعريف توابع إضافية للسياسة حسب الحاجة للعمليات المختلفة التي تأذن بها. على سبيل المثال، قد تقوم بتعريف توابع view أو delete لترخيص مختلف عمليات النشر، ولكن تذكر أنك حر في إعطاء أي اسم تريده لتوابع السياسة الخاصة بك.

ملاحظة: إذا استخدمت خاصية ‎--model عند إنشاء سياستك عبر وحدة التحكم Artisan، فستحتوي بالفعل على طرق للعرض وإنشاء وتحديث وحذف العمليات.

توابع بدون نماذج

لا تتلقى بعض توابع السياسة سوى المستخدم المسجّل دخول حاليًا وليس نسخة عن النموذج المرخص له. هذا الموقف أكثر شيوعًا عند ترخيص عمليات الإنشاء. على سبيل المثال، إذا كنت تقوم بإنشاء مدونة، فقد ترغب في التحقق مما إذا كان المستخدم مرخّصًا له بإنشاء أية منشورات على الإطلاق.

عند إنشاء توابع السياسة التي لن تتلقى نسخة عن النموذج، مثل تابع الإنشاء create، يجب إنشاء الدالة على أنها تتوقع المستخدم المسجّل دخول فقط:

/**
* تحديد ما إذا كان المستخدم المُعطى يمكنه إنشاء منشورات … 
*
* @param  \App\User $user
* @return bool
*/

public function create(User $user)

{
  //
}

مرشّحو السياسة

قد ترغب في ترخيص جميع العمليات ضمن سياسة معينة بالنسبة إلى بعض المستخدمين. لإنجاز هذا، عرف التابع before في السياسة. سينفذ التابع before قبل أي تابع آخر في السياسة، مما يتيح لك الفرصة لترخيص العملية قبل أن يتم استدعاء تابع السياسة المطلوب. تستخدم هذه الميزة بشكل شائع لترخيص مدراء التطبيق لتنفيذ أي عملية:

public function before($user, $ability)

{
   if ($user->isSuperAdmin()) {
       return true;
   }
}

إذا كنت ترغب في رفض جميع الترخيصات للمستخدم، فيجب عليك إرجاع القيمة FALSE من التابع before. إذا رجعت القيمة null، فسيخضع الترخيص إلى طريقة السياسة.

تحذير: لن يستدعى التابع before الخاص بصنف السياسة إذا لم يحتوي الصنف على تابع ذي اسم مطابق لاسم القدرة التي يتم التحقق منها.

ترخيص الإجراءات باستخدام السياسات

عبر نموذج المستخدم

يتضمن نموذج المستخدم المتضمن مع تطبيق Laravel تابعين مفيدين لترخيص العمليات: can و cant. يتلقى تابع can العملية التي ترغب في ترخيصها والنموذج ذا الصلة. على سبيل المثال ، دعنا نحدد ما إذا كان المستخدم مخولًا بتحديث نموذج منشور معين:

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

  //

}

إذا سجلت سياسة للنموذج المحدد، فسيتصل التابع can تلقائيًا بالسياسة المناسبة وإرجاع النتيجة المنطقية. إذا لم تكن هناك سياسة مسجلة للنموذج، فسيحاول التابع can استدعاء النطاق المغلق للبوابة التي تطابق اسم العملية المحددة.

العمليات التي لا تتطلب نماذج

تذكر أن بعض العمليات مثل الإنشاء قد لا تتطلب نسخة عن النموذج. في هذه الحالات، يمكنك تمرير اسم الصنف إلى التابع can. سيستخدم اسم الصنف لتحديد السياسة التي ستستخدم عند ترخيص العملية:

use App\Post;

if ($user->can('create', Post::class)) {
  // ينفذ التابع create على السياسات ذات الصلة 

}

عبر وسيط برمجي

يتضمن Laravel وسيطًا برمجيًا يمكنه ترخيص العمليات قبل أن يصل الاستعلام الوارد حتى إلى المسارات أو وحدات التحكم. بشكل افتراضي، يعين الوسيط Illuminate\Auth\Middleware\Authorize مفتاح التابع can في صنف App\Http\Kernel. دعنا نستكشف مثالًا على استخدام الوسيط البرمجي can لترخيص المستخدم لتحديث منشور المدونة:

use App\Post;

Route::put('/post/{post}', function (Post $post) {
  // المستخدم الحالي يمكنه تحديث المنشور...

})->middleware('can:update,post');

في هذا المثال، سنمرر معاملين للوسيط البرمجي can. الأول هو اسم العملية التي نرغب في ترخيصها، والثاني هو معامل المسار الذي نرغب في تمريره إلى تابع السياسة. في هذه الحالة، بما أننا نستخدم نموذج الربط الضمني (implicit model binding)، فسيمرر نموذج post إلى تابع السياسة. إذا لم يكن المستخدم مرخصا لتنفيذ العملية المحدد، سينشئ الوسيط البرمجي استجابة HTTP برمز الحالة 403.

العمليات التي لا تتطلب نماذج

مرة أخرى، قد لا تتطلب بعض العمليات مثل الإنشاء نسخة عن النموذج. في هذه الحالات، يمكنك تمرير اسم الصنف إلى الوسيط البرمجي. سيستخدم اسم الصنف لتحديد السياسة التي ستستخدم عند ترخيص العملية :

Route::post('/post', function () {

  // المستخدم الحالي يمكنه كتابة منشورات...

})->middleware('can:create,App\Post');

عبر مساعدي وحدة التحكم

بالإضافة إلى التوابع المفيدة المقدمة لنموذج المستخدم، يوفر Laravel تابع ترخيص مفيد لأي من وحدات التحكم الخاصة بك والتي تعمل على توسيع الصنف الأساسي App\Http\Controllers\Controller. مثل تابع can، يقبل هذا التابع اسم العملية التي ترغب بالترخيص لها والنموذج ذي الصلة. إذا لم يتم الترخيص بهذه العملية، فسيقوم تابع الترخيص برمي الاستثناء Illuminate\Auth\Access\AuthorizationException، والذي سيحوله معالج الاستثناءات الافتراضي الخاص بـ Laravel إلى استجابة HTTP برمز الحالة 403:

namespace App\Http\Controllers;

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

class PostController extends Controller

{
   /**
    *تحديث منشور المدونة المُعطى.
    *
    * @param  Request $request
    * @param  Post $post
    * @return Response
    * @throws \Illuminate\Auth\Access\AuthorizationException
    */

   public function update(Request $request, Post $post)
   {
       $this->authorize('update', $post);
      // المستخدم الحالي يمكنه تحديث منشورات للمدونة...
   }
}

العمليات التي لا تتطلب نماذج

كما تمت مناقشته سابقًا، قد لا تتطلب بعض العمليات مثل الإنشاء نسخة عن النموذج. في هذه الحالات، يمكنك تمرير اسم الصنف للتابع authorize. سيستخدم اسم الصنف لتحديد السياسة التي ستستخدم عند ترخيص العملية :

/**
* إنشاء منشور مدونة جديد.
*
* @param  Request $request
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/

public function create(Request $request)
{
   $this->authorize('create', Post::class);
  // المستخدم الحالي يمكنه انشاء منشورات للمدونة ...
}

عبر قالب Blade

عند كتابة قوالب Blade، قد ترغب في عرض جزء من الصفحة فقط إذا كان المستخدم مرخّصًا لتنفيذ عملية ما. على سبيل المثال، قد ترغب في عرض نموذج محدث من منشور مدونة فقط إذا كان بإمكان المستخدم تحديث المنشور فعليًا. في هذه الحالة، يمكنك استخدام مجموعة الموجّهات can وcannot:

@can('update', $post)
    <!-- المستخدم الحالي يمكنه تحديث المنشور -->
@elsecan('create', App\Post::class)
    <!--المستخدم الحالي يمكنه إنشاء منشور جديد -->
@endcan

@cannot('update', $post)
    <!-- المستخدم الحالي لا يمكنه تحديث المنشور -->
@elsecannot('create', App\Post::class)
    <!- المستخدم الحالي لا يمكنه إنشاء منشور جديد-->
@endcannot

هذه الموجّهات هي اختصارات ملائمة لكتابة ‎@if وعبارات ‎@unless. إن عبارات can وcannot أعلاه تترجم إلى العبارات التالية بالترتيب:

@if (Auth::user()->can('update', $post))
    <!-- المستخدم الحالي يمكنه تحديث المنشور -->
@endif

@unless (Auth::user()->can('update', $post))
    <!-- المستخدم الحالي لا يمكنه تحديث المنشور -->
@endunless

العمليات التي لا تتطلب نماذج

مثل معظم دوال الترخيص الأخرى، يمكن تمرير اسم الصنف إلى توجيهات ‎@can  و ‎@cannot إذا كانت العملية لا تتطلب نسخة عن النموذج:

@can('create', App\Post::class)
    <!-- المستخدم الحالي يمكنه انشاء منشورات -->
@endcan

@cannot('create', App\Post::class)
    <!-- المستخدم الحالي لا يمكنه انشاء منشورات -->
@endcannot

مصادر