Yii2-advanced: Гибкая настройка Yii2 RBAC (роли, разрешения, правила)

от автора

У админа может и не быть возможности к разрешению пользователя и в пределах одной роли пользователи могут иметь разный доступ к разрешениям

Как организовать сущности Role,Permission,Rule

Роли (role): типовые роли supper_admin,admin,customer (сотрудник, менеджер),user (авторизированный пользователь),guest (не авторизированный пользователь). Роль supper_admin наследует от всех ролей разрешения благодаря этому supper_admin имеет доступ ко всем permission не зависимо от их наличия в конкретной роли но требуется пропуск во всех правилах;
Разрешения (permission): роль является прямым родителем разрешения, без наследования (кроме роли supper_admin).Другими словами, одно и тоже разрешение будет назначаться каждой нужной роли.
Правила (Rule): правила для ролей и для разрешений наследуются от BaseRole в котором присутсвует проверка общих правил.

От вас потрубуется закодить админку для ролей,разрешений,разрешения пользователя.

Что там должно быть:
Админка для ролей.
Добавление, удаление, обновление разрешений.

Админка для разрешений.
Добавление, удаление.

Админка разрешения пользователя.
Тут должна быть возможность конкретному пользователю по мимо его разрешений и запрещающих ролей(запрещающие разрешения) назначить или снять определенное разрешение или запрещающюю роль(запрещающее разрешение).По поводу запрещающих ролей(запрещающих разрешений) будет пояснение дальше.

