رتبه موضوع:
  • 0 رای - 0 میانگین
  • 1
  • 2
  • 3
  • 4
  • 5
دسترسی مبتنی بر نقش کاربران با Yii2 RBAC
#1
اعتبارسنجی
  1. فیلتر کنترل دسترسی
  2. کنترل دسترسی مبتنی بر نقش
اعتبارسنجی، فرآیند بررسی این موضوع است که یک کاربر، مجوز کافی برای انجام کاری را دارد یا خیر؟ Yii دو روش اعتبارسنجی ارائه می‌دهد: فیلتر کنترل دسترسی (ACF) و کنترل دسترسی مبتنی بر نقش (RBAC).

فیلتر کنترل دسترسی
ACF (مخفف Access Control Filter) یک روش ساده اعتبارسنجی است که بصورت یک فیلتر در yiifiltersAccessControl پیاده‌سازی شده است و بهترین کاربرد را در برنامه‌هایی دارد که فقط نیاز به برخی کنترل‌های ساده دسترسی دارند. همانطور که از نامش پیداست، ACF یک فیلتر عملیات است که می‌تواند در یک کنترلر یا ماژول بکار گرفته شود. وقتی که یک کاربر درخواست اجرای یک اکشن را دارد، ACF فهرستی از قوانین دسترسی را بررسی می‌کند تا مشخص نماید که کاربر مجاز به دسترسی به اکشن درخواستی است یا نه.
کد زیر نحوه استفاده از ACF را در کنترلر site نشان می‌دهد:

use yiiwebController;
use yiifiltersAccessControl;

class SiteController extends Controller
{

   public function behaviors()
   {
       return [
           'access' => [
               'class' => AccessControl::className(),
               'only' => ['login', 'logout', 'signup'],
               'rules' => [
                   [
                       'allow' => true,
                       'actions' => ['login', 'signup'],
                       'roles' => ['?'],
                   ],
                   [
                       'allow' => true,
                       'actions' => ['logout'],
                       'roles' => ['@'],
                   ],
               ],
           ],
       ];
   }
   // ...
}

در کد بالا ACF به کنترلر site بصورت یک رفتار ضمیمه شده است. این روش، راه عمومی استفاده از یک فیلتر عملیات است. گزینه only مشخص میکند که ACF فقط باید به اکشنهای login و logout و signup اعمال شود. تمام اکشنهای دیگر در کنترلر site موضوع کنترل دسترسی نخواهند بود (بدون کنترل اجرا میشوند). گزینه rules فهرست قوانین دسترسی را مشخص میکند که بصورت زیر خوانده میشود:
  • تمام میهمانان (کسانی که با شناسه کاربری و... وارد نشده اند) میتوانند به اکشنهای login و signup دسترسی داشته باشند. گزینه roles شامل یک علامت سؤال است که یک شناسه خاص معادل «کاربران میهمان» میباشد.
  • کاربرانی که با شناسه کاربری خود وارد سایت شده اند (لاگین کرده اند) به متد logout دسترسی دارند. کارکتر @ یک شناسه خاص دیگر است که معادل «کاربران احراز هویت شده» میباشد.
ACF عمل اعتبارسنجی را با بررسی قوانین دسترسی یکی پس از دیگری و از بالا به ئایین انجام میدهد تا زمانی که یک قانون ئیدا کند که با شرایط جاری کاربر مطابقت دارد. مقدار گزینه allow در قانون موردنظر میگوید که آیا آن کاربر مجاز به اجرای اکشن درخواست شده است یا خیر. اگر هیچ قانونی با شرایط کاربر مطابقت نداشته باشد، به معنای عدم دسترسی است و ACF اجرای اکشن را متوقف میکند.
وقتی ACF تشخیص دهد که کاربر مجاز به اجرای اکشن جاری نیست، کارهای زیر را بطور پیشفرض انجام میدهد:
  • اگر کاربر میهمان باشد، متد yiiwebUser::loginRequired() را صدا میزند تا مرورگر کاربر را به صفحه لاگین هدایت نماید.
  • اگر کاربر قبلاً وارد شده باشد، یک yiiwebForbiddenHttpException تولید میکند.
شما میتوانید این رفتار را با تنظیم خصوصیت yiifiltersAccessControler::$denyCallback مشابه کد زیر سفارشی کنید:

