Сервис на Yii2: Добавление RBAC для разграничения прав

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

Предыдущие части | Исходники на GitHub

Сейчас наша панель управления закрыта на примитивном уровне только от незалогиненных пользователей:

namespace app\modules\admin;
 
use yii\filters\AccessControl;
 
class Module extends \yii\base\Module
{
    ...
 
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    [
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                ],
            ],
        ];
    }
 
    ...
}

Но нам нужно будет сделать так, чтобы он был доступен только администраторам.

Как мы уже рассматривали в вебинаре по RBAC, в Yii2 имеется два встроенных менеджера по управлению ролями и привязке их к пользователю. Это PhpManager, хранящий данные в виде массивов в файлах, и DbManager, сохраняющий всё в таблицах в базе данных. Мы подробно обсудили плюсы и минусы каждого подхода в том видео, поэтому здесь теории будет не очень много. Рассмотрим практику внедрения RBAC в проект.

В любом случае, достаточно подключить любой из них как компонент authManager в конфигурационном файле:

'components' => [
    ...
    'authManager' => [
        'class' => 'yii\rbac\PhpManager',
    ],
],

и его методами вроде таких:

$auth = Yii::$app->authManager;
$user = $auth->createRole('user');
$user->description = 'User';
$auth->add($user);
 
$admin = $auth->createRole('admin');
$admin->description = 'Admin';
$auth->add($admin);
 
$auth->addChild($admin, $user);

создавать роли и методом $auth->assign($role, $userId) привязывать роль к пользователю. А сам менеджер уже будет сохранять эту информацию в своих файлах или таблицах.

Для нашего случая нужны всего две роли user и admin. Можно создать только их и проверять доступ по этим ролям. Но более гибкий и предпочтительный вариант – это использование так называемых «разрешений» вроде adminPanel и подобных и привязка их к ролям.

Использование имён разрешений вроде adminPanel в модулях предпочтительнее имён ролей вроде user или admin, так как на каждом сайте можно будет делать разных админов и пользователей, в любой последовательности привязывая к ним ваши разрешения.

Чтобы не создавать всё вручную, можно создать консольный контроллер, в действии которого производить всё построение ролей и разрешений:

namespace app\commands;
 
use Yii;
use yii\console\Controller;
 
/**
 * RBAC generator
 */
class RbacController extends Controller
{
    /**
     * Generates roles
     */
    public function actionInit()
    {
        $auth = Yii::$app->getAuthManager();
        $auth->removeAll();
 
        $adminPanel = $auth->createPermission('adminPanel');
        $adminPanel->description = 'Admin panel';
        $auth->add($adminPanel);
 
        $user = $auth->createRole('user');
        $user->description = 'User';
        $auth->add($user);
 
        $admin = $auth->createRole('admin');
        $admin->description = 'Admin';
        $auth->add($admin);
 
        $auth->addChild($admin, $user);
        $auth->addChild($admin, $adminPanel);
 
        $this->stdout('Done!' . PHP_EOL);
    }
}

Здесь мы создаём роли user и admin, для простоты наследуем admin от user (чтобы администратор мог делать всё, что позволено пользователю). Также создаём разрешение adminPanel, по которому будем проверять доступ в панель управления, и присваиваем это разрешение только роли admin.

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

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

Сейчас у нас есть всего одно разрешение, относящееся к модулю admin. А мы договорились, что всё, что относится к конкретному модулю мы будем помещать в его директорию. Так что создадим в модуле папку rbac и создадим там класс с константой:

namespace app\modules\admin\rbac;
 
class Rbac
{
    const PERMISSION_ADMIN_PANEL = 'permAdminPanel';
}

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

Теперь впишем наше значение в фильтр модуля:

namespace app\modules\admin;
 
use app\modules\admin\rbac\Rbac as AdminRbac;
use yii\filters\AccessControl;
use Yii;
 
class Module extends \yii\base\Module
{
    ...
 
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    [
                        'allow' => true,
                        'roles' => [Rbac::PERMISSION_ADMIN_PANEL],
                    ],
                ],
            ],
        ];
    }
 
    ...
}

Также в пункт главного меню в views/layouts/main.php:

use app\modules\admin\rbac\Rbac as AdminRbac;
...
echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'activateParents' => true,
    'items' => array_filter([
        ['label' => Yii::t('app', 'NAV_HOME'), 'url' => ['/main/default/index']],
        ...
        Yii::$app->user->can(AdminRbac::PERMISSION_ADMIN_PANEL) ?
            ['label' => Yii::t('app', 'NAV_ADMIN'), 'url' => ['/admin/default/index']] :
            false,
        ...
    ]),
]);
NavBar::end();

и в наш консольный контроллер:

use app\modules\admin\rbac\Rbac as AdminRbac;
...
class RbacController extends Controller
{
    /**
     * Generates roles
     */
    public function actionInit()
    {
        $auth = Yii::$app->getAuthManager();
        $auth->removeAll();
 
        $adminPanel = $auth->createPermission(AdminRbac::PERMISSION_ADMIN_PANEL);
        $adminPanel->description = 'Admin panel';
        $auth->add($adminPanel);
 
        $user = $auth->createRole('user');
        $user->description = 'User';
        $auth->add($user);
 
        $admin = $auth->createRole('admin');
        $admin->description = 'Admin';
        $auth->add($admin);
 
        $auth->addChild($admin, $user);
        $auth->addChild($admin, $adminPanel);
 
        $this->stdout('Done!' . PHP_EOL);
    }
}

Одно из достоинств наличия класса-справочника с константами – возможность быстро просмотреть весь список в одном месте. Не нужно каждый раз искать по контроллерам и представлениям какие же правила имеются в данном модуле. Да и можно спокойно переименовать константу средствами самой IDE, и во всём проекте она переименуется автоматически.

Роли и разрешения готовы. Теперь их надо как-то присвоить пользователям. Сделаем для этого ещё один консольный контроллер:

namespace app\commands;
 
use app\modules\user\models\User;
use Yii;
use yii\console\Controller;
use yii\console\Exception;
use yii\helpers\ArrayHelper;
 
/**
 * Interactive console roles manager
 */
class RolesController extends Controller
{
    /**
     * Adds role to user
     */
    public function actionAssign()
    {
        $username = $this->prompt('Username:', ['required' => true]);
        $user = $this->findModel($username);
        $roleName = $this->select('Role:', ArrayHelper::map(Yii::$app->authManager->getRoles(), 'name', 'description'));
        $authManager = Yii::$app->getAuthManager();
        $role = $authManager->getRole($roleName);
        $authManager->assign($role, $user->id);
        $this->stdout('Done!' . PHP_EOL);
    }
 
    /**
     * Removes role from user
     */
    public function actionRevoke()
    {
        $username = $this->prompt('Username:', ['required' => true]);
        $user = $this->findModel($username);
        $roleName = $this->select('Role:', ArrayHelper::merge(
            ['all' => 'All Roles'],
            ArrayHelper::map(Yii::$app->authManager->getRolesByUser($user->id), 'name', 'description'))
        );
        $authManager = Yii::$app->getAuthManager();
        if ($roleName == 'all') {
            $authManager->revokeAll($user->id);
        } else {
            $role = $authManager->getRole($roleName);
            $authManager->revoke($role, $user->id);
        }
        $this->stdout('Done!' . PHP_EOL);
    }
 
    /**
     * @param string $username
     * @throws \yii\console\Exception
     * @return User the loaded model
     */
    private function findModel($username)
    {
        if (!$model = User::findOne(['username' => $username])) {
            throw new Exception('User is not found');
        }
        return $model;
    }
}

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

php yii roles/assign

Он попросит нас ввести логин пользователя и роль.

После этого можно увидеть, что всё у нас попало в assignments.php:

return [
    1 => [
        'admin',
    ],
];

Пользователю с ID=1 присвоилась роль admin.

Если подключить аналогично DbManager, применить его миграции, выполнить php yii rbac/init и привязать аналогично роль к пользователю, то это всё появится в соответствующих таблицах в базе данных.

Для любого сайта достаточно выбрать PhpManager или DbManager и использовать его. Это стандартная практика, применимая повсюду. Если же хочется рассмотреть другие альтернативы, то об одной из них поговорим в этой статье.

Но при использовании DbManager нам не особо хочется дёргать каждый раз базу данных для перебора ролей и разрешений.

Менеджер PhpManager удобен высокой производительностью, так как он загружает все роли одним запросом к своим файлам вместо рекурсивных запросов к базе данных. Но при большом числе пользователей файл assignments.php станет большим и неповоротливым.

Мы можем пойти альтернативным путём, храня привязку к роли прямо в поле role модели User и используя подход с ролями по умолчанию defaultRoles и распределением ролей общим правилом GroupRule. Но это «костыль», так как с ним не будут работать getRolesByUser(), assign() и прочие системные методы.

Вместо этого можно схитрить, как мы и говорили в вебинаре. А именно, оставить все роли в файлах, а хранение роли поместить в поле role в модели пользователя и доработать PhpManager на получение роли из этого поля (вместо файла assignments.php). Всё равно по стандартам чистого RBAC пользователю не нужно присваивать несколько ролей.

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

use yii\db\Migration;
 
class m160310_085103_add_user_role_field extends Migration
{
    public function up()
    {
        $this->addColumn('{{%user}}', 'role', $this->string(64));
 
        $this->update('{{%user}}', ['role' => 'user']);
    }
 
    public function down()
    {
        $this->dropColumn('{{%user}}', 'role');
    }
}

Теперь напишем свой гибридный AuthManager. В Yii2 менеджер монолитен, так что просто поменять хранилище ролей и привязок к пользователю мы не сможем. Вместо этого поправим функциональность самого PhpManager.

Отнаследуемся от класса yii\rbac\PhpManager и переопределим те методы, которые работают с его полем $this->_assignments. В них будем напрямую обращаться к нашей модели, а не брать значения, полученные из файла assignments.php. В примитивном случае для определения роли нужно переопределить только метод getAssignments, так как в методе checkAccess вызывается именно он. В итоге достаточно такого переопределения:

namespace app\components;
 
use app\modules\user\models\User;
use yii\rbac\Assignment;
use yii\rbac\PhpManager;
use Yii;
 
class AuthManager extends PhpManager
{
    public function getAssignments($userId)
    {
        if ($userId && $user = $this->getUser($userId)) {
            $assignment = new Assignment();
            $assignment->userId = $userId;
            $assignment->roleName = $user->role;
            return [$assignment->roleName => $assignment];
        }
        return [];
    }
 
    /**
     * @param integer $userId
     * @return null|\yii\web\IdentityInterface|User
     */
    private function getUser($userId)
    {
        $webUser = Yii::$app->get('user', false);
        if ($webUser && !$webUser->getIsGuest() && $webUser->getId() == $userId) {
            return $webUser->getIdentity();
        } else {
            return User::findOne($userId);
        }
    }
}

Но такой менеджер слишком примитивен. Его можно использовать только для чтения ролей, а не для их изменения. Для большей совместимости с оригинальным классом переопределим и методы вроде assign и revoke:

namespace app\components;
 
use app\modules\user\models\User;
use yii\rbac\Assignment;
use yii\rbac\PhpManager;
use Yii;
 
class AuthManager extends PhpManager
{
    public function getAssignments($userId)
    {
        if ($userId && $user = $this->getUser($userId)) {
            $assignment = new Assignment();
            $assignment->userId = $userId;
            $assignment->roleName = $user->role;
            return [$assignment->roleName => $assignment];
        }
        return [];
    }
 
    public function getAssignment($roleName, $userId)
    {
        if ($userId && $user = $this->getUser($userId)) {
            if ($user->role == $roleName) {
                $assignment = new Assignment();
                $assignment->userId = $userId;
                $assignment->roleName = $user->role;
                return $assignment;
            }
        }
        return null;
    }
 
    public function getUserIdsByRole($roleName)
    {
        return User::find()->where(['role' => $roleName])->select('id')->column();
    }
 
    public function assign($role, $userId)
    {
        if ($userId && $user = $this->getUser($userId)) {
            $assignment = new Assignment([
                'userId' => $userId,
                'roleName' => $role->name,
                'createdAt' => time(),
            ]);
            $this->setRole($user, $role->name);
            return $assignment;
        }
        return null;
    }
 
    public function revoke($role, $userId)
    {
        if ($userId && $user = $this->getUser($userId)) {
            if ($user->role == $role->name) {
                $this->setRole($user, null);
                return true;
            }
        }
        return false;
    }
 
    public function revokeAll($userId)
    {
        if ($userId && $user = $this->getUser($userId)) {
            $this->setRole($user, null);
            return true;
        }
        return false;
    }
 
    /**
     * @param integer $userId
     * @return null|\yii\web\IdentityInterface|User
     */
    private function getUser($userId)
    {
        $webUser = Yii::$app->get('user', false);
        if ($webUser && !$webUser->getIsGuest() && $webUser->getId() == $userId) {
            return $webUser->getIdentity();
        } else {
            return User::findOne($userId);
        }
    }
 
    /**
     * @param User $user
     * @param string $roleName
     */
    private function setRole(User $user, $roleName)
    {
        $user->role = $roleName;
        $user->updateAttributes(['role' => $roleName]);
    }
}

В конфигурационном файле config/common.php поменяем теперь оригинальный PhpManager на свой:

'components' => [
    ...
    'authManager' => [
        'class' => 'app\components\AuthManager',
    ],
],

Теперь можно попробовать перегенерировать роли:

php yii rbac/init

и заново попробовать привязать их к пользователям:

php yii roles/assign

При этом файл assignments.php должен остаться пустым, а поле role в таблице user в базе заполниться новой ролью.

Хранение ролей мы организовали и поле role в таблицу добавили. Осталось добавить вывод и установку роли в админку.

Управление ролями

Для отображения роли и присваивания её при управлении пользователями добавим правила валидации и надпись для поля role в модель User:

namespace app\modules\user\models;
...
class User extends ActiveRecord implements IdentityInterface
{
    ...
 
    public function rules()
    {
        return [
            ...
            ['role', 'string', 'max' => 64],
        ];
    }
 
    public function attributeLabels()
    {
        return [
            ...
            'role' => Module::t('module', 'USER_ROLE'),
        ];
    }
}

И в модели backend\User добавим это поле в сценарии валидации:

namespace app\modules\user\models\backend;
...
class User extends \app\modules\user\models\User
{
    ...
 
    public function scenarios()
    {
        $scenarios = parent::scenarios();
        $scenarios[self::SCENARIO_ADMIN_CREATE] = ['username', 'email', 'status', 'role', 'newPassword', 'newPasswordRepeat'];
        $scenarios[self::SCENARIO_ADMIN_UPDATE] = ['username', 'email', 'status', 'role', 'newPassword', 'newPasswordRepeat'];
        return $scenarios;
    }
}

Аналогично добавим новое поле в поисковую модель backend\search\UserSearch:

$query->andFilterWhere([
    'id' => $this->id,
    'status' => $this->status,
    'role' => $this->role,
]);

И в форму в представлении modules/user/views/backend/_form.php добавим выпадающий список ролей:

<div class="user-form">
 
    <?php $form = ActiveForm::begin(); ?>
 
    <?= $form->field($model, 'username')->textInput(['maxlength' => true]) ?>
 
    ...
 
    <?= $form->field($model, 'status')->dropDownList(User::getStatusesArray()) ?>
 
    <?= $form->field($model, 'role')->dropDownList(ArrayHelper::map(Yii::$app->authManager->getRoles(), 'name', 'description')) ?>
 
    ...
 
    <?php ActiveForm::end(); ?>
 
</div>

В Yii нет построителя форм, в который можно было бы спрятать такие повороты судьбы с дёрганием authManager из представления. Если бы мы использовали отдельную модель для формы, то бы тоже могли спрятать это туда. Чтобы это не выглядело так кощунственно, лучше передать массив для выпадающего списка из контроллера:

<?= $form->field($model, 'role')->dropDownList($rolesList) ?>

или даже сочинить свой InputWidget. Ну это сейчас не так важно.

Здесь мы могли бы последовать принципам профессиональной разработки и переписать модуль user, но управление пользователями – это не главная часть нашего приложения. Выделением слоя предметной области, отделением его от слоя отображения и продумыванием архитектуры мы займёмся в основном модуле проектов. А здесь вместо настоящих моделей оставим стандартные классы ActiveRecord и обычный CRUD.

Теперь нам нужно вывести роль в GridView. Для этого сделаем колонку, которая будет отображать описание роли, выделяя привилегированных пользователей красными бейджиками:

namespace app\modules\user\widgets\backend\grid;
 
use yii\grid\DataColumn;
use yii\helpers\Html;
use Yii;
 
class RoleColumn extends DataColumn
{
    public $defaultRole = 'user';
 
    protected function renderDataCellContent($model, $key, $index)
    {
        $value = $this->getDataCellValue($model, $key, $index);
        $label = $value ? $this->getRoleLabel($value) : $value;
        $class = $value == $this->defaultRole ? 'primary' : 'danger';
        $html = Html::tag('span', Html::encode($label), ['class' => 'label label-' . $class]);
        return $value === null ? $this->grid->emptyCell : $html;
    }
 
    private function getRoleLabel($roleName)
    {
        if ($role = Yii::$app->authManager->getRole($roleName)) {
            return $role->description;
        } else {
            return $roleName;
        }
    }
}

И подключим эту колонку в наш modules/user/views/backend/index.php:

<?= GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        'id',
        ...
        [
            'class' => LinkColumn::className(),
            'attribute' => 'username',
        ],
        'email:email',
        ...
        [
            'class' => RoleColumn::className(),
            'filter' => ArrayHelper::map(Yii::$app->authManager->getRoles(), 'name', 'description'),
            'attribute' => 'role',
        ],
 
        ['class' => ActionColumn::className()],
    ],
]); ?>

Чтобы видеть все присвоенные роли:

Теперь дополним фронтенд.

Доработка формы регистрации

Дополним наш модуль новым параметром $defaultRole:

namespace app\modules\user;
 
use Yii;
 
class Module extends \yii\base\Module
{
    /**
     * @var string
     */
    public $defaultRole = 'user';
    /**
     * @var int
     */
    public $emailConfirmTokenExpire = 259200; // 3 days
    /**
     * @var int
     */
    public $passwordResetTokenExpire = 3600;
 
    public static function t($category, $message, $params = [], $language = null)
    {
        return Yii::t('modules/user/' . $category, $message, $params, $language);
    }
}

В саму форму регистрации добавим поле $_defaultRole, которое будем передавать в конструктор и присваивать пользователю:

namespace app\modules\user\models\frontend\form;
 
use app\modules\user\models\User;
use app\modules\user\Module;
use yii\base\Model;
use Yii;
 
/**
 * Signup form
 */
class SignupForm extends Model
{
    public $username;
    public $email;
    public $password;
    public $verifyCode;
 
    private $_defaultRole;
 
    public function __construct($defaultRole, $config = [])
    {
        $this->_defaultRole = $defaultRole;
        parent::__construct($config);
    }
 
    ...
 
    public function signup()
    {
        if ($this->validate()) {
            $user = new User();
            $user->username = $this->username;
            $user->email = $this->email;
            $user->setPassword($this->password);
            $user->status = User::STATUS_WAIT;
            $user->role = $this->_defaultRole;
            $user->generateAuthKey();
            $user->generateEmailConfirmToken();
            ...
            return $user;
        }
 
        return null;
    }
}

И в контроллере будем передавать в форму этот параметр через конструктор:

namespace app\modules\user\controllers\frontend;
 
...
 
class DefaultController extends Controller
{
    ...
    public function actionSignup()
    {
        $model = new SignupForm($this->module->defaultRole);
        if ($model->load(Yii::$app->request->post())) {
            if ($user = $model->signup()) {
                Yii::$app->getSession()->setFlash('success', Module::t('module', 'FLASH_EMAIL_CONFIRM_REQUEST'));
                return $this->goHome();
            }
        }
 
        return $this->render('signup', [
            'model' => $model,
        ]);
    }
}

На этом с ролями пока всё.

Мы добавили к проекту поддержку ролей и разрешений. При этом мы сделали собственный «гибридный» вариант хранения ролей, скрестив PhpManager с полем role в таблице модели. При этом все стандартные методы вроде assign, revoke и getRolesByUser остались полностью рабочими.

В следующей части подготовим этот компонент для совместного использования в других проектах, научившись выкладыввть свои расширения на GitHub и публиковать их как пакеты Composer:

Публикация расширений на GitHub и Packagist

Не забудьте подписаться на рассылку статей в сайдбаре или вместе с нами записаться и «позажигать» на бесплатных вебинарах. До встречи!

Комментарии

 

LAV45
class AuthManager extends PhpManager { ... }

> Но такой менеджер слишком примитивен.

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

Добавьте User setRole и getRole и AuthManager уже не будет казаться таким примитивеным.

class User extends ActiveRecord implements IdentityInterface
{
...
    public function setRole($name)
    {
        $auth = Yii::$app->authManager;

        if (!empty($name)) {
            $userRoles = array_keys($auth->getRolesByUser($this->id));
            if (!isset($userRoles[0]) || $userRoles[0] != $name) {
                $role = $auth->getRole($name);
                $event = $this->getIsNewRecord() ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE;

                $this->on($event, function () use ($auth, $role) {
                    $auth->revokeAll($this->id);
                    $auth->assign($role, $this->id);
                });
            }
        } elseif ($this->getIsNewRecord() === false) {
            $auth->revokeAll($this->id);
        }
    }

    public function getRole()
    {
        $auth = Yii::$app->authManager;
        $roles = $auth->getRolesByUser($this->id);
        return !empty($roles) ? array_keys($roles)[0] : null;
    }

    public static function getRoleList()
    {
        $data = Yii::$app->authManager->getRoles();
        $roles = ArrayHelper::getColumn($data, 'description');
        return $roles;
    }
}
Ответить

 

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

И как это относится к избавлению от хранения assignments?

Ответить

 

lav45

Вам ненужно избавляться от хранения assignments.
Этот метод поможет вам избавится от переопределения класса PhpManager, и исользовать стандактный authManager с произвольными настройками. Пускай разраюотчик решает использовать ему DbManager или PhpManager.
Нет необходимости добавлять User поле role т.к. эта информация будет хранится в Yii::$app->authManager->getRolesByUser($user_id).

Ответить

 

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

Да, обычно так и вывожу через getRolesByUser($id), если использую стандартные менеджеры.

Здесь аналогично разработчик может использовать (или нет) любой свой менеджер, подписавшись на события модели и реализовать такой же assign() в своём обработчике.

Ответить

 

Гераклит Вяткин

Если я, например, использую DbManager и вывожу роли в GridView, то у меня для каждого пользователя на странице GridView будет отдельный запрос чтобы выяснить какие у него есть роли?

Ответить

 

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

Да.

Ответить

 

Дмитрий Кривошеин

Как всегда отличный материал!!! Спасибо Дима за твой труд!

Ответить

 

Andrey

Отличная статья!

Ответить

 

Олег

Дмитрий, ошибка у Вас :-)

$ yii roles/assign
Username: admin
Role: [user,admin,?]: admin
Error: Getting unknown property: yii\console\Application::user
Ответить

 

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

Исправил.

Ответить

 

Олег

Пару мелочей по модулю user.

'BUTTON_SEND' => 'Send',
'BUTTON_SEND' => 'Отправить',

views/frontend/default/passwordReset.php

<?= Html::submitButton(Module::t('module', 'BUTTON_SAVE'), ['class' => 'btn btn-primary', 'name' => 'reset-button']) ?>

P.S:. Лень кидать pull request'ы.

Ответить

 

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

Спасибо! Исправил.

Ответить

 

...

Спасибо, отличный материал!

Ответить

 

Николай Куропятников

Как привязать роль к не авторизованному гостю? Чтобы в RBAC была роль Гость.

Ответить

 

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

Добавить роль guest и прописать её в defaultRoles.

Ответить

 

Anton Gubarev

Дмитрий поясните пожалуйста по Module::t(). Не нашел нигде описания это возможности. В документации только Yii::t()

Ответить

 

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

 

Anton Gubarev

Спасибо!

Ответить

 

Михаил

Здравствуйте, скажите а есть ли простой способ использовать RBAC вместе с activeDataProvider?
Проверять, что пользователь может читать статью которая выбирается.

Ответить

 

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

А в чём именно проблема?

Ответить

 

Михаил

В данном случае не ясно как это будет выглядеть в принципе.
Мы имеем методы can у user, мы имеем папку с правами RBAC, где лежат правила.
Затем есть контроллер который допустим выводит новости.
Все как мы можем использовать rbac это вызвать Yii::$app->user->can, как при этом заставить activeDataProvider фильтровать данные - не понятно.

Приложение это REST Full API.
Если вы знаете статью на эту тему, буду признателен.
Спасибо)

Ответить

 

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

Для выборок фильтруем хардкорно:

Post::find()->andWhere(['user_id' => Yii::$app->user->id])
Ответить

 

Иван Зайцев

Решил пропустить все, что связано с тестированием, и теперь пожалел... в какой-то момент понял, что у меня не выполняется действие logout модуля user:

Method Not Allowed. This url can only handle the following request methods: POST.

в виджете меню все прописано:

'url' => ['/user/default/logout'],
'linkOptions' => ['data-method' => 'post']]

А если закомментировать в контроллере:

'actions' => [
    // 'logout' => ['post'],
],

то logout начинает работать. Зачем вообще отправлять этот Url методом post?
И так и не могу понять, почему перестало работать...

Ответить

 

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

> Зачем вообще отправлять этот Url методом post?

Все что-то производящие операции вроде delete для безопасности (и для защиты от кеширования) делают через POST. А иначе кто-то впишет такую "картинку" или ссылку:

<img src="/admin/user/delete/1" />
<a href="/admin/user/delete/1">здесь</a>

и у администратора этот адрес сработает и в пользователя нечаянно удалит. Аналогично /logout сделано работающим только через POST.

Если вдруг перестало работать после обновления фреймворка, то очистите папку web/assets и обновите страницу.

Ответить

 

Иван Зайцев

> Все что-то производящие операции вроде delete для безопасности делают через POST
Спасибо за пояснение)

Очистил папку web/assets (оставил в ней только .gitignore), не помогло...
Заметил, что в модуле admin при нажатии на /logout, действие выполняется.
а в модуле main тот же самый /logout не выполняется.
Описаны они одинаково. пока ищу причину.

Ответить

 

Иван Зайцев

Все, решил проблему, дело было в закомментированном фрагменте:

public $depends = [
    //'yii\web\YiiAsset',
    //'yii\bootstrap\BootstrapAsset',
];

раскомментировал подключение 'yii\web\YiiAsset' и все заработало
а если раскомментировать строку 'yii\bootstrap\BootstrapAsset' то возникает проблема в верскте, конфликты в css файлах

Ответить

 

SerF SerF

Странно, все сделал как написано
не сохраняются childs и assignments, ни в базе, ни в файлах
отсюда не работает Yii::$app->user->can(AdminRbac::PERMISSION_ADMIN_PANEL

Ответить

 

Sergey Aver

Куда мне нужно копать если не работает команда

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

Вернее она обрабатывается, в таблицу миграций записывается запись, но вот сами таблицы rbac не добавляются

Ответить

 

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

Весьма странно. С таким не сталкивался.

Ответить

 

Роман

Здрвствуйте.
Запускаю команду

php yii roles/assign

валятся ошибки:

Exception 'yii\base\InvalidConfigException' with message 'Unknown bootstrapping

in Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\base\Application.

Stack trace:
#0 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\base\Application.
#1 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\console\Applicati
#2 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\base\Object.php(1
#3 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\base\Application.
#4 Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\console\Applicati
#5 Z:\Programs\Sites\yii2-basic-elisdn\www\yii(28): yii\console\Application->__c
#6 {main}

Попробовал запустить

php yii

- тоже самое.
Видимо где-то в конфиге ошибка закралась?
Где смотреть?

Ответить

 

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

Смотреть в полном тексте ошибки 'Unknown bootstrapping...'.

Ответить

 

Роман

Извиняюсь, Шторм урезает почему-то.

Вот такой полный текст ошибок:

Exception 'yii\base\InvalidConfigException' with message 'Unknown bootstrapping component ID: gii'

in Z:\Programs\Sites\yii2-basic-elisdn\www\vendor\yiisoft\yii2\base\Application.php:306

Stack trace:
#0 vendor\yiisoft\yii2\base\Application.php(267): yii\base\Application->bootstrap()
#1 vendor\yiisoft\yii2\console\Application.php(120): yii\base\Application->init()
#2 vendor\yiisoft\yii2\base\Object.php(107): yii\console\Application->init()
#3 vendor\yiisoft\yii2\base\Application.php(206): yii\base\Object->__construct(Array)
#4 vendor\yiisoft\yii2\console\Application.php(85): yii\base\Application->__construct(Array)
#5 yii(28): yii\console\Application->__construct(Array)
#6 {main}
Ответить

 

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

В секции 'bootstrap' => ['log', 'gii'] в config/console.php удалите 'gii'.

Ответить

 

Роман

Дмитрий, спасибо.
Удалил.

Теперь при попытке выполнить

php yii roles/assign

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

Z:\Programs\Sites\yii2-basic-elisdn\www>php yii roles/assign
Username: admin
PHP Fatal error:  Call to a member function getRoles() on null in commands\RolesController.php on line 29
...

Скорее всего из-за неуказанного в config/components.php:

'authManager' => [
    'class' => 'yii\rbac\PhpManager',
],

Ok, указываю.

Но после этого шаг указания роли в консоли зацикливается:

Z:\Programs\Sites\yii2-basic-elisdn\www>php yii roles/assign
Username: admin
Role: [,?]: admin
Role: [,?]: anything else?
Role: [,?]:

Что я мог упустить?

Ответить

 

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

Роли не создали.

Ответить

 

Роман

Так ведь роли были созданы в app\commands\RbacController.php

Всего две: user и admin.

Соответственно, ввожу вторым шагом роль admin или user.
В ответ повторный запрос.
И так без конца.

Ничего уже не понимаю.

Где в таком случае необходимо роли создавать?

Ответить

 

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

Тогда бы выводилось:

Role: [user,admin,?]:
Ответить

 

Роман

В общем, создал в корне приложения папку rbac.
В ней файлы assignments, items и rules.
В items прописал роли.
Всё заработало.

В статье не было указано.
Статью изучаю без каких-либо дополнительных знаний.
Буду курить эдишынали.

Ответить

 

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

> В статье не было указано.

После создания в RbacController его нужно запустить.

> Статью изучаю без каких-либо дополнительных знаний.

А смысл?

Ответить

 

Алексей

Действительно, в статье не указано, что после создания RbacController необходимо в рукопашную создать rbac/items.php. Мы, новички, на этом здорово спотыкаемся )

Ответить

 

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

Не в рукопашную создать, а этот контроллер как yii rbac/init запустить.

Ответить

 

Алексей

Я так и запускал, в ответ получал ругательство:
PHP Warning 'yii\base\ErrorException' with message 'Invalid argument supplied for foreach()'

А когда в ручную создал, тогда всё заработало.

Ответить

 

oleg

Добрый день у меня вылетает ошибка.

# php yii rbac/init
Exception 'Error' with message 'Call to a member function createPermission() on null'

in /basic/commands/RbacController.php:21
Ответить

 

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

Настройте 'authManager' в config/console.php.

Ответить

 

oleg

Спасибо за ответ но теперь вылетает

php yii rbac/init
PHP Warning 'yii\base\ErrorException' with message 'Invalid argument supplied for foreach()'

in basic/vendor/yiisoft/yii2/rbac/PhpManager.php:646

Stack trace:
#0 basic/vendor/yiisoft/yii2/rbac/PhpManager.php(646): yii\base\ErrorHandler->handleError(2, 'Invalid argumen...', '/home/oleg/cont...', 646, Array)
#1 basic/vendor/yiisoft/yii2/rbac/PhpManager.php(91): yii\rbac\PhpManager->load()
#2 basic/vendor/yiisoft/yii2/base/Object.php(107): yii\rbac\PhpManager->init()
#3 [internal function]: yii\base\Object->__construct(Array)
#4 basic/vendor/yiisoft/yii2/di/Container.php(372): ReflectionClass->newInstanceArgs(Array)
#5 basic/vendor/yiisoft/yii2/di/Container.php(151): yii\di\Container->build('yii\\rbac\\PhpMan...', Array, Array)
#6 basic/vendor/yiisoft/yii2/BaseYii.php(344): yii\di\Container->get('yii\\rbac\\PhpMan...', Array, Array)
#7 basic/vendor/yiisoft/yii2/di/ServiceLocator.php(133): yii\BaseYii::createObject(Array)
#8 basic/vendor/yiisoft/yii2/di/ServiceLocator.php(71): yii\di\ServiceLocator->get('authManager')
Ответить

 

Роман

Имел ввиду, что изучаю Yii2. И не знал как организовать разграничение прав.

В любом случае Вы даёте полезный материал.
Спасибо.

Ответить

 

Антон

Подскажите в чем может быть проблема

root@acer:/var/www/html/yii# php yii rbac/init
Exception 'Error' with message 'Call to a member function removeAll() on null'

in /commands/RbacController.php:20
Ответить

 

Антон Бурый

волшебный коммент :) Написал и понял что в config/console.php нужно прописать новые настройки AuthManager. Спасибо вам. Очень крутые статьи.

Ответить

 

евген

у вас на гитхабе есть yii2-hybrid-authmanager. скажите, это усовершенствованный вариант описанного в этой статье Rbac?
можно узнать как он устроен? мультироли как хранятся? json в поле role?

Ответить

 

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

Посмотрите в следующей статье, на которую дана ссылка в конце этой.

Ответить

 

Maksimus1991

Спасибо, отличная статья)

Ответить

 

Юрий

Добрый день!!!

Спасибо за Ваши труды..
я столкнулся с такой проблемой, у нас есть модуль user в нем вид
views/backend/user/login.php

для чего он там? у меня не получается им воспользоваться, мы закрыли модуль админ от не прошеных гостей, а как сделать переброс на вид login.php но уже админ..

Спасибо

Ответить

 

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

Это для использования в advanced-приложении. Если хотите воспользоваться именно им в basic, то в методе beforeAction модуля admin налету замените адрес:

Yii::$app->uset->loginUrl = ['/admin/user/user/login'];
return parent::beforeAction($action);
Ответить

 

Юрий

Спасибо такой способ лучше чем мой я делал через denyCallback в access модуля админ....

но результат один и тот же, так как все идет через админ, а админ разрешен только админам:)
то происходит зацикливание...
мне кажется нужно в access rule какое то правило прописать. что бы оно разрешало только логин гостям...
пробовал так..

......
'rules' => [
    [
        'allow' => true,
        'actions' => ['index'],
        'controllers' => ['login'],
        'roles' => ['?'],
    ],
    [
        'allow' => true,
        'roles' => [AdminRbac::PERMISSION_ADMIN_PANEL],
    ],
],
....
Ответить

 

Дмитрий Елисеев
'access' => [
    'class' => AccessControl::className(),
    'except' => ['site/login', 'site/error'],
    'rules' => [
        'allow' => true,
        'roles' => [AdminRbac::PERMISSION_ADMIN_PANEL],
    ],
],
Ответить

 

Юрий

Спасибо, Дмитрий
вот то что сделал работает
может кому пригодится


'access' => [
    'class' => AccessControl::className(),
    'except' => ['user/user/index'], 
    'rules' => [
        [
        'allow' => true,
        'roles' => [AdminRbac::PERMISSION_ADMIN_PANEL],
        ],
    ],
],
Ответить

 

Анатолий Белов

Всё здорово! но манера подачи в тексте запутывает в конец.

Идешь последовательно, последовательно ложные примеры, которые в дальнейшем разбиваются о более совершенные. Тут нужен простой копипаст, потом устранять ошибки интерпритатора, текст бесполезен в данном случае, он ничего не раскрывает и не объясняет.

Ответить

 

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

Это статьи для программистов, а не для копипастеров. Увы.

Ответить

 

Анатолий Белов

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

Просто предлагаю, сосредотачивать статьи на конечном варианте.

Я хочу разобраться как это работает, меня не жмет копипастить, по этому, эта статья не для копипастеров и не для таких как я. Мне нужно увидеть как это работает. В итоге у меня в ide куча вкладок, с гитхаба вкладки, статья. И не понятно, как свести всё воедино.

В итоге я разобрался. Теперь пишу в чем у меня возникли сложности при усвоении материала. Принимать это во внимание или отсылать меня лесом, дело ваше. Вам все равно спасибо, за труды! Эти статьи хорошие примеры, к ним претензий нет. Суть моего коммента описана выше. Я поймал это исключение при чтении, практически всего цикла статей по yii2. Еще раз спасибо, подписан давно, жду следующих открытий.

Ответить

 

Oleg

Добрый день.
При выполнении php yii roles/assign
выдает ошибку

Exception 'Error' with message 'Call to undefined method app\models\User::findOne()' in commands/RolesController.php:59
Ответить

 

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

Переделайте User в ActiveRecord.

Ответить

 

Андрей

Дмитрий, приветствую!
Я столкнулся с такой проблемой - при добавлении пермишенов для новых контроллеров в метод init, я в самом начале делаю

$authManager = \Yii::$app->authManager;
$authManager->removeAll();

Затем все пермишены пересоздаются, новые добавляются, но в таблице auth_assignment удаляются все записи и соответственно уже созданные пользователи с ролями теряют все свои возможности.
Как быть в такой ситуации? Неужели каждый раз нужно будет добавлять новый метод для новых пермишенов?
Может быть можно как-то делать бэкап текущих назначенных прав на лету?

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

Ответить

 

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

Либо бэкапить и восстанавливать в init, либо добавлять роли через миграции.

Ответить

 

Андрей

Вдруг кому пригодится

private function backup()
{
    Console::output('Create backup auth_assignment table to auth_assignment_back');
    $connection = \Yii::$app->getDb();
    $connection->createCommand("DROP TABLE IF EXISTS `auth_assignment_back`")->query();
    $connection->createCommand("CREATE TABLE `auth_assignment_back` LIKE `auth_assignment`")->query();
    $connection->createCommand("INSERT INTO `auth_assignment_back` SELECT * FROM `auth_assignment`")->query();
}

private function restore()
{
    Console::output('Restore data from auth_assignment_back table to auth_assignment');
    $connection = \Yii::$app->getDb();
    $connection->createCommand("INSERT INTO `auth_assignment` SELECT * FROM `auth_assignment_back`")->query();
}
Ответить

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

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


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



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