Подключение компонента:  'components' => [     ....         'authManager' => [             'class'           => 'yii\rbac\DbManager',              'itemTable'       => 'auth_item',             'itemChildTable'  => 'auth_item_child',             'assignmentTable' => 'auth_assignment',             'ruleTable'       => 'auth_rule',             'defaultRoles'    => ['guest'],// роль которая назначается всем пользователям по умолчанию         ], ....] 

Главное найте место в проекте где будет располагаться проверка, так как от этого зависит имена разрешений которые будут проверятся.
Самый простой это формировать ключи разрешений из action контроллера в который мы попадаем.Можно добавить в ключ админка это или фронтенд часть, название контроллера и метода и метод запроса GET,POST,PUT,DELETE… что бы сформировать уникальное название разрешения на всем сайте. К примеру fr_user_profile_get для фронтенда site.com.ua/user/profile методом GET

По ссылке ниже можно ознакомится с вариантами расположения проверки достпупа:
Альтернативная настройка RBAC

Способ 1 — в методе контроллера

public function actionIndex() {     if (!\Yii::$app->user->can('index')) {         throw new ForbiddenHttpException('Access denied');     }     return $this->render('index'); } 

Способ 2 — прописать beforeAction

public function beforeAction($action) {     if (parent::beforeAction($action)) {         if (!\Yii::$app->user->can($action->id)) {             throw new ForbiddenHttpException('Access denied');         }         return true;     } else {         return false;     } } 

И так, вы уже определились с местом проверки разрешения.Теперь организуем логику.
Что такое запрещающая роль(запрещающее разрешение) — это по сути запрещающее разрешение, а
по факту запрещающая роль.Что бы была возможность конкретному пользователю запретить определенное разрешение.Дело в том что мы не можем назначить пользователю разрешение мы можем только роль назначить при чем даже и не одну.Вот тут мы и создаем роль именованную названием разрешения с постпрефиксом _not которая при наличии у пользователя будет запрещать доступ, а проверка на наличие этой роли-разрешения будет происходить в базом правиле от которого наследуются все правили для ролей и разрешений.

Проверка разрешения

 /* При проверки разрешения мы передаем массив параметров конкретно в моем случае это класс который будет использован в правиле удаления/обновления */ if(!Yii::$app->user->can( 'ваш ключ разрешения',['class'=>static::class])){        throw new \yii\web\ForbiddenHttpException('Access denied role ');  } 
Стартовое создание ролей и правил

    // RULES                 Yii::$app->authManager->removeAllRules();                //общая проверка во всех разрешениях без правил на отсутствие блокирующего разрешения                 $BaseRule= new \common\rbac\BaseRule();                 Yii::$app->authManager->add($BaseRule);                                 //только для разрешений                 $RuleUpdateDelete=new   \common\rbac\RuleUpdateDelete();                  Yii::$app->authManager->add($RuleUpdateDelete);                 // правило только для роли admin                 $RuleForAdmin= new \common\rbac\RuleForAdmin();                  Yii::$app->authManager->add($RuleForAdmin);                  // правило только для роли customer                 $RuleForCustomer= new \common\rbac\RuleForCustomer();                    Yii::$app->authManager->add($RuleForCustomer);                 // правило только для роли user                 $RuleForUser= new \common\rbac\RuleForUser();                 Yii::$app->authManager->add($RuleForUser);                  // правило только для роли guest                 $RuleForGuest= new \common\rbac\RuleForGuest();                 Yii::$app->authManager->add($RuleForGuest);  // ROLES                 Yii::$app->authManager->removeAllRoles();                  $role_supper_admin = Yii::$app->authManager->createRole('supper_admin');                 $role_supper_admin->description='supper_admin';                 Yii::$app->authManager->add($role_supper_admin);                  $role_admin = Yii::$app->authManager->createRole('admin');                 $role_admin->description='Сотрудник admin';                 $role_admin->ruleName=$RuleForAdmin->name;                 Yii::$app->authManager->add($role_admin);                  $role_customer = Yii::$app->authManager->createRole('customer');                 $role_customer->description='Сотрудник customer';                 $role_customer->ruleName=$RuleForCustomer->name;                 Yii::$app->authManager->add($role_customer);                  $role_user = Yii::$app->authManager->createRole('user');// авторизирован                 $role_user->description='Авторизированный пользователь';                 $role_user->ruleName=$RuleForUser->name;                 Yii::$app->authManager->add($role_user);                  $role_guest = Yii::$app->authManager->createRole('guest');// не авторизирован                 $role_guest->description='Не авторизированный пользователь';                 $role_guest->ruleName=$RuleForGuest->name;                 Yii::$app->authManager->add($role_guest);                  //наследование тольку у суппер админа                 Yii::$app->authManager->addChild($role_supper_admin, $role_admin);                 Yii::$app->authManager->addChild($role_supper_admin, $role_customer);                 Yii::$app->authManager->addChild($role_supper_admin, $role_user);                 Yii::$app->authManager->addChild($role_supper_admin, $role_guest); 

При создании роли учесть

create

 public function create(){        //общая проверка во всех разрешениях без правил на отсутствие блокирующего разрешения          $BaseRule= new BaseRule();          $role_new  = Yii::$app->authManager->createRole($this->role);         $role_new->description=$this->description;         if($this->data)$role_new->data=$this->data;         // правило которое будет срабатывать при проверке на эту роль         $role_new->ruleName=$BaseRule->name;          Yii::$app->authManager->add($role_new);         // Добавление разрешений         if($role_new=Yii::$app->authManager->getRole($this->role)){             if(isset($this->permissions)){                 foreach ($this->permissions as $permission=>$val){                      $child= Yii::$app->authManager->getPermission($permission);                     if($child instanceof  yii\rbac\Permission && Yii::$app->authManager->canAddChild($role_new, $child)){                         Yii::$app->authManager->addChild($role_new, $child);                     }                 }             } // Обязательно дабавляем новую роль к supper_admin так как он не имеет сових непосредственных разрешений             $role_supper_admin=Yii::$app->authManager->getRole('supper_admin');             if(Yii::$app->authManager->canAddChild($role_supper_admin, $role_new)){                 Yii::$app->authManager->addChild($role_supper_admin, $role_new);             }             return true;         }else{             return false;         }     } 

При создании разрешения учесть

create

   public function create() { /* После валидации и инициализации аттрибутов мы имеем $this->permission название разрешения из котого мы должны понять какое правило ему назначить  базовое или для удаление/обновления которое также наследуется от базового.    Правило для удаления и обновления должно по мимо проверки запрещающих  разрешений еще проверять имеет ли конкретный пользователь к изменяемой информации  (т.е. если пользователь этот комментарий создал то он может его и удалить или  изменить но другой пользователь кроме supper_admin,admin,customer)    В $permission->data можете сохранять полезню информацию о составляющих вашего ключа что б при редактировании легче можно было найти источник его. */         if(preg_match('#.*(Delete|Put)$#',   $this->method) ){             $Rule=Yii::$app->authManager->getRule('RuleUpdateDelete');         }else{             $Rule=Yii::$app->authManager->getRule('BaseRule');         }          $permission = Yii::$app->authManager->createPermission($this->permission);         $permission->description = $this->description;         // правило которое будет срабатывать при проверке на это разрешение         $permission->ruleName = $Rule->name;         $permission->data = [....];// ваши вспомогательные данные         Yii::$app->authManager->add($permission);        //Создаем роль-разрешение с поспрефиксом _not         $role_not  = Yii::$app->authManager->createRole($this->permission.'_not');         $role_not->description = 'Для закрытия разрешения '.$this->permission;         Yii::$app->authManager->add($role_not);  //Добавил в таблицу gr_auth_item поле isnot показывающее роли ли это по сути         return Yii::$app->db->createCommand("UPDATE `gr_auth_item` SET  `isnot`= 1 WHERE type=1 AND  name=:name")             ->bindValue(":name", $this->permission.'_not',PDO::PARAM_STR)             ->execute();     } 

Базовое правило

BaseRule

/*     Проверка на роль supper_admin и запрещающее разрешение     что требуется у всех ролей  */ class BaseRule  extends \yii\rbac\Rule {     public $name ='BaseRule';     public function execute($user_id, $role, $params)     {         if(Yii::$app->user->can('supper_admin') )return 1;         // при налии блокирующего разрешения у пользователя         if(Yii::$app->user->can($role->name.'_not') )return false;          //Даже у роли admin и manager может быть блокирующее разрешение         return true;     } } 

Базовое правило роли

RuleForUser

//Правило для конкретной роли (присутствует у каждой роли кроме supper_admin) //срабатывает при проверке на причастность к роли   ...->can('user') // к примеру user /*    Обычная проверка на причастность к самой роли и базовая проверка от BaseRule */ class RuleForUser extends BaseRule {     public $name='RuleForUser' ;      public function execute($user_id, $role, $params)     {         $parent= parent::execute($user_id, $role, $params);         if($parent===1)return true;         if($parent==false)return false;          if( isset(Yii::$app->authManager->getRolesByUser($user_id)[$role->name]) )return true;         return  false;     } } 

Правило требующее проверки на изменение

RuleUpdateDelete

/* В нем мы также наследуемся от BaseRule */  class RuleUpdateDelete extends BaseRule {     public $name = 'RuleUpdateDelete' ;      public function execute($user_id, $permission, $params)     {        // пропускаем базовые проверки          $parent= parent::execute($user_id, $permission, $params);         if($parent===1)return true;         if($parent==false)return false;         // пропускаем такие роли как admin и customer         if(Yii::$app->user->can('admin') || Yii::$app->user->can('customer'))return true;          if(isset($params['class'])  && method_exists($params['class'], 'can') ){             // проверка принадлежности пользователя к изменяемому объекту             if(method_exists($params['class'], 'can'))             return $params['class']::can($user_id);             else return false;         }         return false;     } } 

От куда берется аргумент $params в методе execute?
Когда мы выполняем проверку

if(!Yii::$app->user->can( 'ваш ключ разрешения',['class'=>static::class])){        throw new \yii\web\ForbiddenHttpException('Access denied role ');  } 

мы передаем вторым параметром массив.В мое случае это класс который я использую для вызова метода одноименного метода can в этом классе для проверки принадлежности конкретного пользователя к изменяемому объекту

Итог

Должены получить функционал с помощью которого можно гибко управлять правами и доступом ко всему сайту иметь возможность без ограничений получить полный доступ с supper_admin ролью, гибко настраивать разрешения у конкретных пользователей.

good luck, Jekshmek
ссылка на оригинал статьи https://habrahabr.ru/post/327170/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *