إطار العمل Yii2: الأدوار والصلاحيات

إتمام بناء نظام المستخدمين عن طريق إضافة الأدوار والصلاحيات والتحكّم بها وإسنادها للمستخدمين

عدنان توتنيمنذ 4 سنوات

تحدّثنا في المقال السّابق عن بناء نظام المستخدمين، وقمنا بتعريف عمليات تسجيل حساب جديد وتسجيل الدخول والخروج.

ولكنّ ذلك لا يكفي لإتمام بناء نظام المستخدمين في حال كان المستخدمون في النّظام متفاوتين في مهامهم وصلاحياتهم، فربما يكون أحدهم مستخدماً عادياً لا يحق له الاطّلاع إلا على بعض الصفحات أو تنفيذ أوامر محددة، بينما هنالك مستخدمٌ آخر برتبة مشرفٍ يحقّ له تنفيذ أوامر أعلى، وهنالك المسؤول الذي له القدرة على التحكّم في كلِّ شيءٍ والاطّلاع على كلِّ شيء.

من أجل ذلك فلا بدّ للنظام أن يحوي آليةً للأدوار والصلاحيات الخاصّة بالمستخدمين كي يتمّ تخصيص الوصول إلى  مختلف الصفحات والأوامر تبعاً لدور المستخدم وصلاحياته.

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

 

تُعرف هذه الآلية بـ RBAC أي Role-Based Access Control، والمقصود بالـ Access Control أي أن تتحكّم بإمكانية وصول مستخدمٍ يحمل صلاحيةً أو دوراً معيناً إلى صفحةٍ أو إلى تنفيذ أمرٍ في النّظام.

وآلية RBAC التي سنعتمد عليها هي DB Manager؛ أي التحكّم عن طريق قواعد البيانات، فهناك طريقةٌ أخرى أقلّ استخداماً تعتمد على الملفات المكتوبة بلغة PHP.

توفّر الـ Yii2 آليةً للتعامل مع الأدوار والصلاحيات من خلال جداول تقوم بإضافتها إلى قاعدة البيانات الخاصّة بك، هذه الجداول تحوي الأدوار والصلاحيات وإسناد هذه الأدوار والصلاحيات للمستخدمين.

في الخطوة الأولى سنقوم بتغيير الإعدادات الخاصة بالتطبيق من أجل إعلامه أنّنا نريد استخدام طريقة DB Manager، ويكون ذلك عن طريق الـ componenst الذي اسمه authManager، لذلك سنقوم بإضافة هذا الـ componenst إلى ملفَي config/web.php و config/console.php كالتالي:

// config/web.php file AND config/console.php file
'components' => [
    'authManager' => [
        'class' => 'yii\rbac\DbManager',
    ],
    ...
]

الآن سنقوم بإضافة الجداول الجاهزة إلى قاعدة البيانات عن طريق تنفيذ الأمر التالي باستخدام الـ CMD:

yii migrate --migrationPath=@yii/rbac/migrations 

ستظهر لك رسالة تأكيد، قم بالضغط على حرف y في لوحة المفاتيح كي يتم إكمال تنفيذ الأمر.

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

(لسنا مضطرّين للتعامل مع أوامر migrations، فعندما قمنا ببناء الجداول في المقالات السّابقة قمنا بإنشائها عن طريق تنفيذ استعلامات MySql بسيطة، ولكن معظم أطُر العمل فيها خيار لتنفيذ تعليمات migrations تساعد في بناء قواعد البيانات ومحتوياتها).

بالعودة إلى التعليمة التي قمنا بتنفيذها، فمن خلالها جعلنا الـ Yii تقوم بإنشاء جداول جاهزة مبنية بشكلٍ مسبق وإضافتها إلى قاعدة البيانات الخاصّة بنا، لذلك إن قمتَ بفتح phpMyAdmin سترى وجود 4 جداول جديدة كالتالي:

