Гибкая настройка разрешений для ролей 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 включают его использование.

Комментарии

 

MrArthur

Если такую систему строить, то уж лучше удобное расширение использовать.

Ответить

 

vovik

А вы не могли бы написать статью о реализации ulogin как в вашем блоге?

Ответить

 

Akulenok

а можете подсказать что надо доработать чтобы ulogin заработал, я делал авторизацию по этому рецепту, затем закинул ulogin, но авторизация не происходит, просто кидает на главную страницу и все.

Может статью напишите, тема очень актуальная.

Еще нравятся комментарии как у вас. Это расширение какое-то или вы сами написали?

Ответить

 

Akulenok

разобрался, проблема была в save() надо было false дорисовать

Ответить

 

Дмитрий Елисеев

Комментарии самодельные.

Ответить

 

Roman

Интересная статья. Спасибо.

А вот интересно, как в такой модели применять бизнес-правила?

Например, если нужно, чтобы к одной таблице имели доступ две роли: Admin и Moderator. Первый может все, Moderator - может только RU и то, только над своими. необходимо ввести правило по id, но теряется гибкость, и прийдется лезть в код действий контроллеров.

Ответить

 

Дмитрий Елисеев

Как раз для этого сделаны правила в виде классов в Yii2 и атрибут bizRule у правил в Yii1. Но да, придётся вставлять проверки в сами действия контроллеров.

Ответить

 

Антон

Большое спасибо, НО... А как эти роли передать пользователю из БД, чтобы механизм заработал на практике?

Это странно, но нигде не нашел этого или нашел, но какую-то странную реализацию. Как Вы это делаете?

Заранее спсибо за ответ.

Ответить

 

Дмитрий Елисеев

Кое-что описано здесь.

Ответить

 

Виктор

добрый день. скажите пожалуйста можно ли переделать фильтр accessControl или создать свой. который делал проверки и отправлял пользователя без прав доступа на заданную страницу. если можно скажите в какую сторону смотреть, чтобы такое сделать. потому что у меня пользователя редиретит из админки на site/login

Ответить

 

Дмитрий Елисеев

Можно определить deniedCallback для контроллера:

array('allow',
    'deniedCallback' => function() { Yii::app()->controller->redirect(array('/admin/mypage')); },
    'users' => array('@'),
),

Или в init() модуля админки заменить значение Yii::app()->user->loginUrl.

Ответить

 

Виктор

Еще такой вопрос. Проверка:

Yii::app()->admin->checkAccess('admin')

возвращает true. а если в accessRules добавить на экшн:

'roles' => array('admin')

то редирект на site/login. Почему эта проверка в данной функции не проходит?

Ответить

 

Дмитрий Елисеев

Yii::app()->admin или Yii::app()->user?

Ответить

 

Виктор

у меня объект admin для админки и user для сайта

Ответить

 

Дмитрий Елисеев

Вероятно, что вторая проверка в accessRules работает от user.

Ответить

 

Виктор

я тоже так думаю. хотел бы знать есть ли возможность изменить эту проверку?

Ответить

 

Виктор

помогло Yii::app()->setComponent('user', Yii::app()->admin); спасибо

Ответить

 

Vladimir Perepechenko

Здравствуйте

>Этот рецепт является уточнением рецепта Аутентификация и авторизация

эта статья уже недоступна

Я не знаком с yii. Пытаюсь найти подсказки к реализации RBACв своем приложении с микро-фреймворком.

Буду благодарен за ссылку на пропавшую статью на другом ресурсе. Гугл ничего определенного не дал.

Пытаюсь понять не только как создать свои классы, описывающие роли и привилегии. Более всего не понятно как эти ограничения применять при действиях пользователя, если не пользуешься тяжелым фреймворком типа Symfony или yii.

Ответить

Оставить комментарий

Войти | Завести аккаунт | Войти через


(никто не увидит)





Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>