Yii2. КОНТРОЛЬ ДОСТУПА НА ОСНОВЕ РОЛЕЙ (ROLE BASED ACCESS CONTROL)

Эта статья является дополнением и продолжением предыдущей статьи по авторизации в Yii2. В ней мы рассмотрим контроль доступа к модулям, контроллерам, действиям и отдельным функциям на основе встроенного в Yii2 механизма RBAC.

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

Вторым важным аспектом является понимание терминов с которыми работает RBAC:

  • роль (role) — принадлежность пользователя к какой-либо группе, которая определяет его права доступа. Примеры: администратор, модератор, обычный пользователь.
  • разрешение (permission) — возможность доступа к какому-либо функционалу. Примеры: открытие страницы, добавление статьи, редактирование личных данных.
  • правило (rule) — набор требований к пользователю, которым он должен соответствовать для получения доступа к функционалу. Правила могут крепиться как к роли(role), так и к разрешению(permission). Примеры: проверка авторства статьи перед её редактированием, проверка подтверждения контактных данных покупателя в магазине перед оформлением покупки, проверка возраста пользователя перед заказом билета.
  • привязка (assigment) — установка связей между сущностями RBAC. Примеры: привязка роли к пользователю, привязка правила к пользователю.
  • наследование (в Yii2 — addChild()) — передача прав доступа (разрешений и правил). Примеры: наследование роли от роли, роли от разрешения.

Давайте перейдем к обзору реализации RBAC в Yii2, точнее к двум её вариантам: PhpManager и DbManager. Различие между этими двумя реализациями состоит в способе хранения данных, в PHP файлах и базе данных соответственно. К плюсам PhpManager’а можно отнести скорость работы на небольшом количестве пользователей из-за хранения всей инфраструктуры менеджера в файлах, но этот плюс постепенно превращается в минус при росте количества пользователей, так как эти файлы начинают расти в размерах. Сильной стороной  DbManager’а является гибкое управление политикой доступа (для этих задач обычно реализуется раздел в панели администратора) к функционалу и относительно высокая скорость работы при большом количестве пользователей.

В примерах этой статьи используется шаблон приложения basic.

Для подключения RBAC менеджера нужно настроить компонент в двух файлах конфигурации: «../config/web.php» и «../config/console.php».

return [
    // Другие параметры.
    'components' => [
        'authManager' => ['class' => 'yii\rbac\DbManager'], // или 'yii\rbac\PhpManager'.
        // Другие параметры.
    ],
];

Для функционирования DbManager’а требуется настроенное подключение к БД и применение миграции от разработчиков фреймворка:

yii migrate --migrationPath=@yii/rbac/migrations

А для работы PhpManager’а необходимо лишь наличие директории @app/rbac/ с правами записи для сервера.

После подключения RBAC-менеджера необходимо создать требуемые роли, разрешения и правила, которые можно будет присоединять к пользователю. Для небольших систем эти операции выводят в отдельный контроллер (RbacController например) или в миграцию, но вы можете делать это там, где заходите, разницы никакой нет. А для больших систем часто пишут свою надстройку для управления правами доступа или используют стороннее расширение наподобие yii2-admin.

РОЛИ

Для примера создадим три роли: администратор, модератор и пользователь.

$roleAdministrator = Yii::$app->authManager->createRole('administrator');
$roleAdministrator->description = 'Администратор';
Yii::$app->authManager->add($roleAdministrator);
 
$roleModerator = Yii::$app->authManager->createRole('moderator');
$roleModerator->description = 'Модератор';
Yii::$app->authManager->add($roleModerator);
 
$roleUser = Yii::$app->authManager->createRole('user');
$roleUser->description = 'Пользователь';
Yii::$app->authManager->add($roleUser);

После выполнения кода выше в таблице auth_item (для DbManager) или в файле app/rbac/items (для PhpManager) должны появиться записи для наших ролей. Эти роли уже можно использовать для контроля доступа. Чтобы протестировать работу нашего менеджера привяжем каждую из ролей к разным пользователям.

// Достаем пользователя Иванова из БД.
$ivanov = User::findOne(1);
// Достаем роль администратора из RBAC-менеджера.
$roleAdministrator = Yii::$app->authManager->getRole('administrator');
// Привязываем роль администратора к идентификатору Иванова.
Yii::$app->authManager->assign($roleAdministrator, $ivanov->getId());
 
// Достаем пользователя Петрова из БД.
$petrov = User::findOne(2);
// Достаем роль модератора из RBAC-менеджера.
$roleModerator = Yii::$app->authManager->getRole('moderator');
// Привязываем роль модератора к идентификатору Петрова.
Yii::$app->authManager->assign($roleModerator, $petrov->getId());
 
// Достаем пользователя Сидорова из БД.
$sidorov = User::findOne(3);
// Достаем роль модератора из RBAC-менеджера.
$roleUser = Yii::$app->authManager->getRole('user');
// Привязываем роль пользователя к идентификатору Сидорова.
Yii::$app->authManager->assign($roleUser, $sidorov->getId());

В реальных приложениях пользователям присваивают роли при регистрации в системе, здесь же для примера они были добавлены ранее и найдены через findOne() по идентификатору. После выполнения этого кода в таблице auth_assignment (для DbManager) или в файле app/rbac/items (для PhpManager) добавятся записи для связи ролей и пользователей.

Если вы раньше не использовали RBAC менеджер, то скорее всего создавали поле «role» в таблице пользователей и записывали туда присвоенное имя роли. Теперь вся работа с ролями будет происходить через RBAC менеджер и в поле «role» нет надобности.

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

<?php
 
namespace app\controllers;
 
use yii\web\Controller;
use yii\filters\AccessControl;
 
class SiteController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::class,
                'only' => ['manage-users', 'manage-articles'],
                'denyCallback' => function () {
                    die('Доступ запрещен!');
                },
                'rules' => [
                    [
                        'allow'   => true,
                        'actions' => ['manage-users'],
                        'roles'   => ['administrator'],
                    ],
                    [
                        'allow'   => true,
                        'actions' => ['manage-articles'],
                        'roles'   => ['administrator', 'moderator'],
                    ],
                ],
            ],
        ];
    }
 
    public function actionManageUsers()
    {
        echo 'Управление пользователями доступно только администратору.';
    }
 
    public function actionManageArticles()
    {
        echo 'Управление статьями доступно администратору и модератору.';
    }
}

Если вам ранее приходилось использовать фильтры контроля доступа (ACF), то представленный выше способ ограничения доступа покажется знакомым за одним исключением — появилась возможность указывать свои роли в параметре «roles» вместо двух стандартных @ и ?.

РАЗРЕШЕНИЯ

Разрешения позволят нам более гибко управлять доступом. Продолжим развивать предыдущий пример: создадим разрешение на доступ к странице управления пользователями (SiteController/ManageArticles) и добавим его к роли модератора, что бы Петров тоже мог видеть всех пользователей:

// Создаем разрешение на доступ к странице управления пользователями.
$permissionManageUsers = Yii::$app->authManager->createPermission('view_manage_users_page');
 
// Добавляем своё описание к разрешению, чтобы не забыть для чего мы его создавали.
$permissionManageUsers->description = 'Доступ к странице управления пользователями.';
 
// Добавляем разрешение в систему через RBAC менеджер.
Yii::$app->authManager->add($permissionManageUsers);
 
// Ищем роль модератора.
$roleModerator = Yii::$app->authManager->getRole('moderator');
 
// Добавляем (наследуем) разрешение для роли модератора.
Yii::$app->authManager->addChild($roleModerator, $permissionManageUsers);

После создания и привязки разрешения мы можем начать с ней работать. Есть два варианта: через ACF и через метод «can()» в компоненте «User».

public function behaviors()
{
    return [
	'access' => [
	    'class'        => AccessControl::class,
	    'only'         => ['manage-users'],
	    'denyCallback' => function () {
		die('Доступ запрещен!');
	    },
	    'rules'        => [
	    	[
	            'allow'   => true,
		    'actions' => ['manage-users'],
		    'roles'   => ['view_manage_users_page'], // Разрешения как и роли можно использовать тут.
		],
	    ],
        ],
    ];
}
 
public function actionManageUsers()
{
    echo 'Управление пользователями доступно только администратору.';
}

Через метод «can()» в компоненте «User»:

public function actionManageUsers()
{
    if (Yii::$app->user->can('view_manage_users_page')) {
        echo 'Доступ есть.';
    } else {
        echo 'Доступ закрыт.';
    }
 
    echo 'Управление пользователями доступно только администратору.';
}

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

ПРАВИЛА

Правила в RBAC являются «вишенкой на торте» в управлении доступом, так как они позволяют еще точнее задать условия доступа для каждой роли и даже для отдельного пользователя. Еще больше разовьем наш пример и задействуем последнего пользователя — Сидорова, который пока не принимал участия в работе на системой. Дадим возможность редактировать свои статьи всем пользователям.

Для начала создадим само правило проверки авторства статьи:

<?php
 
namespace app\components\rbac;
 
use yii\rbac\Rule;
 
class OwnerRule extends Rule
{
    public $name = 'isOwner';
 
    /**
     * Проверка авторства статьи.
     *
     * @param string|int $user   Идентификатор пользователя.
     * @param Item       $item   Роль или разрешение ассоциированное с этим правилом.
     * @param array      $params Параметры.
     *
     * @return bool Результат проверки, вернет true или false.
     */
    public function execute($user, $item, $params)
    {
        if (!isset($params['article'])) {
            return false;
        }
 
        return $params['article']->user_id == $user;
    }
}

Теперь добавим это правило в систему и прикрепим к роли пользователя:

// Достаем пользователя Сидорова из БД.
$sidorov = User::findOne(3);
// Достаем роль модератора из RBAC-менеджера.
$roleUser = Yii::$app->authManager->getRole('user');
// Привязываем роль пользователя к идентификатору Сидорова.
Yii::$app->uthManager->assign($roleUser, $sidorov->getId());
 
// Создаем объект класса нашего правила (листинг этого класса ниже).
$ruleCheckOwner = new OwnerRule();
// Добавляем правило в систему.
Yii::$app->authManager->add($ruleCheckOwner);
 
// Создаем разрешение "update_own_article" для владельцев статей.
$updateOwnArticle = Yii::$app->authManager->createPermission('update_own_article');
// Добавляем своё описание, чтобы не забыть зачем мы создавали это правило.
$updateOwnArticle->description = 'Правило для проверки авторства статьи.';
// Добавляем наименование используемого правила в наше разрешение.
$updateOwnArticle->ruleName = $ruleCheckOwner->name;
// Добавляем разрешение в систему.
Yii::$app->authManager->add($updateOwnArticle);
 
// Прикрепляем к роли пользователя право редактировать свои статьи.
$auth->addChild($roleUser, $updateOwnArticle);

Осталось опробовать наше правило:

<?php
 
namespace app\controllers;
 
use yii\web\Controller;
use app\models\Article;
 
class SiteController extends Controller
{
    public function actionManageArticle()
    {
        // В тестовой базе данных только одна статья и её автором является Сидоров.
        $article = Article::findOne(1);
 
        if (\Yii::$app->user->can('update_own_article', ['article' => $article])) {
            echo 'Этот пользователь может редактировать статью.';
        } else {
            echo 'Статья не принадлежит пользователю.';
        }
    }
}

На этом описание работы встроенного в Yii2 RBAC менеджера закончено. Если вам показалось, что в управлении доступом на основе ролей трудно разобраться, то это только первое впечатление, оно когда-то было и у меня. После пары часов практики все должно проясниться, главное начать пробовать.

08.01.2020

Категория(-и): PHP, Yii

# # # # #

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