[
    'class' => AccessControl::className(),
    ...
    'denyCallback' => function ($rule, $action) {
        throw new Exception('You are not allowed to access this page');
    }
]

قوانین دسترسی از گزینه های زیادی پشتیبانی می‌کنند. در زیر، فهرستی از گزینه‌های پشتیبانی شده را مشاهده می‌کنید. حتی می‌توانید از کلاس yiifiltersAccessRule یک کلاس دیگر مشتق کنید و کلاس‌های سفارشی قوانین دسترسی خودتان را بسازید.
  • allow مشخص می‌کند که یک قانون allow می‌نویسید یا deny.
  • actions مشخص‌کننده اکشن‌هایی است که این قانون با آنها مطابقت دارد. این گزینه باید آرایه‌ای از ID اکشن‌ها باشد. مقایسه نسبت به بزرگی و کوچکی حروف حساس است. اگر این گزینه خالی باشد یا تعریف نشده باشد، بمعنای آن است که قانون مذکور تمام اکشن‌ها را دربر می‌گیرد.
  • controllers مشخص‌کننده کنترلرهایی است که این قانون با آنها مطابقت دارد. این گزینه باید یک آرایه شامل ID کنترلرها باشد. هر کنترلر باید شامل پیشوند ID ماژول مربوطه باشد (اگر کنترلر درون یک ماژول تعریف شده باشد). مقایسه نسبت به بزرگی و کوچکی حروف حساس است. اگر این گزینه خالی باشد یا اعلام نشده باشد، بمعنای پوشش تمام کنترلرها توسط قانون مربوطه است.
  • roles مشخص می‌کند که چه دسته کاربرانی شامل این قانون می‌شوند. دو نقش اختصاصی وجود دارد که توسط yiiwebUser::$isGuest بررسی می‌شوند:
    • ? معادل کاربر میهمان (لاگین نکرده) است.
    • @ معادل کاربر احراز هویت شده (لاگین کرده) است.
استفاده از هرگونه اسم نقش دیگری موجب فراخوانی متد yiiwebUser::can() می‌شود که نیازمند فعالسازی RBAC است (که در بخش بعدی توضیح داده خواهد شد). اگر این گزینه خالی یا مشخص نشده باشد، به معنای آن است که تمام نقش‌ها توسط این قانون پوشش داده می‌شوند.
  • ips مشخص می‌کند که کدام آدرس‌های IP کلاینت شامل این قانون می‌شوند. یک آدرس IP می‌تواند شامل کارکتر عمومی * باشد که به معنای تمام آدرسهای IP با پیشوند مشابه است. برای مثال 192.168.* معادل تمام آدرسهای IP در محدوده 192.168. خواهد بود. اگر این گزینه خالی باشد یا ذکر نشده باشد به معنای پوشش تمام آدرسهای IP توسط قانون مربوطه است.
  • verbs مشخص‌کننده روش درخواست (برای مثال GET یا POST) است که این قانون شامل آن می‌شود. مقایسه به بزرگی و کوچکی حروف حساس نیست.
  • matchCallback مشخص‌کننده یک متد قابل فراخوانی PHP است که باید صدا زده شود و برحسب خروجی آن که true یا false است، تصمیم گرفته شود که این قانون اعمال شود یا خیر؟
  • denyCallback مشخص‌کننده یک متد قابل فراخوانی PHP است که باید زمانی صدا زده شود که این قانون جلوی دسترسی را می‌گیرد.
در ادامه مثالی از نحوه کاربرد گزینه matchCallback را مشاهده می‌کنید که اجازه بررسی یک روند منطقی خاص را می‌دهد:
use yiifiltersAccessControl;

class SiteController extends Controller
{

   public function behaviors()
   {
       return [
           'access' => [
               'class' => AccessControl::className(),
               'only' => ['special-callback'],
               'rules' => [
                   [
                       'actions' => ['special-callback'],
                       'allow' => true,
                       'matchCallback' => function ($rule, $action) {
                       return date('d-m') === '31-10';
                   }
                   ],
               ],
           ],
       ];
   }

   // Match callback called! This page can be accessed only each October 31st
   public function actionSpecialCallback()
   {
       return $this->render('happy-halloween');
   }
}

کنترل دسترسی مبتنی بر نقش (RBAC)

Roll Based Access Control یا به اختصار RBAC یک روش ساده و در عین حال قدرتمند برای کنترل مرکزی دسترسی ارائه می‌دهد. لطفاً به ویکی‌پدیا برای جزئیات مقایسه RBAC با سایر الگوهای سنتی کنترل دسترسی مراجعه کنید.
Yii یک سیستم عمومی سلسله مراتبی از RBAC را با پیروی از مدل RBAC معرفی شده توسط NIST پیاده‌سازی کرده است. این مدل، قابلیت RBAC را ازطریق کامپوننت authManager ارائه می‌کند.
استفاده از RBAC شامل دو مرحله است. بخش اول پیاده‌سازی داده‌های اعتبارسنجی RBAC است و بخش دوم شامل استفاده از آن داده‌ها برای چک‌کردن سطح دسترسی در مکان موردنیاز است. برای اینکه توضیحات بعدی را راحت‌تر درک کنید، ابتدا برخی از مفاهیم پایه RBAC را توضیح می‌دهیم:

مفاهیم پایه
یک نقش، نمایانگر مجموعه‌ای از مجوزها است (برای مثال، ایجاد پست، اصلاح پست). یک نقش ممکن است به یک یا چند کاربر داده‌شود. برای بررسی اینکه یک کاربر مجوز خاصی را دارد یا نه، می‌توانیم چک‌کنیم که آیا آن کاربر دارای نقشی هست که شامل آن مجوز باشد یا خیر؟
درکنار هر نقش یا مجوز ممکن است یک قانون (Rule) نیز ذکر شود. یک قانون معادل بخشی از کد است که باید در حین انجام عملیات بررسی دسترسی اجرا شود تا مشخص‌گردد که نقش یا مجوز موردنظر به کاربر جاری اختصاص داده می‌شود یا نه؟ برای مثال، مجوز «اصلاح پست» ممکن است دارای قانونی باشد که چک می‌کند آیا کاربر جاری، ایجاد کننده پست است یا خیر؟ درطی عملیات چک‌کردن دسترسی، اگر کاربر جاری ایجادکننده پست نباشد، اینگونه درنظر گرفته می‌شود که وی مجوز «اصلاح پست» را ندارد.
هر دو گزینه «نقش» و «مجوز» می‌توانند بصورت سلسله‌مراتبی مشخص‌شوند. بطور خاص، یک نقش ممکن‌است شامل چند مجوز یا نقش‌های دیگر باشد؛ و یک مجوز ممکن است شامل مجوز دیگری نیز باشد. Yii یک سیستم سلسله مراتبی «ترتیبی جزئی» را پیاده‌سازی کرده است که شامل یک سلسله مراتب خاص‌تر «درختی» است؛ بدین‌ترتیب که ممکن‌‌است یک نقش شامل یک مجوز باشد اما عکس آن امکان‌پذیر نیست.

تنظیم RBAC
قبل از اینکه داده‌های اعتبارسنجی را مشخص‌کنیم و بررسی دسترسی را انجام‌دهیم، باید کامپوننت authManager را تنظیم نماییم. Yii دو نوع مدیر اعتبارسنجی ارائه می‌دهد: اولی از یک فایل PHP برای نگهداری اطلاعات اعتبارسنجی استفاده می‌کند، درحالی‌که دومی این اطلاعات را در پایگاه داده‌ها ذخیره می‌نماید. اگر برنامه شما به مدیریت نقش‌ها و مجوزهای آنچنان پویایی نیاز ندارد، می‌توانید از اولی استفاده نمایید.

استفاده از PhpManager
کد زیر نحوه تنظیم authManager را در فایل تنظیمات برنامه با کمک کلاس yiirbacPhpManager نشان می‌دهد:

return [
   // ...
   'components' => [
       'authManager' => [
            'class' => 'yiirbacPhpManager',
        ],
   // ...
   ],
];

