اذهب إلى المحتوى
  • 0

إنشاء نظام صلاحيات و مستخدمين في laravel

Mahmoud Alrashidi

السؤال

كيف يُمكن إنشاء مُستخدمين و تقسيمهم حسب صلاحيات مُحددة، مثلاً إنشاء مُستخدمين لهم دور مدير و مستخدمين لهم دور مٌحاسب في النظام مع تحديد صلاحيات المُحاسب و مُستخدمين عاديين ... الخ

رابط هذا التعليق
شارك على الشبكات الإجتماعية

Recommended Posts

  • 0

اللارافيل تسهل القيام بذلك من خلال امرين:

  1. البوابات او الgates : وهي عبارة عن بوابة تعطي المرور للمستخدمين المصرح لهم القيام بالواجبات المعينة. ويتم تعريف البوابات بداخل الدالة boot
    use App\Models\Post;
    use App\Models\User;
    use Illuminate\Support\Facades\Gate;
    
    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();
    
        Gate::define('update-post', function (User $user, Post $post) {
            return $user->id === $post->user_id;
        });
    }

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

    <?php
    
    namespace App\Http\Controllers;
    
    use App\Http\Controllers\Controller;
    use App\Models\Post;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Gate;
    
    class PostController extends Controller
    {
        /**
         * Update the given post.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \App\Models\Post  $post
         * @return \Illuminate\Http\Response
         */
        public function update(Request $request, Post $post)
        {
            if (! Gate::allows('update-post', $post)) {//هنا يتم التأكد باستخدام البوابة update-post من أن المستخدم مصرح له تحديث ال post 
                abort(403);//يتم اجهاض العملية مع الخطأ 403 اي أن المستخدم غير مصرح للقيام بالعملية
            }
    
            // متابعة الكود وتحديث المنشور
        }
    }

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

  2.  : او السياسات  policies  من خلال ال 

    php artisan make:policy PostPolicy --model=Post

      خاصة بالموديل بوست حتكون موجودة في المسار policy الامر ده من خلاله سيتم إنشاء

    app/Policies

     بعدها ستقوم بتسجيل السياسة التي قمت بعملها في المسار

App\Providers\AuthServiceProvider

والتسجيل بمثابة اخبار للارافيل بالسياسات التي ستستخدمها في البرنامج.

<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

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

في الكود السابق يتم التحقق من أن المستخدم مصرح له أن يقوم بعملية التحديث.

وتقدر تستخدم الpolicy بواحدة من طريقتين:

  1. اما عن طريق الblade : 
    
    @can('update', $post)
        <!-- ...بتقوم بعرض جزئيات من التصميم للمستخدم في حال انه كان يمكنه عمل تحديث للبوست -->
    @elsecan('create', App\Models\Post::class)
        <!-- ...بتقوم بعرض جزئيات من التصميم للمستخدم في حال انه كان يمكنه عمل انشاء لبوست جديد-->
    @endcan
    
    @cannot('update', $post)
        <!-- ...تقوم بعرض جزئيات من التصميم للمستخدم في حال انه كان لا يمكنه عمل تحديث للبوست -->
    @elsecannot('create', App\Models\Post::class)
        <!-- ...بتقوم بعرض جزئيات من التصميم للمستخدم في حال انه كان لا يمكنه عمل انشاء لبوست جديد-->
    @endcannot

     

  2. عن طريق الربط مع الcontroller:

    <?php
    
    namespace App\Http\Controllers;
    
    use App\Http\Controllers\Controller;
    use App\Models\Post;
    use Illuminate\Http\Request;
    
    class PostController extends Controller
    {
        /**
         * Update the given post.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \App\Models\Post  $post
         * @return \Illuminate\Http\Response
         */
        public function update(Request $request, Post $post)
        {
            if ($request->user()->cannot('update', $post)) {
                abort(403);
            }
    
            // Update the post...
        }
    }

     

رابط هذا التعليق
شارك على الشبكات الإجتماعية

  • 0

في البداية يجب عليك تجهيز بعض الجداول للقيام بذلك, فستحتاج على الأقل إلى جدول للأدوار وجدول للصلاحيات وجدول صلاحيات الدور. كل واحد منها يحمل الاسم الذي سيتعامل معه النظام, كالتالي:

 Schema::create('permissions', function (Blueprint $table) {
  $table->bigIncrements('id');
  $table->string('name');
  $table->timestamps();
});

هذا للصلاحيات وكذلك للأدوار كالتالي:

 Schema::create('roles', function (Blueprint $table) {
  $table->bigIncrements('id');
  $table->string('name');
  $table->timestamps();
});

أما لجدول صلاحيات الدور فسيحتوي على id لكل دور يقابله الصلاحيات المعطاه له كالتالي:

 Schema::create('role-permissions', function (Blueprint $table) {
  $table->bigIncrements('id');
  $table->unsignedBigInteger('role_id');
  $table->unsignedBigInteger('permission_id');
  $table->timestamps();
  $table->foreign('role_id')->references('id')->on('roles');
  $table->foreign('permission_id')->references('id')->on('permissions');
});

حيث أن هذا الجدول مخصص للعلاقة وربط بين الجدولين. بالطبع سنحتاج إلى Models لل Role و Permission حتى نستطيع التعامل مع الجداول سأفترض معرفتك بهذا الأمر ولكن سأوضح العلاقة الموجودة والتي هي واحد لمجموعة 1:m وممثلة كالتالي في Permission model:

public function role(): 
{
  return $this->belongsTo(Role::class);
}

وفي Role model: 

public function permission(): 
{
  return $this->hasMany(Permission::class);
}

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

ولكن نريد تطبيق ذلك في المشروع لدينا وهذا يتم عبر ال Middlewate غالباً لكن يمكن فحصه في controller بداخل ال constructor الخاص بهذا ال controller ولكن دعنا نقول أننا نريد التعامل باستخدام Middleware فما علينا فعله هو أن نتأكد بداخله إن كان هذا المستخدم لديه الصلاحيات لدخول إلى صفحة معينة أو القيام بأمر معين.

وعلى المستوي الشخصي أستخدم مكتبة جاهزة توفر الوقت علي بهذا الأمر وتقوم به بطريقة ممتازة واسم هذه المكتبة Laravel-permission فكل ما عليك فعله هو تتبع خطوات التنزيل الموضحة ومن ثم تعلم استخدام المكتبة.

رابط هذا التعليق
شارك على الشبكات الإجتماعية

  • 0

هناك عدة حزم مُعظم المُطورون يستخدمون إحداها لعمل هذه الجزئية في مشروعهم و من هذه الحزم أذكر:

  1. Laravel-permission التي تم تطويرها من طرف spatie و هم معروفين بتطوير عدة حزم مشهورة و مُستخدمة و يُطورون عليها بإستمرار.
  2. bouncer من تطوير Joseph Silber
  3. Laratrust

هذه الحزم أكثر إستخداماً في هذا الموضوع و هي تُحدث بإستمرار حتى تدعم الإصدارات الجديدة من إطار laravel.

إن كنت تريد إنجاز هذا الأمر فستتعب قليلاً و هذه الخطوات يُمكنك تطبيقها للوصول إلى ما تريد:

  • إنشاء مودل لكل من Role و Permission و ملف تهجير لكل منهما:
php artisan make:model Permission -m
php artisan make:model Role -m

كما تعلم فتمرير -m يعني أننا نريد إنشاء ملف تهجير أيضاً.

  • تعديل ملفات التهجير:

ملف تهجير الصلاحيات:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePermissionsTable extends Migration
{
    
    public function up()
    {
        Schema::create('permissions', function (Blueprint $table) {
            $table->id();
            $table->string('name'); // edit posts
            $table->string('slug'); //edit-posts
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('permissions');
    }
}

ملف تهجير الأدوار:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateRolesTable extends Migration
{
    public function up()
    {
        Schema::create('roles', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('roles');
    }
}
  • إنشاء ملفات تهجير الجداول الإضافية لربط الصلاحيات بالمستخدمين و ربط الصلاحيات بالأدوار و ربط الأدوار بالمُستخدمين:
php artisan make:migration create_users_permissions_table --create=users_permissions
php artisan make:migration create_users_roles_table --create=users_roles
php artisan make:migration create_roles_permissions_table --create=roles_permissions
  • و في ما يلي مُحتوى ملفات التهجير التي أنشأناها:

جدول users_permissions:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersPermissionsTable extends Migration
{
    public function up()
    {
        Schema::create('users_permissions', function (Blueprint $table) {
            $table->unsignedBigInteger('user_id');
            $table->unsignedBigInteger('permission_id');

            //FOREIGN KEY CONSTRAINTS
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
 
            //SETTING THE PRIMARY KEYS
            $table->primary(['user_id','permission_id']);
        });
    }

    public function down()
    {
        Schema::dropIfExists('users_permissions');
    }
}

جدول users_roles:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersRolesTable extends Migration
{
    public function up()
    {
        Schema::create('users_roles', function (Blueprint $table) {
            $table->unsignedBigInteger('user_id');
            $table->unsignedBigInteger('role_id');

         //FOREIGN KEY CONSTRAINTS
           $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
           $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');

         //SETTING THE PRIMARY KEYS
           $table->primary(['user_id','role_id']);
        });
    }

    public function down()
    {
        Schema::dropIfExists('users_roles');
    }
}

جدول roles_permissions:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateRolesPermissionsTable extends Migration
{
    public function up()
    {
        Schema::create('roles_permissions', function (Blueprint $table) {
             $table->unsignedBigInteger('role_id');
             $table->unsignedBigInteger('permission_id');

             //FOREIGN KEY CONSTRAINTS
             $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
             $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');

             //SETTING THE PRIMARY KEYS
             $table->primary(['role_id','permission_id']);
        });
    }

    public function down()
    {
        Schema::dropIfExists('roles_permissions');
    }
}
  • نقوم بعملية التهجير:
php artisan migrate
  • إنشاء العلاقات بين النماذج

علاقات النموذج Role:

public function permissions() {

   return $this->belongsToMany(Permission::class,'roles_permissions');
       
}

public function users() {

   return $this->belongsToMany(User::class,'users_roles');
       
}

علاقات النموذج Permission:

public function roles() {

   return $this->belongsToMany(Role::class,'roles_permissions');
       
}

public function users() {

   return $this->belongsToMany(User::class,'users_permissions');
       
}
  • إنشاء Trait لإستخدامه في النموذج User، في مٌجلد app نقوم بإنشاء مُجلد Permissions و نضع بداخله ملف HasPermissionsTrait.php ثم نقوم بتضمينه داخل النموذج User:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Permissions\HasPermissionsTrait;

class User extends Authenticatable
{
    use HasFactory, Notifiable, HasPermissionsTrait;

}

يكون مُحتوى الملف HasPermissionsTrait بالشكل التالي:

<?php

namespace App\Permissions;

use App\Models\Permission;
use App\Models\Role;

trait HasPermissionsTrait {

   public function givePermissionsTo(... $permissions) {

    $permissions = $this->getAllPermissions($permissions);
    if($permissions === null) {
      return $this;
    }
    $this->permissions()->saveMany($permissions);
    return $this;
  }

  public function withdrawPermissionsTo( ... $permissions ) {

    $permissions = $this->getAllPermissions($permissions);
    $this->permissions()->detach($permissions);
    return $this;

  }

  public function refreshPermissions( ... $permissions ) {

    $this->permissions()->detach();
    return $this->givePermissionsTo($permissions);
  }

  public function hasPermissionTo($permission) {

    return $this->hasPermissionThroughRole($permission) || $this->hasPermission($permission);
  }

  public function hasPermissionThroughRole($permission) {

    foreach ($permission->roles as $role){
      if($this->roles->contains($role)) {
        return true;
      }
    }
    return false;
  }

  public function hasRole( ... $roles ) {

    foreach ($roles as $role) {
      if ($this->roles->contains('slug', $role)) {
        return true;
      }
    }
    return false;
  }

  public function roles() {

    return $this->belongsToMany(Role::class,'users_roles');

  }
  public function permissions() {

    return $this->belongsToMany(Permission::class,'users_permissions');

  }
  protected function hasPermission($permission) {

    return (bool) $this->permissions->where('slug', $permission->slug)->count();
  }

  protected function getAllPermissions(array $permissions) {

    return Permission::whereIn('slug',$permissions)->get();
    
  }

}
  • إنشاء مُزود خدمة PermissionsServiceProvider:
php artisan make:provider PermissionsServiceProvider

نقوم بإضافة blade directive فيه و تعريف بوابة Gate من أجل كل صلاحية في الدالة boot:

<?php

namespace App\Providers;

use App\Models\Permission;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;

class PermissionsServiceProvider extends ServiceProvider
{
   
    public function register()
    {
        //
    }

    public function boot()
    {
        try {
            Permission::get()->map(function ($permission) {
                Gate::define($permission->slug, function ($user) use ($permission) {
                    return $user->hasPermissionTo($permission);
                });
            });
        } catch (\Exception $e) {
            report($e);
            return false;
        }

        //Blade directives
        Blade::directive('role', function ($role) {
             return "if(auth()->check() && auth()->user()->hasRole({$role})) :"; //return this if statement inside php tag
        });

        Blade::directive('endrole', function ($role) {
             return "endif;"; //return this endif statement inside php tag
        });

    }
}

بعد ذلك نقوم بتسجيل مُزود الخدمة في مصفوفة providers في الملف config\app.php:

'providers' => [

	App\Providers\PermissionsServiceProvider::class,

],
  • الآن تحتاج إلا إضافة بعض البيانات الإختبارية ملف بذر لكل من الصلاحيات و الأدوار و تضع فيه كل صلاحيات الموقع و بعض الأدوار التي تريد أن تكون ضمن تطبيقك و تربط الصلاحيات بالأدوار ثم تربط الأدوار بالمُستخدمين.
<?php

$admin_permission = Permission::where('slug','create-users')->first();
$manager_permission = Permission::where('slug', 'edit-users')->first();

//RoleTableSeeder.php
$admin_role = new Role();
$admin_role->slug = 'admin';
$admin_role->name = 'Admin Role';
$admin_role->save();
$admin_role->permissions()->attach($admin_permission);

$manager_role = new Role();
$manager_role->slug = 'manager';
$manager_role->name = 'Assistant Manager';
$manager_role->save();
$manager_role->permissions()->attach($manager_permission);

$admin_role = Role::where('slug','admin')->first();
$manager_role = Role::where('slug', 'manager')->first();

$createUsers = new Permission();
$createUsers->slug = 'create-users';
$createUsers->name = 'Create Users';
$createUsers->save();
$createUsers->roles()->attach($admin_role);

$editUsers = new Permission();
$editUsers->slug = 'edit-users';
$editUsers->name = 'Edit Users';
$editUsers->save();
$editUsers->roles()->attach($manager_role);

$admin_role = Role::where('slug','admin')->first();
$manager_role = Role::where('slug', 'manager')->first();
$admin_perm = Permission::where('slug','create-users')->first();
$manager_perm = Permission::where('slug','edit-users')->first();

$admin = new User();
$admin->name = 'Samir Abboud';
$admin->email = 'samir@gmail.com';
$admin->password = bcrypt('password');
$admin->save();
$admin->roles()->attach($admin_role);
$admin->permissions()->attach($admin_perm);

$manager = new User();
$manager->name = 'Kamel Mahmoudi';
$manager->email = 'kamel@gmail.com';
$manager->password = bcrypt('password');
$manager->save();
$manager->roles()->attach($manager_role);
$manager->permissions()->attach($manager_perm);

يُمكنك إستخدام tinker لعمل test:

$admin = User::where('email', 'samir@gmail.com')->first();
$admin->hasRole('admin'); // لفحص إن كان المُستخدم يملك الدور مدير
$admin->givePermissionsTo('edit-users') // إعطاء المُستخدم صلاحية تعديل مُستخدمين
$admin->can('create-users') // فحص المُستخدم إذا كان لديه صلاحية إنشاء مُستخدم

في صفحات العرض يُمكن إستخدام التوجيه الذي أنشأناه:

@role('admin')

 مرحبا مدير

@endrole
  • الخطوة الأخيرة هي إنشاء middleware لعدم السماح للمُستخدم الوصول إلى مسار مُحدد إذا لم يكن يملك الصلاحية:
php artisan make:middleware RoleMiddleware
php artisan make:middleware PermissionMiddleware
<?php

namespace App\Http\Middleware;

use Closure;

class RoleMiddleware
{

    public function handle($request, Closure $next, $role)
    {
      	if (!auth()->check()) {
            return redirect()->route('login');
        }

      
        if(!auth()->user()->hasRole($role)) {

             abort(403);

        }

        return $next($request);

    }
}
<?php

namespace App\Http\Middleware;

use Closure;

class PermissionMiddleware
{

    public function handle($request, Closure $next, $permission)
    {
      	if (!auth()->check()) {
            return redirect()->route('login');
        }

      
        if(!auth()->user()->can($permission)) {

             abort(403);

        }

        return $next($request);

    }
}

بعد ذلك تقوم بتسجيل هاتين الmiddleware ضمن App\Http\Kernel.php في مصفوفة routeMiddleware:

<?php

protected $routeMiddleware = [
  .
  .
  'role' => \App\Http\Middleware\RoleMiddleware::class,
  'permission' => \App\Http\Middleware\PermissionMiddleware::class,
];

الآن يتبقى فقط الإستخدام في ملف المسارات:

<?php

Route::group(['middleware' => 'role:admin'], function() {

	// مسارات المُستخدم الذي يملك الدور مدير

});

أو من خلال باني المُتحكم:

public function __construct()
{
   $this->middleware('permission:create-users')->only(['create', 'store']);
}

إن كنت ستستخدم حزمة جاهزة فستجد كل هذه الخصائص موجودة فيها و ستجدها قد تم إختبارها بإستعمال الإختبارات الأحادية أيضاً.

رابط هذا التعليق
شارك على الشبكات الإجتماعية

انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أجب على هذا السؤال...

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.

  • إعلانات

  • تابعنا على



×
×
  • أضف...