- جدول auth_item: نضع فيه أسماء الأدوار والصلاحيات التي نريد التعامل معها، ونضع فيه أيضاً وصفاً لها ونمطها، حيث أن نمطها يحمل القيمة 1 في حال كان الـ item هو role، بينما يحمل القيمة 2 في حال كان الـ item هو permission.

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

- جدول auth_assignment: نقوم من خلاله بإسناد الأدوار والصلاحيات للمستخدمين عن طريق وضع الـ id الخاص بالمستخدم ضمن هذا الجدول مع الدور الذي نريد إسناده له.

- جدول auth_rule: من أجل إضافة قواعد أو قيود إضافية للأدوار والصلاحيات، فربما نحتاج لإضافة قاعدة خاصّة للتفريق بين مستخدمَين يحملان نفس الأدوار والصلاحيات (لن نقوم بالتطرّق لها في هذا المقال).

 

الآن بعد تهيئة الإعدادات وإضافة الجداول يمكننا التعمّق في الموضوع لنقوم بتطبيقٍ عمليٍ يساعدنا على الفهم أكثر.

سنقوم بإنشاء 3 roles؛ الأول هو Admin، والثاني هو Manager، والثالث هو User، وسنقوم بإنشاء 2 permissions لكلِ دورٍ من الأدوار الثلاثة السابقة.

الطريقة المعتمدة -غالباً- للتعامل مع RBAC هي عن طريق إنشاء ملف migration ووضع التعليمات فيه، فملفات الـ migrations يتم تنفيذها مرّة واحدة باستخدام تعليمة CMD محددة، وبعدها عندما نريد تنفيذ التعليمة ذاتها فسيتم تجاهل ملفات الـ migrations التي تمّ تنفيذها من قبل لأنّه لا داعي لإعادة تنفيذها من جديد، وبالتالي فإنّ وضع هذه التعليمات في ملف migration يُعتبر أفضل من وضعها في action ضمن controller.

نقوم بإنشاء ملف migration عن طريق تنفيذ التعليمة التالية:

yii migrate/create add_our_roles_and_permissions

سنلاحظ أنّه تمّ إنشاء مجلد باسم migrations وفيه الملف الذي قمنا بإنشائه، والذي يحوي تابعَين؛ الأول sageUp يتم تنفيذه عندما نقوم بتهجير الملفات (أي نقلها إلى قاعدة البيانات)، والثاني هو safeDown يتم تنفيذه عند التراجع أو حذف ما قمنا بنقله.

سنضع الكود الخاص بنا ضمن التابع safeUp بالتأكيد، وسنبدأ الكود بالوصول إلى الـ component المسؤول عن العملية كلّها وهو الـ authManager:

public function safeUp(){
    $auth = Yii::$app->authManager;
}

سنقوم بعدها بإنشاء role عن طريق التابع createRole، وبعد ذلك سننشئ الـ permissions التابعة لهذا الـ role عن طريق التابع createPermission:

public function safeUp(){
    $auth = Yii::$app->authManager;

    $admin = $auth->createRole('Admin');
    $admin_first_permission = $auth->createPermission('Admin First Permission');
    $admin_second_permission = $auth->createPermission('Admin Second Permission');

}

بعد ذلك سنقوم بإضافة هذه الـ role والـ permissions عن طريق التابع add، وسنقوم بإضافة الـ permissions إلى الـ role عن طريق التابع addChild:

public function safeUp(){
    $auth = Yii::$app->authManager;

    $admin = $auth->createRole('Admin');
    $admin_first_permission = $auth->createPermission('Admin First Permission');
    $admin_second_permission = $auth->createPermission('Admin Second Permission');

    $auth->add($admin);
    $auth->add($admin_first_permission);
    $auth->add($admin_second_permission);
    $auth->addChild($admin, $admin_first_permission );
    $auth->addChild($admin, $admin_second_permission );

}

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

public function safeUp(){
    $auth = Yii::$app->authManager;

    $admin = $auth->createRole('Admin');
    $admin_first_permission = $auth->createPermission('Admin First Permission');
    $admin_second_permission = $auth->createPermission('Admin Second Permission');

    $auth->add($admin);
    $auth->add($admin_first_permission);
    $auth->add($admin_second_permission);
    $auth->addChild($admin, $admin_first_permission );
    $auth->addChild($admin, $admin_second_permission );

    $manager = $auth->createRole('Manager');
    $manager_first_permission = $auth->createPermission('Manager First Permission');
    $manager_second_permission = $auth->createPermission('Manager Second Permission');

    $auth->add($manager);
    $auth->add($manager_first_permission);
    $auth->add($manager_second_permission);
    $auth->addChild($manager, $manager_first_permission );
    $auth->addChild($manager, $manager_second_permission );

    $user = $auth->createRole('User');
    $user_first_permission = $auth->createPermission('User First Permission');
    $user_second_permission = $auth->createPermission('User Second Permission');

    $auth->add($user);
    $auth->add($user_first_permission);
    $auth->add($user_second_permission);
    $auth->addChild($user, $user_first_permission );
    $auth->addChild($user, $user_second_permission );

    $auth->addChild($admin, $manager);
}

أخيراً بعد إضافة كل ما سبق لم يبقَ لنا إلا أن نقوم بعملية الإسناد، أي سنقوم بربط id المستخدم مع الـ role الذي نريده عن طريق التابع assign.

(سنفرض أنّه لديك 3 مستخدمين في النّظام كي تكون عملية الإسناد صحيحة)

public function safeUp(){
    ....
    ....
    $auth->assign($admin, 1);
    $auth->assign($manager, 2);
    $auth->assign($user, 3);
}

الآن يجب علينا تنفيذ التعليمة migrate كي يتم نقل هذه البيانات إلى الجداول المُنشأة من قبل:

yii migrate

إذا قمتَ بفتح phpMyAdmin فسترى الإضافات التي حدثت في الجداول الثلاثة (جدول auth_rule لم يتم إضافة أي شيء له)، وسترى في جدول auth_item أنّ الـ type قيمته 1 عندما يكون الـ item هو role، وتكون قيمته 2 عندما يكون الـ item هو permission.

 

كيف يمكن التحكم بما قمنا بفعله حتى الآن؟

في كل controller يمكننا إضافة التابع behaviors والذي من خلاله نضع الـ actions القابلة للتنفيذ من قبل المستخدمين الذين يحملون أدواراً أو صلاحياتٍ محدّدة.

سنستخدم الـ UsersController وسنقوم بإنشاء action ونسمّيها test مثلاً، وسنقوم بتحديد الـ role أو الـ permission القادرة على الوصول لهذه الـ action وتنفيذها، وفي حال عدم القدرة على تنفيذها (لأنّ المستخدم ليس له صلاحية تنفيذها) فسنقوم بتحويله إلى action أخرى اسمها forbidden مثلاً (سنقوم بإنشائها أيضاً):

class UsersController extends Controller
{
    public function behaviors(){
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['test'],
                'rules' => [
                    [
                        'actions' => ['test'],
                        'allow' => true,
                        'roles' => ['Admin', 'User First Permission']
                    ]
                ],
                'denyCallback' => function(){
                    return \Yii::$app->response->redirect(['/users/forbidden']);
                }
            ]
        ];
    }

    public function actionTest(){
        return var_dump('You have permissions to access this action, so welcome!');
    }

    public function actionForbidden(){
        return var_dump('Not allowed!');
    }
}

شرح لما سبق:

في التابع behaviors استخدمنا الكلاس AccessControl المسؤول عن تنظيم هذه الأمور (لا تنسَ أن تقوم بعمل use له: yii\filters\AccessControl).

