Yii2-advanced: Гибкая настройка Yii2 RBAC (роли, разрешения, правила) +4
У админа может и не быть доступа к разрешению пользователя и в пределах одной роли пользователи могут иметь разный доступ к разрешениям
Как организовать сущности 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
public function actionIndex()
{
if (!\Yii::$app->user->can('index')) {
throw new ForbiddenHttpException('Access denied');
}
return $this->render('index');
}
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);
При создании роли учесть
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;
}
}
При создании разрешения учесть
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
$permission_not = Yii::$app->authManager->createPermission($this->permission.'_not');
$permission_not ->description = 'Для закрытия разрешения '.$this->permission;
Yii::$app->authManager->add($permission_not );
//Добавил в таблицу gr_auth_item поле isnot показывающее тип permission
return Yii::$app->db->createCommand("UPDATE `gr_auth_item` SET `isnot`= 1 WHERE type=2 AND name=:name")
->bindValue(":name", $this->permission.'_not',PDO::PARAM_STR)
->execute();
}
Базовое правило
/*
Проверка на роль supper_admin и запрещающее разрешение,
это требуется у всех ролей
*/
class BaseRule extends \yii\rbac\Rule
{
public $name ='BaseRule';
public function execute($user_id, $permission, $params)
{
if(Yii::$app->user->can('supper_admin') )return 1;
// при налии блокирующего разрешения у пользователя
if(Yii::$app->user->can($permission->name.'_not') )return false;
//Даже у роли admin и manager может быть блокирующее разрешение
return true;
}
}
Базовое правило роли
//Правило для конкретной роли (присутствует у каждой роли кроме 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;
}
}
Правило требующее проверки на изменение
/*
В нем мы также наследуемся от 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
Комментарии (9)
vtvz_ru
23.04.2017 14:35+1Вот сколько уже читаю эти статьи про rbac, и все равно до меня всего доходит. Скажите мне, это правда все так сложно или просто со мной что-то не так? Мне почему-то кажется это все немного запутанным и «чрезмерно универсальным»
Samouvazhektra
23.04.2017 15:52Для многих проектов полный рбак действительно избыточен и сложности в понимание часто вносят как раз туториалы по упрощению и оптимизации.
Попытки нагородить мегауниверсальное ownRule — далеко не всегда хорошее решение. Как-только задача выйдет из стандартного CRUD пойдут костыли
Дело в том что мы не можем назначить пользователю разрешение мы можем только роль назначить при чем даже и не одну.
Что значит — не можем?
assign и revoke методы принимают как роль, так и полномочие
Jekshmek
24.04.2017 00:18Спасибо большое! Я по документации писал, а там как раз методы assign и revoke первым параметром принимают Yii\rbac\Role. Переписал код и теперь даже проще стало, без велосипеда с ролями.
GraneDirval
24.04.2017 00:13В такой маленькой статье столько грамматических ошибок…
Jekshmek
24.04.2017 00:15Исправлю если еще найду ошибки! Маленькая, как написали «на самом деле rbac прост как три копейки»
wispoz
Хорошо, это все относится к проверке доступа к методам, а как быть с доступом к записям из базы: пример, есть пость который может редактировать админ и пользователь (админ все, пользователь только свое)?
Jekshmek
Второй параметр метода Yii::$app->user->can('role',[...]) в который вы передаете нужные вам для этого данные, должен порещать все вопросы связанные с причастностью пользователя.