X

Yii2 расширяем роли

Потребовалось мне добавить пару ролей в проект Yii2. Задача стояла распределить пользователей на несколько групп. Для этих целей можно использовать RBAC, однако это решение в данном случае было слишком избыточно - требовалось что-то гораздо проще. Т.к. такая задача возникает довольно часто, напишу как можно справиться с ней всего парой строчек кода..

Начнем с модели User. Данные в моем случае хранятся в бд, поэтому User унаследован от ActiveRecord и реализует IdentityInterface.

Добавляем в модель (файл app\models\User.php) необходимые роли и метод проверки ролей can:

class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
{
    const ROLE_ADMIN = 'root';
    const ROLE_USER = 'user';
    const ROLE_ATTORNEY = 'attorney';
..
    public function can($role) {
        return $this->role == $role;
    }

}

Далее добавляем колонку role в таблицу user, использя для этого либо миграции, либо напрямую редактируя таблицу, в зависимости от этапа разработки.

Теперь, создаем новый фильтр AccessRule (файл app\components\AccessRule.php), который наследуется от базового и переопределяет метод match:

<?php
namespace app\components;

class AccessRule extends \yii\filters\AccessRule {

    /**
     * @inheritdoc
     */    protected function matchRole($user)
    {
        if (empty($this->roles)) {
            return true;
        }
        foreach ($this->roles as $role) {
            if ($role === '?') {
                if ($user->getIsGuest()) {
                    return true;
                }
            } elseif ($role === '@') {
                if (!$user->getIsGuest()) {
                    return true;
                }
            } elseif ($user->can($role)) {
                return true;
            } elseif ($identity = $user->getIdentity()) {
                if ($identity->can($role)) {
                    return true;
                }
            }
        }

        return false;
    }
}

Здесь мы добавили в конец проверок, вызов метода can из модели User который мы определили ранее.

Вот и всё, теперь нам осталось в соответствующем контроллере, переопределить namespace AccessRule на наш и можно использовать правила фильтрации как и раньше, но уже с нашими ролями:

namespace app\controllers;

use Yii;
use yii\filters\AccessControl;
use app\components\AccessRule;
..
use app\models\User;


class SiteController extends Controller
{
    /**
     * @inheritdoc
     */    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'ruleConfig' => [
                    'class' => AccessRule::className(),
                ],
                'only' => ['test'],
                'rules' => [
                    [
                        'actions' => ['test'],
                        'allow' => true,
                        'roles' => [User::ROLE_ADMIN],
                    ],
                ],
            ],
        ];
    }

Как видите, задача решается довольно просто. Слава фреймворку Yii 2 и его разработчикам 😀

Категории: Yii
Тэги: rbacyiiроли

Комментарии (14)

  • --------------
    Вау! Да ВЫ просто ВОЛШЕБНИК))) спасли меня!!!!!
    --------------

    Еще вопрос: вместо конструкции

    if (Yii::$app->user->getIdentity()) { .... }

    можно ведь использовать так?! или не правильно? вроде работает..

    if (!Yii::$app->user->isGuest) { .... }

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

    И да, еще вопрос... если использовать yii basic и создать модули user и admin и для каждого модуля свой identity (model user) - то ваш код отказывается работать... начинает обращаться к identity который прописан в конфиге. А я хотел разделить регистрацию/авторизацию как например можно было сделать в yii1 - а вот как в yii2 так сделать? чтобы например создал 3 модуля админа, модератора, редактора.. и чтобы у каждого модуля был своя форма авторизации и регистрации?

    • Привет,

      Yii::$app->user->isGuest

      в коде выглядит так

      ...
      public function getIsGuest()
      {
      return $this->getIdentity() === null;
      }
      ...

      смотри тут https://github.com/yiisoft/yii2

      т.е. можно использовать и так, и так

  • Спасибо за такой простой хак. Есть вопрос: а как можно проверять во вьюшках? например чтобы выводить для каждой роли свою инфу...? Пробовал так
    --
    User::can(User::ROLE_USER) - но пишет что не известно свойство.

    а так вообще не видит метода
    -
    Yii::$app->user->identity->can(USER::ROLE_ADMIN);

    как мне по вашему способу ролей проверять эти роли во вьюшках?

    пробовал в модель User добавить геттеры и сеттеры, но не робит.

    public static function getRole(){
    return !empty($this->role) ? $this->role : "";
    }

    public static function setRole(){
    return $this->role = $role;
    }

    Подскажите пожалуйста?!

    • Почему такие извращения? дело в том,что у меня был самописный проект..и каждый тип пользователя ( админ, редактор, модератор) находятся в отдельных таблицах. На yii1 я быстро перешел... а вот на yii2 никак не могу решить проблему.. Как при авторизации понять, в какой таблице искать? Когда все пользователи в одной таблице находятся- это понятно... А вот если в разных? Может вопрос кажется глупым, но я уже мозг сломал свой..никак не получается реализовать. Разные identity почему то не работают... Как бы работают...авторизация проходит..но вот потом почему то подключается identity который указан в конфиге.... Брр... сумбур уже в голове. А так хочется перелезть с yii1 на yii2

      • Если еще не знаешь, то на гитхабе в официальном репозитории есть перевод доков на русский, там неплохо все описано, в том числе роли (через RBAC): https://github.com/yiisoft/yii2/blob/master/docs/guide-ru/security-authorization.md

        Identity класс не обязательно должен наследоваться от ActiveRecord, т.е. ты можешь сделать внутри него кастомную логику которая будет обращаться к разным таблицам. Кроме того, если там совсем все сложно, то можно вообще отказаться от Identity, определить свою модель аутентификации и использовать её, но это на крайний случай, т.к. любые не стандартные решения, в большинстве случаев затратны в плане дальнейшей поддержки.

        • не надо ляля, в дках ничего толком не описано и нет примеров. Запарили советовать всякую шляпу

        • или создать модели для каждой талицы (админ, редактор, пользователь) и уже реализовать identity и работать с конкретной моделью? так?

          • Когда у тебя 3и таблицы, то Identity должен понимать из какой таблицы (модели) ему брать информацию. У него есть обязательные методы:

            ..
            public function getId()
            public static function findIdentity($id)
            ..

            так вот getId у тебя должен возвращать уникальный id для 3х таблиц, а findIdentity находить нужный по id

            не проверял, но идея кодом будет выглядеть примерно так


            namespace app\models;
            class Admin extends ActiveRecord {
            ...
            }
            class Editor extends ActiveRecord {
            ...
            }


            namespace app\models;

            class User implements IdentityInterface
            {
            public $id;
            public $model;

            public static function findIdentity($id)
            {
            $ret = null;

            list($model, $id) = explode('::', $id);

            if (class_exists($model)) {
            $ret = new static;
            $ret->id = $id;
            $ret->model = $model;
            }

            return $ret;
            }

            public static function findIdentityByAccessToken($token, $type = null)
            {
            return null;
            }

            public function getId()
            {
            return $this->model.'::'.$this->id;
            }

            public function getAuthKey()
            {
            $model = $this->getModelInstance()->findOne($id);
            return $model ? $model->authKey : null;
            }

            public function validateAuthKey($authKey)
            {
            return $this->getAuthKey() === $authKey;
            }

            public function getModelInstance(){
            $model = $this->model;
            return new $model;
            }
            }

            Пример дальнейшего использования:

            // логин в контролере
            $identity = Yii::$app->findIdentity('\app\models\Admin::1');
            Yii::$app->user->login($identity);

            // вывод имени пользователя во вьюхе
            $identity = Yii::$app->user->getIdentity();
            if ($identity) {
            $identity->getModelInstance()->username;
            }

            Думаю, идея понятна, но надо проверять..

            Другой способ, это завести еще одну таблицу, например так:

            ======================
            табл user
            -------
            id: 1
            model: admin_user
            model_id:1
            -------
            id: 2
            model: editor_user
            model_id:1
            ======================
            табл admin_user
            -------
            id: 1
            username: Admin
            ======================

            и определить в классе user метод getModel, который будет брать запись из зависимой таблицы на основе полей model и model_id

            Еще один способ, отказаться вообще от компонента \yii\web\User и написать свой компонент аутентификации со своим блэкджеком и дальше использовать его.

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

          Я вот представляю так: что делаем свою логику , а уже потом просто вызвать метод компанента из коробки - Yii::$app->getUser()->login($user) ..... или не так? но опять же, все равно нужно наследовать identity -

        • --- т.е. ты можешь сделать внутри него кастомную логику которая будет обращаться к разным таблицам --

          Вроде как понимаю, и с другой стороны все равно не пойму..... Краткий примерчик бы, чтоб глянуть.

      • спасибо за помощь! я решил проблему))) оказалось намного все проще с вашим приведенным в статье кодом))) а я начал велосипед изобретать))

    • Привет, попробуй так во вьюшке:

      if (Yii::$app->user->getIdentity()) {
      if (Yii::$app->user->getIdentity()->can(\app\models\User::ROLE_USER)) {
      // разрешено
      }
      }