Гибкая настройка разрешений для ролей RBAC
Практически в каждом проекте возникает необходимость реализовать регистрацию и авторизацию пользователей. В рецепте RBAC и описание ролей в файле подробно описана реализация распределения доступа пользователям по ролям. Этот рецепт является уточнением рецепта Аутентификация и авторизация, в котором и описано использование доступа по ролям. Попробуем сделать настройку ролей пользователей более гибкой.
В примере из рецепта обозначены четыре роли:
return array( 'guest' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Гость', 'bizRule' => null, 'data' => null ), 'user' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Пользователь', 'children' => array( 'guest', ), 'bizRule' => null, 'data' => null ), 'moderator' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Модератор', 'children' => array( 'user', ), 'bizRule' => null, 'data' => null ), 'administrator' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Администратор', 'children' => array( 'moderator', // позволим админу всё, что позволено модератору ), 'bizRule' => null, 'data' => null ), );
Предположим, что мы задали эти же роли. Теперь в панели управления нашего сайта разрешаем доступ к редактированию записей блога только администраторам:
class PostAdminController extends СController { public function filters() { return array( 'accessControl', ); } public function accessRules() { return array( array('allow', 'roles'=>array('administrator'), ), array('deny', 'users'=>array('*'), ), ); } }
и доступ к удалению комментариев только модераторам:
class CommentAdminController extends СController { public function filters() { return array( 'accessControl', ); } public function accessRules() { return array( array('allow', 'roles'=>array('moderator'), ), array('deny', 'users'=>array('*'), ), ); } }
Заметим, что администратор наследует права модератора, поэтому администратор тоже получит доступ к комментариям.
Здесь мы использовали простую иерархию и указывали разрешения на основе ролей:
В любом из проектов однажды может появиться несколько одноуровневых ролей (администратор, модератор, контент-менеджер, представитель компании и т.д.) и потребуется независимо разделять доступ для каждой роли к администрированию разных разделов сайта. Причём должна быть возможность гибкого управления разрешениями. Настраивать их наследование друг от друга не очень удобно, так как каждый должен иметь порой несовместимые разрешения.
Для реализации такого разделения вместо разрешения по ролям удобно воспользоваться системой разрешений на основе операций.
Предположим, что у нас должны быть пять ролей:
- Гость
- Пользователь
- Принятый пользователь
- Модератор
- Контент-менеджер
- Администратор
Пусть модератору нужно доверить работу с комментариями и пользователями, а контент-менеджеру – с блогом и статическими страницами. Также обычный пользователь не должен входить в корпоративный форум и в админку.
Для реализации такого распределения стоит отказаться от разделения по именам ролей в пользу разделения по именам конкретных операций.
Введём несколько атомарных разрешений для каждого раздела сайта, например:
- profile (доступ к личному кабинету)
- forum (доступ к корпоративному форуму)
- control_panel (доступ к панели управления)
- m_pages (доступ к администрированию страниц)
- m_blogs (доступ к администрированию блога)
- m_comments (доступ к администрированию комментариев)
- m_users (доступ к администрированию пользователей)
Теперь настроим иерархию ролей и для каждой роли укажем список разрешённых действий. Иерархия может быть, например, такой:
Теперь в фильтрах доступа наших контроллеров помимо имён ролей можно использовать имена операций.
class PostAdminController extends СController { public function filters() { return array( 'accessControl', ); } public function accessRules() { return array( array('allow', 'roles'=>array('m_blogs'), ), array('deny', 'users'=>array('*'), ), ); } }
class CommentAdminController extends СController { public function filters() { return array( 'accessControl', ); } public function accessRules() { return array( array('allow', 'roles'=>array('m_comments'), ), array('deny', 'users'=>array('*'), ), ); } }
При таком подходе контроллеры имеют зависимость от разрешений m_blogs
и m_comments
вместо зависимости от ролей, что даёт возможность легко вводить новые роли и добавлять каждой роли свои разрешения не переписывая правила контроллеров.
В файле auth.php для ролей и разрешений нужно использовать соответственно записи типов CAuthItem::TYPE_ROLE
и CAuthItem::TYPE_OPERATION
и прописать все наследования:
return array( /* Разрешения */ 'profile' => array( 'type' => CAuthItem::TYPE_OPERATION, 'description' => 'Доступ в личный кабинет', 'bizRule' => null, 'data' => null ), 'forum' => array( 'type' => CAuthItem::TYPE_OPERATION, 'description' => 'Доступ в форум', 'bizRule' => null, 'data' => null ), 'control_panel' => array( 'type' => CAuthItem::TYPE_OPERATION, 'description' => 'Доступ в панель управления', 'bizRule' => null, 'data' => null ), 'm_comments' => array( 'type' => CAuthItem::TYPE_OPERATION, 'description' => 'Управление комментариями', 'bizRule' => null, 'data' => null ), 'm_pages' => array( 'type' => CAuthItem::TYPE_OPERATION, 'description' => 'Управление страницами', 'bizRule' => null, 'data' => null ), 'm_users' => array( 'type' => CAuthItem::TYPE_OPERATION, 'description' => 'Управление пользователями', 'bizRule' => null, 'data' => null ), 'm_blogs' => array( 'type' => CAuthItem::TYPE_OPERATION, 'description' => 'Управление блогом', 'bizRule' => null, 'data' => null ), /* Роли */ 'role_guest' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Гость', 'bizRule' => null, 'data' => null ), 'role_user' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Пользователь', 'children' => array( 'role_guest', 'profile' ), 'bizRule' => null, 'data' => null ), 'role_active_user' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Принятый пользователь', 'children' => array( 'role_user', 'forum' ), 'bizRule' => null, 'data' => null ), 'role_moderator' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Модератор', 'children' => array( 'role_active_user', 'control_panel', 'm_comments', 'm_users', ), 'bizRule' => null, 'data' => null ), 'role_manager' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Контент-менеджер', 'children' => array( 'role_active_user', 'control_panel', 'm_blogs', 'm_pages', ), 'bizRule' => null, 'data' => null ), 'role_admin' => array( 'type' => CAuthItem::TYPE_ROLE, 'description' => 'Администратор', 'children' => array( 'role_active_user', 'control_panel', 'm_pages', 'm_blogs', 'm_comments', 'm_users', ), 'bizRule' => null, 'data' => null ), );
Если же необходимо раздавать правила для каждой операции (например, разрешить пользователю добавлять статьи, но не удалять), то можно пойти дальше к низкоуровневым задачам, например:
blog_read_post blog_add_post blog_edit_post blog_edit_own_post blog_delete_post
Потом ввести роли c наследованием данных задач:
Читатель blog_read_post Редактор blog_add_post blog_edit_own_post Модератор blog_add_post blog_edit_post blog_delete_post
Этот способ рассмотрен в другой подробной статье.
Теперь не нужно будет думать о переписывании кода контроллеров при каждом изменении прав. Все изменения можно будет легко делать в файле или таблице ролей.
Этот принцип разделения на роли и разрешения подойдёт для любого способа хранения (в PHP файле, в базе данных и т.д.), и многие существующие расширения для Yii включают его использование.
Если такую систему строить, то уж лучше удобное расширение использовать.
А вы не могли бы написать статью о реализации ulogin как в вашем блоге?
Немного доработанный официальный компонент. Ничего особенного.
а можете подсказать что надо доработать чтобы ulogin заработал, я делал авторизацию по этому рецепту, затем закинул ulogin, но авторизация не происходит, просто кидает на главную страницу и все.
Может статью напишите, тема очень актуальная.
Еще нравятся комментарии как у вас. Это расширение какое-то или вы сами написали?
разобрался, проблема была в save() надо было false дорисовать
Комментарии самодельные.
Интересная статья. Спасибо.
А вот интересно, как в такой модели применять бизнес-правила?
Например, если нужно, чтобы к одной таблице имели доступ две роли: Admin и Moderator. Первый может все, Moderator - может только RU и то, только над своими. необходимо ввести правило по id, но теряется гибкость, и прийдется лезть в код действий контроллеров.
Как раз для этого сделаны правила в виде классов в Yii2 и атрибут bizRule у правил в Yii1. Но да, придётся вставлять проверки в сами действия контроллеров.
Большое спасибо, НО... А как эти роли передать пользователю из БД, чтобы механизм заработал на практике?
Это странно, но нигде не нашел этого или нашел, но какую-то странную реализацию. Как Вы это делаете?
Заранее спсибо за ответ.
Кое-что описано здесь.
добрый день. скажите пожалуйста можно ли переделать фильтр accessControl или создать свой. который делал проверки и отправлял пользователя без прав доступа на заданную страницу. если можно скажите в какую сторону смотреть, чтобы такое сделать. потому что у меня пользователя редиретит из админки на site/login
Можно определить deniedCallback для контроллера:
Или в init() модуля админки заменить значение Yii::app()->user->loginUrl.
Еще такой вопрос. Проверка:
возвращает true. а если в accessRules добавить на экшн:
то редирект на site/login. Почему эта проверка в данной функции не проходит?
Yii::app()->admin или Yii::app()->user?
у меня объект admin для админки и user для сайта
Вероятно, что вторая проверка в accessRules работает от user.
я тоже так думаю. хотел бы знать есть ли возможность изменить эту проверку?
помогло Yii::app()->setComponent('user', Yii::app()->admin); спасибо
Здравствуйте
>Этот рецепт является уточнением рецепта Аутентификация и авторизация
эта статья уже недоступна
Я не знаком с yii. Пытаюсь найти подсказки к реализации RBACв своем приложении с микро-фреймворком.
Буду благодарен за ссылку на пропавшую статью на другом ресурсе. Гугл ничего определенного не дал.
Пытаюсь понять не только как создать свои классы, описывающие роли и привилегии. Более всего не понятно как эти ограничения применять при действиях пользователя, если не пользуешься тяжелым фреймворком типа Symfony или yii.