حال می‌توانید به کامپوننت authManager با کمک Yii::$app->authManager دسترسی پیدا کنید.
بطور پیشفرض، yiirbacPhpManager داده‌های RBAC را در یک فایل در مسیر @app/rbac ذخیره می‌کند. اگر ساختار سلسله مراتبی مجوزها نیازمند تغییر بصورت آنلاین است، مطمئن شوید که پوشه و تمام فایلهای داخل آن توسط پردازش وب‌سرور قابل نوشتن است.

استفاده از DbManager
کد زیر نحوه تنظیم authManager را در فایل تنظیمات برنامه با استفاده از کلاس DbManager نشان می‌دهد:

return [
   // ...
   'components' => [
       'authManager' => [
            'class' => 'yiirbacDbManager',
        ],
   // ...
   ],
];

نکته: اگر از قالب yii2-basic-app استفاده می‌کنید، یک فایل تنظیمات config/console.php نیز وجود دارد که باید کامپوننت authManager را در آنجا نیز علاوه بر config/web.php معرفی نمایید. درصورتی که از yii2-advanced-app استفاده می‌کنید، authManager باید فقط یکبار در common/config/main.php معرفی‌شود.
DbManager از چهار جدول در دیتابیس برای نگهداری داده‌های خود استفاده می‌کند:
  • itemTable جدول نگهداری عناصر اعتبارسنجی (مجوزها و نقش‌ها) است. مقدار پیشفرض آن auth_item می‌باشد.
  • itemChildTable جدول نگهداری ساختار سلسله مراتبی عناصر است. مقدار پیشفرض آن auth_item_child می‌باشد.
  • assignmentTable جدول نگهداری انتساب عناصر اعتبارسنجی به کاربران است. مقدار پیشفرض آن auth_assignment می‌باشد.
  • ruleTable جدول نگهداری قوانین اعتبارسنجی است. مقدار پیشفرض آن auth_rule می‌باشد.
قبل از اینکه بتوانید جلوتر بروید، باید این جداول را در دیتابیس ایجاد کنید. برای انجام این کار می‌توانید از migration ذخیره شده در @yii/rbac/migrations استفاده کنید. این کد را در پنجره کنسول بنویسید:
yii migrate --migrationPath=@yii/rbac/migrations
حال می‌توانید ازطریق Yii::$app->authManager به کامپوننت authManager دسترسی پیدا کنید.

ایجاد داده های اعتبارسنجی
ساخت داده‌های اعتبارسنجی شامل اعمال زیر است:
  • تعریف نقش‌ها و مجوزها
  • ایجاد ارتباط بین نقش‌ها و مجوزها
  • تعریف قوانین
  • مرتبط‌کردن قوانین به نقش‌ها و مجوزها
  • انتساب نقش‌ها به کاربران
بسته به انعطاف‌پذیری موردنیاز، وظایف فوق را می‌توانید به روش‌های مختلفی انجام دهید. اگر ساختار سلسله‌مراتبی شما برای نقش‌ها و مجوزها ثابت است و تعداد ثابتی از کاربران دارید، می‌توانید یک دستور کنسول بسازید که داده‌های اعتبارسنجی را یکبار ازطریق APIهایی که توسط authManager ارائه می‌شود، آماده می‌کند:

namespace appcommands;

use Yii;
use yiiconsoleController;

class RbacController extends Controller
{

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


       // add "createPost" permission
       $createPost = $auth->createPermission('createPost');
       $createPost->description = 'Create a post';
       $auth->add($createPost);


       // add "updatePost" permission
       $updatePost = $auth->createPermission('updatePost');
       $updatePost->description = 'Update post';
       $auth->add($updatePost);


       // add "author" role and give this role the "createPost" permission
       $author = $auth->createRole('author');
       $auth->add($author);
       $auth->addChild($author, $createPost);


       // add "admin" role and give this role the "updatePost" permission
       // as well as the permissions of the "author" role
       $admin = $auth->createRole('admin');
       $auth->add($admin);
       $auth->addChild($admin, $updatePost);
       $auth->addChild($admin, $author);


       // Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId()
       // usually implemented in your User model.
       $auth->assign($author, 2);
       $auth->assign($admin, 1);
   }
}

بعد از اجرای دستور yii rbac/init در کنسول، ساختار سلسله مراتبی زیر بدست می آید:
   
کسی که نقش author را دارد می‌تواند پست ایجاد کند و admin اجازه اصلاح پست و تمام کارهایی را دارد که author می‌تواند انجام دهد.
اگر برنامه شما به کاربران اجازه ثبت‌نام می‌دهد، نیاز دارید نقش‌های خاصی را به کاربران جدید در زمان
ثبت‌نام بدهید. برای مثال، اگر بخواهید تمام کاربران ثبت‌نام شده مجوز author را در پروژه شما داشته باشند، می‌توانید در مدل SignupForm خودتان متد signup را بصورت زیر پیاده‌سازی کنید:

public function signup()
{
   if ($this->validate()) {
       $user = new User();
       $user->username = $this->username;
       $user->email = $this->email;
       $user->setPassword($this->password);
       $user->generateAuthKey();
       $user->save(false);


       // the following three lines were added:
       $auth = Yii::$app->authManager;
       $authorRole = $auth->getRole('author');
       $auth->assign($authorRole, $user->getId());


       return $user;
   }


   return null;
}

برای برنامه‌هایی که نیازمند کنترل دسترسی پیچیده و داده‌های پویا و اصلاح‌شدۀ اعتبارسنجی هستند، رابط کاربری خاص (برای مثال پنل مدیریت) را می‌توان با کمک APIهای ارائه شده توسط authManager تولید نمود.

استفاده از قوانین
همانگونه که قبلاً ذکر شد، قوانین می‌توانند قیدهای اضافه‌ای به نقش‌ها و مجوزها اضافه کنند. یک قانون، یک کلاس است که از yiirbacRule مشتق شده است. این کلاس باید متد execute() را پیاده‌سازی کند. در ساختار سلسله‌مراتبی که قبلاً نوشته بودیم، author نمی‌توانست پست خودش را ویرایش کند. اجازه دهید این موضوع را حل کنیم. اول نیاز به یک قانون داریم که مشخص می‌کند کاربر، ایجادکننده پست است:

namespace apprbac;

use yiirbacRule;

/**
* Checks if authorID matches user passed via params
*/
class AuthorRule extends Rule
{
   public $name = 'isAuthor';

   /**
    * @param string|integer $user the user ID.
    * @param Item $item the role or permission that this rule is associated with
    * @param array $params parameters passed to ManagerInterface::checkAccess().
    * @return boolean a value indicating whether the rule permits the role or permission it is associated with.
    */
   public function execute($user, $item, $params)
   {
       return isset($params['post']) ? $params['post']->createdBy == $user : false;
   }
}

قانون بالا چک می‌کند که آیا پست توسط $user ایجاد شده است یا خیر؟ حال یک نقش خاص ایجاد می‌کنیم بنام updateOwnPost درون دستور کنسول قبلی که نوشته بودیم:

$auth = Yii::$app->authManager;

// add the rule
$rule = new apprbacAuthorRule;
$auth->add($rule);

// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;

$auth->add($updateOwnPost);

// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);

// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);

حالا ساختار سلسله‌مراتبی ما به این شکل تبدیل شده است:
   

بررسی دسترسی
حال که داده‌های اعتبارسنجی آماده است، چک‌کردن دسترسی به سادگی فراخوانی متد yiirbacManagerInterface::checkAccess() است. از آنجا که اغلب بررسی‌های دسترسی درمورد کاربر جاری انجام می‌شود، برای راحتی بیشتر Yii یک متد میانبر بنام yiiwebUser::can() ارائه کرده است، که میتواند بصورت زیر بکار گرفته شود:

if (Yii::$app->user->can('createPost')) {
   // create post
}

اگر کاربر جاری Jane باشد با ID=1 و از createPost شروع کنیم و بخواهیم به Jane برسیم:
   
برای بررسی اینکه یک کاربر میتواند یک پست را ویرایش کند، نیاز به ارسال یک پارامتر اضافه داریم که توسط قانون AuthorRule بکار میرود:
if (Yii::$app->user->can('updatePost', ['post' => $post])) {
   // update post
}
این اتفاقی است که در زمانی رخ می‌دهد که کاربر جاری John است:
   
ما کار را با updatePost شروع میکنیم و وارد updateOwnPost میشویم. برای عبور از بررسی دسترسی، AuthorRule باید نتیجه true را در متد execute() خودش بازگرداند. متد مذکور $params خودش را از متد can() دریافت میکند که مقدار ['post' => $post] را دارد. اگر همه چیز درست باشد، ما به نقش نویسنده میرسیم که به John نسبت داده شده است.
درمورد Jane اوضاع کمی ساده تر است چون وی یک admin است:
   