الخاصّية only -والتي قيمتها مصفوفة من أسماء الـ actions- تعني أنّه فقط هذه الـ actions سيتم التحقق من الـ roles والـ permissions للمستخدمين الذين يقومون باستدعائها (فليست كل الـ actions بحاجةٍ لاختبار قابلية الوصول إليها)، وبالتالي عند تنفيذ action ليست موجودة في هذه المصفوفة فسيتم تنفيذها بشكلٍ طبيعي دون التحقق من صلاحيات المستخدم، أمّا عندمحاولة تنفيذ إحدى الـ actions الموجودة في هذه المصفوفة سيتم التحقق من صلاحيات المستخدم لتتم معرفة فيما إذا كان المستخدم مخوّلاً لتنفيذها أم لا.

الخاصّية rules نقوم فيها بتقسيم الـ actions إلى مجموعات، كل مجموعة منها خاصّة بـ roles أو permissions معينة.

الخاصّية denyCallback نقوم فيها بإعادة توجيه المستخدم إلى action أخرى في حال لا يملك صلاحيات تنفيذ إحدى الـ actions الموجودة في الـ rules.

في الكود السابق حدّدنا أنّ الـ action التي اسمها test يستطيع فقط الـ Admin والذي يحمل صلاحية User First Permission أن يصل إليها (أي المستخدم الأول والثالث حسب الإسناديات التي قمنا بها في ملف الـ migration، فالمستخدم الأول هو Admin، والثالث هو User وله صلاحية User First Permission)، لذلك إن قمتَ بتسجيل الدخول باستخدام المستخدم الأول أو الثالث، وحاولتَ تنفيذ actionTest سيتم الأمر بنجاح، بينما في حال قمتَ بتسجيل الدخول باستخدام المستخدم الثاني فلن ينجح الأمر، وسيتم تحويل المستخدم إلى actionForbidden لأنّه Manager ولا يملك الصلاحية.

 

الآن قم بتغييرٍ أخير، واجعل الـ actionTest قابلة للوصول من قِبَل الـ Manager فقط.

في حال قمتَ بتسجيل الدخول باستخدام المستخدم الثاني فستتمكّن من تنفيذ actionTest، وفي حال قمتَ بتسجيل الدخول باستخدام المستخدم الأول فستستطيع أيضاً تنفيذ actionTest لأنّه حسب الهرمية التي وضعناها قمنا بجعل الـ Manager ابناً للـ Admin، وبالتالي فإنّ صلاحيات الـ Manager ستصبح أيضاً من صلاحيات الـ Admin (بالإضافة لصلاحيات الـ Admin الأساسية).

 

ملاحظة: إذا أردتَ أن تقوم بعمليات الإسناد من خلال تنفيذ actions معينة (مثل أن تعطي المستخدم الذي يقوم بتسجيل حساب جديد في النظام الدور User بشكلٍ تلقائي) فيمكنك ذلك من خلال استدعاء الـ authManager كما قمنا في ملف الـ migration، وتستطيع من خلاله أن تستدعي الـ role الذي تريده عن طريق التابع getRole الذي تعطيه اسم الـ role كـ string، وبعدها يمكنك أن تقوم بعملية الإسناد:

//Registering new user in the system
…
$auth = Yii::$app->authManager;
$user_role = $auth->getRole('User');
$auth->assign($user_role, $new_user->id);

لاحظ أنّنا جلبنا الـ id الخاص بالمستخدم الجديد من الـ object الذي افترضنا أنّ اسمه new_user، وذلك بالطبع بعد أن يتم عمل save لهذا الـ user، لأنّه لولا الـ save فلن نتمكّن من الحصول على الـ id الخاص به.

 

تعرّفنا في هذا المقال على كيفية إتمام بناء نظام المستخدمين وتحقيق الأدوار والصلاحيات والتحكّم بها ... الآن يمكنك أن تبنيَ التطبيق الخاص بك بهرميةٍ محددة وفعّالة لوصول المستخدمين حسب صلاحياتهم إلى صفحاتٍ دون غيرها. 

0
إعجاب
1337
مشاهدات
0
مشاركة
1
متابع

التعليقات (0)

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !