Я думаю, все, кто хоть немного работал с фреймворком Yii знают, что он поддерживает возможность разграничения прав доступа на основе ролей.
Принцип работы этой системы достаточно прост. Вы создаёте наборы правил и пользователей, связываете их между собой. После этого, вы в любой момент можете проверить, имеет ли пользователь право на выполнение какой-то операции или нет.
Одно из основных преимуществ использования данной библиотеки заключается в том, что вам нужно написать минимум кода для проверки прав доступа. Обычно этот код выглядит следующим образом.
if (!Yii::app()->user->checkAccess('createUser')) { throw new CHttpException(403, 'Forbidden'); } //остальной код…
В теории всё просто. Но на практике, документации и примеров по этой теме практически нет (надеюсь, это скоро изменится).
Основные источники информации (на русском): Аутентификация и авторизация и RBAC и описание ролей в файле. На английском хороших и подробных примеров, к сожалению, я не нашел.
Примечание. Очень советую прочитать эти статьи, прежде чем переходить к моему примеру.
Когда я первый раз решил использовать RBAC, то выяснилось, что есть множество нюансов, которые приходится учитывать при работе с этой библиотекой. Ничего запредельно сложного и недоступного для понимания, но «ковырялся» я довольно долго 😉
В этой статье мы рассмотрим пример создания несложной системы управления пользователями (идея взята из одного web приложения).
Постановка задачи.
Есть три типа пользователей.
Обычный (user
) – может создавать и редактировать свои данные (например, список контактов) и изменять свои логин/пароль.
Администратор (admin
) – может создавать новых пользователей, но не может изменять их данные. Он может изменить свои собственные логин и пароль, но не может изменить роль.
Суперпользователь (root
) – может выполнять любые операции с пользователями.
Ни admin
, ни root
не имеют доступа к персональным данным пользователя (которые он создаёт при работе с приложением).
Примечание. Чтобы сократить объем кода, я урезал набор правил. Но, думаю, вы без особого труда сможете его дополнить. Главное понять идею и принцип работы.
Шаг первый. Выбираем тип хранилища для ролей и операций.
Для этих целей Yii позволяет использовать PHP файл (CPhpAuthManager
) или базу данных (CDbAuthManager
).
Если вы предполагаете, что количество пользователей будет большим, то лучше использовать БД. Но для знакомства с библиотекой лучше использовать PHP файл, т.к. читать его легче. К тому же перейти от одного типа хранилища к другому совсем несложно.
Для того, чтобы Yii «узнал» о вашем выборе, в массив с настройками (config/main.php) нужно добавить следующий элемент.
'components'=>array( … 'authManager'=>array( 'class' => 'CPhpAuthManager', ), ),
Шаг второй. Создадим таблицу в БД для хранения пользователей.
Эта таблица будет состоять из шести полей.
u_id
– первичный ключ;
u_name
– имя пользователя;
u_email
– адрес почты (используется как логин);
u_pass
– пароль;
u_state
– статус (активен, заблокирован);
u_role
– роль пользователя (root
, admin
, user
).
В данном примере для нас играет роль последнее поле (u_role
).
Шаг третий. Создаём операции, роли и задачи.
Я очень надеюсь, что вы прочли статью из Полного руководства и знаете, что представляют собой операции, роли и задачи и зачем они нужны.
Сразу перейдём к созданию файла с этими настройками.
Т.к. в качестве хранилища мы выбрали PHP файл, то можно создать его вручную (этот способ подробно рассмотрен в статье RBAC и описание ролей в файле).
Но, на мой взгляд, гораздо удобнее использовать API. Поэтому создадим небольшой инсталляционный скрипт (контроллер SiteController
метод actionInstall
).
Примечание. Данные будут сохранены в файле protected/data/auth.php. Файл будет создан автоматически, поэтому запись в эту папку должна быть разрешена.
public function actionInstall() { $auth=Yii::app()->authManager; //сбрасываем все существующие правила $auth->clearAll(); //Операции управления пользователями. $auth->createOperation('createUser', 'создание пользователя'); $auth->createOperation('viewUsers', 'просмотр списка пользователей'); $auth->createOperation('readUser', 'просмотр данных пользователя'); $auth->createOperation('updateUser', 'изменение данных пользователя'); $auth->createOperation('deleteUser', 'удаление пользователя'); $auth->createOperation('changeRole', 'изменение роли пользователя'); $bizRule='return Yii::app()->user->id==$params["user"]->u_id;'; $task = $auth->createTask('updateOwnData', 'изменение своих данных', $bizRule); $task->addChild('updateUser'); //создаем роль для пользователя admin и указываем, какие операции он может выполнять $role = $auth->createRole('admin'); $role->addChild('createUser'); $role->addChild('viewUsers'); $role->addChild('readUser'); $role->addChild('updateOwnData'); //все пользователи будут создаваться по-умолчанию с ролью user, //только root может менять роль другого пользователя //создаем роль для пользователя root $role = $auth->createRole('root'); //наследуем операции, определённые для admin'а и добавляем новые $role->addChild('admin'); $role->addChild('updateUser'); $role->addChild('deleteUser'); $role->addChild('changeRole'); //создаем операции для user'а $bizRule='return Yii::app()->user->id==$params["contact"]->c_user_id;'; $auth->createOperation('createContact','создание контакта'); $auth->createOperation('viewContacts','просмотр списка контактов'); $auth->createOperation('readContact','просмотр контакта', $bizRule); $auth->createOperation('updateContact','редактирование контакта',$bizRule); $auth->createTask('deleteContact','удаление контакта',$bizRule); //создаем роль user и добавляем операции для неё $user = $auth->createRole('user'); $user->addChild('createContact'); $user->addChild('viewContacts'); $user->addChild('readContact'); $user->addChild('updateContact'); $user->addChild('deleteContact'); $user->addChild('updateOwnData'); //создаем пользователя root (запись в БД в таблице users) //тут используем DAO, т.к. AR автоматически назначит пользователю роль user $sql = 'INSERT INTO users(u_name, u_email, u_pass, u_state, u_role)' .' VALUES ("root", "test@test.ru", "'.md5('11111') .'", '.Users::STATE_ACTIVE.', "'.Users::ROLE_ROOT.'")'; $conn = Yii::app()->db; $conn->createCommand($sql)->execute(); //связываем пользователя с ролью $auth->assign('root', $conn->getLastInsertID()); //сохраняем роли и операции $auth->save(); $this->render('install'); }
Метод получился довольно объемный, но в нём большую часть занимаю вызовы createOperatio
n и addChild
, которые создают операции и связывают их с ролями.
Большинство операций в этом примере соответствуют методам контроллера (CRUD), но они могут быть любыми. Например, такими как changeRole
, позволяющими изменять одно единственное поле записи.
Обратите внимание. После создания операций и ролей доступ ограничен не будет. Вы должны будете сами проверить у пользователя наличие прав с помощью метода checkAccess
.
Отдельного внимания заслуживает использование бизнес правил (bizRule
) в операциях.
Бизнес правило представляет собой обычный PHP код, который должен возвращать true
или false
. Этот код может получить массив с данными, который будет доступен через переменную $params
.
Рассмотрим правило
$bizRule='return Yii::app()->user->id==$params["user"]->u_id;';
Здесь мы проверяем, соответствуют ли id
текущего пользователя (который выполнил вход) и id
записи в таблице пользователей, которую он хочет изменить. Таким образом, это правило позволит пользователю редактировать только свои данные.
В конце метода мы создаём пользователя root
. Дело в том, что все пользователи будут создаваться с ролью user
и только root
может изменять роль. Поэтому мы создаём его сразу при инсталляции.
После создания пользователя назначаем ему роль (метод assign
) и сохраняем изменения
$auth->save();
Шаг четвертый. Создание модели для работы с пользователями.
Как обычно, создаём модель с помощью генератора (gii) и затем вносим свои изменения.
Весь код модели я приводить здесь не буду (в конце статьи есть ссылка на архив с примером). Рассмотрим только измененные методы.
public function beforeSave() { parent::beforeSave(); $this->u_pass = md5($this->u_pass); /* * Если пользователь не имеет права изменять роль, то мы должны * установить роль по-умолчанию (user) */ if (!Yii::app()->user->checkAccess('changeRole')) { if ($this->isNewRecord) { //ставим роль по-умолчанию user $this->u_role = Users::ROLE_USER; } } return true; }
Перед созданием новой записи мы проверяем, имеет ли текущей пользователь право изменять роли, если нет, то ставим роль по-умолчанию (user
).
После сохранения (или создания) записи, нужно назначить пользователю роль. Права пользователя мы уже проверили и роль установили, поэтому сейчас просто назначаем пользователю роль (метод assign
).
Предварительно, с помощью метода revoke
удаляем связь между пользователем и ролью (если такая существовала). Если связь не удалить, то когда root
будет изменять роли, у нас появятся пользователи с несколькими ролями.
public function afterSave() { parent::afterSave(); //связываем нового пользователя с ролью $auth=Yii::app()->authManager; //предварительно удаляем старую связь $auth->revoke($this->prevRole, $this->u_id); $auth->assign($this->u_role, $this->u_id); $auth->save(); return true; }
При удалении пользователя не забываем удалить связь между ним и ролью.
public function beforeDelete() { parent::beforeDelete(); //убираем связь удаленного пользователя с ролью $auth=Yii::app()->authManager; $auth->revoke($this->u_role, $this->u_id); $auth->save(); return true; }
Как видите, принцип работы достаточно простой. Главное, не забывайте вызывать $auth->save();
чтобы сохранить изменения.
Шаг пятый. Контроллер и представления.
Как и в случае с моделью, создаем контроллер и представления с помощью генератора (gii).
Методы filters
и accessRules
можно убрать, т.к. их мы не используем. В остальные методы добавляем проверку прав пользователя.
Опять же, весь контроллер целиком рассматривать нет смысла, он есть в архиве с исходниками. В качестве примера рассмотрим метод обновления данных пользователя.
Это самый сложный случай. У нас есть пользователь, которому можно изменять любые записи, есть пользователи, которым можно изменять только свою запись, но при этом нельзя изменять свою роль. Поэтому проверок будет две.
В первый раз проверяем, пытается ли изменить роль пользователь, у которого на это нет прав.
С помощью второй проверки убеждаемся, что у пользователя есть права на изменение данной записи.
public function actionUpdate() { $model=$this->loadModel(); //проверяем, можно ли пользователю изменять роль if (isset($_POST['Users']['u_role']) && !Yii::app()->user->checkAccess('changeRole')) { throw new CHttpException(403,'Forbidden'); } //проверяем, может ли пользователь изменять данную запись if (!Yii::app()->user->checkAccess('updateUser') && !Yii::app()->user->checkAccess('updateOwnData', array('user'=>$model))) { throw new CHttpException(403,'Forbidden'); } if(isset($_POST['Users'])) { $model->prevRole = $model->u_role; $model->attributes=$_POST['Users']; if($model->save()) $this->redirect(array('view','id'=>$model->u_id)); } $this->render('update',array( 'model'=>$model, )); }
В представлении (views/users/_form.php
) убираем из формы поле «Роль» для пользователя у которого нет прав её изменять.
… <?php if (Yii::app()->user->checkAccess('changeRole')) { ?> <div class="row"> <?php echo $form->labelEx($model,'u_role'); ?> <?php echo $form->dropDownList($model,'u_role',array( Users::ROLE_USER=>Users::ROLE_USER, Users::ROLE_ADMIN=>Users::ROLE_ADMIN, Users::ROLE_ROOT=>Users::ROLE_ROOT, )); ?> <?php echo $form->error($model,'u_role'); ?> </div> <?php } ?> …
На этом мы остановимся. Общую идею, надеюсь, я объяснил. Если есть желание, можете скачать архив и поиграться с этим примером.
SourceВ архиве есть дамп базы и папка protected
приложения. Само приложение вам нужно будет создать самостоятельно. Дальше, думаю, вы разберетесь 😉
Любые вопросы или замечания оставляйте в комментариях. Постараюсь ответить!