استفاده از نقشهای پیشفرض
یک نقش پیشفرض نقشی است که بصورت ضمنی به همه کاربران داده می‌شود. فراخوانی متد yiirbacManagerInterface::asign() نیاز نیست، و داده‌های اعتبارسنجی شامل اطلاعات انتساب آن به کاربران نیستند.
یک نقش پیشفرض معمولاً با یک قانون مرتبط است که مشخص می‌کند نقش به کاربر مورد بررسی اختصاص می‌یابد یا خیر؟
نقش‌های پیشفرض اغلب در برنامه‌هایی بکار می‌روند که از قبل نقش‌هایی به کاربران داده شده است. برای مثال، یک برنامه ممکن است یک ستون «group» در جدول کاربرانش داشته باشد که نشان می‌دهد هر کاربر عضو کدام گروه مجوزهاست. اگر هر گروه مجوز را به یک نقش RBAC نگاشت کنیم، می‌توانید از قابلیت نقش پیشفرض برای اختصاص خودکار هر کاربر به یک نقش RBAC استفاده کنید. اجازه‌دهید از یک مثال برای نمایش روش انجام این کار استفاده کنیم.
فرض‌کنید در جدول کاربران، یک ستون group دارید که از عدد 1 برای گروه مدیران و عدد 2 برای گروه نویسندگان استفاده می‌کند. شما می‌خواهید دو نقش admin و author را در RBAC برای نمایش مجوزهای این دو گروه استفاده نمایید. می‌توانید RBAC را به‌شکل زیر پیاده‌سازی کنید:

namespace apprbac;

use Yii;
use yiirbacRule;

/**
* Checks if user group matches
*/
class UserGroupRule extends Rule
{
   public $name = 'userGroup';

   public function execute($user, $item, $params)
   {
       if (!Yii::$app->user->isGuest) {
           $group = Yii::$app->user->identity->group;
           if ($item->name === 'admin') {
               return $group == 1;
           } elseif ($item->name === 'author') {
               return $group == 1 || $group == 2;
           }
       }
       return false;
   }
}

$auth = Yii::$app->authManager;


$rule = new apprbacUserGroupRule;
$auth->add($rule);


$author = $auth->createRole('author');
$author->ruleName = $rule->name;
$auth->add($author);
// ... add permissions as children of $author ...


$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $author);
// ... add permissions as children of $admin ...

دقت‌کنید که در کد فوق، از آنجا که author بعنوان فرزند admin اضافه شده است، وقتی که متد execute() را در کلاس قانون پیاده‌سازی می‌کنید، باید به این سلسله‌مراتب نیز احترام بگذارید. بخاطر همین موضوع است که وقتی اسم نقش author است، متد execute() درصورتی که گروه کاربر 1 یا 2 بود، نتیجه true برمی‌گرداند (که به‌معنای آن است که کاربر در گروه admin یا author قرار دارد).
در مرحله بعد، authManager را با فهرست‌کردن دو نقش در yiirbacBaseManager::$defaultRoles تنظیم‌کنید:

return [
   // ...
   'components' => [
       'authManager' => [
           'class' => 'yiirbacPhpManager',
           'defaultRoles' => ['admin', 'author'],
       ],
   // ...
   ],
];

حال اگر عمل چک‌کردن سطح دسترسی را انجام دهید، هر دو نقش admin و author با ارزیابی قوانین مرتبط با آنها کار می‌کنند. اگر قانون نتیجه true بازگرداند، به‌معنای آن است که نقش به کاربر جاری اختصاص داده می‌شود. برمبنای پیاده‌سازی قانون در بالا، معنای جمله فوق آن است که اگر مقدار group یک کاربر 1 باشد، نقش admin به کاربر اختصاص داده می‌شود؛ و اگر group برابر با 2 بود، نقش author اعمال خواهد شد.
تشکر شده توسط: web2.developer , Renegad , abdollah110110 , vahid68 , dmaon , inspiration




کاربران در حال بازدید این موضوع: 1 مهمان