Yii PHP framework: контроль доступа с использованием ролей (RBAC)

Владимир | | PHP, Web разработка, Yii.

yii rbac

Я думаю, все, кто хоть немного работал с фреймворком 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');
}

Метод получился довольно объемный, но в нём большую часть занимаю вызовы createOperation и 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 приложения. Само приложение вам нужно будет создать самостоятельно. Дальше, думаю, вы разберетесь 😉

Любые вопросы или замечания оставляйте в комментариях. Постараюсь ответить!

  • Чем больше узнаю о Yii тем больше желание на него перейти. Относительно прост, относительно гибок. Единственно что не нравится, это документация

  • AntonYu

    Если знаешь английский на минимальном уровне, то всегда можно спросить на официальном форуме. Да и на yiiframework.ru/forum всегда помогают.

  • guest

    #
    //создаем пользователя 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();

    какой пздц

  • Если зашла тема об авторизации, очень хотелось бы почитать о использовании Zend_Acl в Yii framework

  • Я знаю, но в данной ситуации это самый удобный вариант.
    При использовании AR библиотеки фреймворка создание пользователя выглядело бы так.
    $user = new Users;
    $user->u_name = 'root';
    $user->u_email = 'test@test.ru';

    $user->save();

    Но при этом был бы вызван обработчик события beforeSave в котором выполняется проверка прав текущего пользователя. При инсталляции никаких пользователей нет, поэтому добавить пользователя с ролью root не получится.

    В общем, удобнее один раз выполнить простой запрос.

    • Дамир Фахрутдинов

      Проблема Yii  и Ruby on Rails — валидаторы железно засунуты в модель.

      •  В Yii отменяем валидацию путем передачи  false в метод save(false). Не думаю что в Руби с этим какие-то проблемы…

  • Почему именно Zend_Acl?

  • Elbek

    pochemuto Yii::app()->user->checkAccess('createUser') vsegda vozvrashaet true esli ya user

    smotrite function checkAccess v classe CPhpAuthManager, vnem on kak proveryayet u vas dosup est ili net tam nikakoy svyaz net s logined useram

    • Elbek

      checkAccess($operation,$this->getId(),$params); izvinite ya ne smotrel

  • Zel_mail

    Возможно, глупый вопрос, но не проясните ситуацию:
    почему createOperation ипользуется для 'createContact', 'viewContacts' и т.д., а для 'deleteContact' используется createTask?

    ( в документации на http://yiiframework.ru/doc/guide/ru/topics.auth тоже самое: createOperation для 'createPost' и т.п., а для 'updateOwnPost' используется createTask )

    Хотя в коде фреймвёка вроде отличия между ними только в типе дочерних элементов, которые можно добавлять. То есть, вроде бы, при наборе правил для пользователей вполне можно обойтись одними операциями, без задач.

    • Обойтись можно, но такая структура позволяет группировать операции. В более сложных приложениях это имеет смысл использовать.

      >> Роль состоит из задач. Задача состоит из операций. Операция — разрешение на какое-либо действие (дальше не делится).

      • Zel_mail

        Попробую еще раз: вопрос был про почему для однотипных процессов в одном случае использовались операции, а в другом — задачи?

        То есть, логично было бы предположить, что роль User должна состоять из задач controlUserData ( состоит из операций читать и изменять свои данные ) и controlContacts ( состоит из операций с контактами ). Соответственно роли admin и root тоже должны состоять из задач.

  • Profi_net

    У меня таблица в БД в которой хранится:
    Группа пользователей->Модуль-> чтение, редактирование, удаление, добавление

    то есть грубо говоря есть админка в которой можно создавать группы пользователей, к каждой группе присвоено три модуля(которые могут быть потом добавлены) и к каждому модулю есть четыре права — чтение, редактирование, удаление, добавление

    Как сделать так чтобы не вручную вводить все правила, а что бы они выгружались из БД?

    • Правила должны откуда-то взяться в БД. Т.е. один раз их нужно ввести.
      А для хранения использовать CDbAuthManager.
      'components'=>array(

      'authManager'=>array(
      'class' => 'CDbAuthManager',
      ),
      ),

      • Profi_net

        я просто не так немного написал…
        я так понимаю CDbAuthManager предполагает использование определенных таблиц в БД
        а у меня уже есть таблица, которая совсем по другому организована:
        столбцы: роль(группа пользователей), модуль, чтение,редактирование, изменение, удаление
        к примеру есть 2 модуля: блог, форум — для них в таблице есть следующие записи:
        админ,блог,1,1,1,1
        админ,форум,1,1,1,1
        модератор,блог,0,0,0,0
        модератор,форум,1,1,1,1
        То есть иными словами, идея такая что не на каждое действие идет разрешение/запрет некоторых правил, а на весь модуль.
        Понятно что в каждом модуле придется прописывать это все в ручную. Но не могу понять просто как сделать так что бы: Пользователь зашел, в системе зарегистрировалось то к какой группе пользователей он относится, и соответственно открылся доступ к соответствующим модулям. Но при этом администратор свободно мог добавить новые роли(группы пользователей), и при этом менять код только в модулях.
        Ну вот как то так примерно.

      • Profi_net

        я просто не так немного написал…
        я так понимаю CDbAuthManager предполагает использование определенных таблиц в БД
        а у меня уже есть таблица, которая совсем по другому организована:
        столбцы: роль(группа пользователей), модуль, чтение,редактирование, изменение, удаление
        к примеру есть 2 модуля: блог, форум — для них в таблице есть следующие записи:
        админ,блог,1,1,1,1
        админ,форум,1,1,1,1
        модератор,блог,0,0,0,0
        модератор,форум,1,1,1,1
        То есть иными словами, идея такая что не на каждое действие идет разрешение/запрет некоторых правил, а на весь модуль.
        Понятно что в каждом модуле придется прописывать это все в ручную. Но не могу понять просто как сделать так что бы: Пользователь зашел, в системе зарегистрировалось то к какой группе пользователей он относится, и соответственно открылся доступ к соответствующим модулям. Но при этом администратор свободно мог добавить новые роли(группы пользователей), и при этом менять код только в модулях.
        Ну вот как то так примерно.

        • Profi_net

          мне кажется можно обойтись и без RBAC, используя стандартный функционал:
          public function accessRules()
          {
          return array(
          array('deny',
          'actions'=>array('create', 'edit'),
          'users'=>array('?'),
          ),
          array('allow',
          'actions'=>array('delete'),
          'roles'=>array('admin'),
          ),
          array('deny',
          'actions'=>array('delete'),
          'users'=>array('*'),
          ),
          );
          }
          только функцию нужно изменить на выгрузку из базы:
          сделать чтобы следующий массив получал роли из БД:
          array('allow',
          'actions'=>array('delete'),
          'roles'=>array('admin'),
          иными словами получаем название модуля, затем подставляем название модуля в базу, получаем все строки связанные с данным модулем, и создаем на каждую роль массив с правами
          скажите правильно идет ход мыслей?

        • Конечно, можно обойтись без RBAC и написать свою библиотеку. И получать массив с правами из базы тоже можно. Yii без разницы как вы формируете этот массив.

          Другой вопрос, что в вашем случае будет удобнее или быстрее, написать свою библиотеку или переделать систему под использование стандартной или воспользоваться сторонним решением вроде Zend_Auth + Zend_Acl?

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

          Связать таблицу пользователей и таблицу с правами доступа (отношение «многие-ко-многим»). После этого можно получить все права для данного пользователя и сформировать массив.

        • Profi_net

          Спасибо за подробные комментарии!
          Скажите а как переопределить метод public function accessRules(), что бы во всех контроллерах использовался общий один на всех метод?

        • Есть несколько вариантов.

          Первый.

          1) Создать еще один промежуточный контроллер.
          class MyController extends CController

          2) Определить в нём метод accessRules

          3) Все контроллеры приложения должны быть потомками MyController, а не
          CController.

          4) Не объявлять метод accessRules в контроллерах.

          Второй.

          1) Создаем свой класс с методом, который будет формировать массив с правилами.
          Назвать их можно как угодно, например, MyAccessManager::getRules()

          2) В контроллерах в методах accessRules пишем код
          return MyAccessManager::getRules();
          Этот метод должен правильно сформировать массив.

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

        • Profi_net

          в каждом контролере есть метод:
          public function accessRules()
          {
          return array(
          array('deny',
          'actions'=>array('create', 'edit'),
          'users'=>array('?'),
          ),
          array('allow',
          'actions'=>array('delete'),
          'roles'=>array('admin'),
          ),
          array('deny',
          'actions'=>array('delete'),
          'users'=>array('*'),
          ),
          );
          }
          у меня получается так что этот массив создается из базы данных, я хочу сделать так что бы при определении роли пользователю присваивались права на 'create', 'edit' к примеру, но получается так что при использовании в массиве 'roles' нужно использовать (RBAC) и (CPhpAuthManager) или (CDbAuthManager), но мне собственно это и ненужно так как вся система построена гораздо проще.

          То есть иными словами мы получаем значение переменной (к примеру роль 'admin') в зависимости от id пользователя, и потом в зависимости от роли в результирующем массиве который возвращает метод accessRules нужно дать те или иные права, как можно это сделать без создания ролей в базе или в файле?

          или еще можно объяснить это так:
          при регистрации загоняется $this->role значение роли пользователя
          потом в методе accessRules() мы проверяем если значение role 'admin' то даются права 'index','view', а если значение role 'user' то даются права 'admin','delete'

          Конечно как вариант можно использовать в массив 'users'=>array('admin'), с перечислением пользователей, но мне кажется что при большом количестве пользователей такой вариант не эффективен

          Как думаете можно что то здесь сделать, или все таки нужно использовать инструкции из описанного выше материала?

        • Честно говоря, мне сложно ответить. Не совсем понял фразу «как вариант можно использовать в массив 'users'=>array('admin'), с перечислением пользователей».
          Зачем перечислять всех пользователей? Права ведь вы определяете не в зависимости от имени пользователя, а от его роли. Или у вас очень много ролей?

  • Roman

    Спасибо за статью! Но возник вопрос, каким образом можно вынести часть прав в контроллер (например в  accessRules) и проверять через user->checkAccess('deleteSomething')? Просто не хочется создавать большой массив с правами, зная что часть их необходима только в определённых контролерах.

    • Посмотрите эту статью. Там есть пример создания собственного класса PhpAuthManager. В методе init этого класса загружается
      application.config.auth с правилами. Если вы хотите разнестиправила по разным файлам, то в методе init подключайте нужныефайлы. Выбор файла определяется названием контроллера и метода.

      • Roman

        Спасибо, эту статью читал. Но тем не менее не понял «Выбор файла определяется названием контроллера и метода.». Можно поподробнее? Или же пример

        • Вы писали, что хотите поместить часть правил в контроллеры. Идея в том, чтобы создать по отдельному файлу с правилами для каждого из контроллеров и загружать нужный. Т.е. вместо
          $this->authFile=Yii::getPathOfAlias('application.config.auth').'.php';добавляете проверку контроллера и загружаете не auth.php, в котором находятся все правила, а файл с правилами именно для текущего контроллера.

  • Roms

    Что в статье ничего не сказано об применение данных из столбика u_state (статус (активен, заблокирован)), как его проверять в действиях?

    • Действительно, пропустил. В файле components/UserIdentity.php есть строчка
      if(($user===null) or (md5($this->password)!==$user->u_pass)) {
      замените ее на
      if(($user===null) or (md5($this->password)!==$user->u_pass) or ($user->u_state !== Users::STATE_ACTIVE)) {

      • Mat

        Один субъект может иметь несколько ролей.Одну роль могут иметь несколько субъектов.Одна роль может иметь несколько разрешений.Одно разрешение может принадлежать нескольким ролям.

        В Yii реализации всё верно, но всё же под субъектом нужно принимать групу пользователей 

        • Полностью согласен. Хотя технически это может быть и один пользователь. Вообще определение субъекта довольно многозначное 🙂

  • Alexander

     скачал ваш вариант, создал приложение, заменил папку protected, создал БД из дампа, изменил настройки соединения с БД в конфиге, зашел в приложение и что дальше? как проверить его работу? вызов любой операции с юзером выдает

    Error 403

    Forbidden

    • Сначала нужно залогиниться. Метод actionLogin контроллера SiteController. После этого можете проверять работу методов контроллера ContactsController

      • user

        та же проблема что и у Alexander, хотя я авторизован как root, мне кажется во всех методах контроллера UsersController
         if (!Yii::app()->user->checkAccess('viewUsers')) {
                    throw new CHttpException(403,'Forbidden');
                }
        надо убрать воскл знак и писать так if (Yii::app()->user->checkAccess('viewUsers')) {
                    throw new CHttpException(403,'Forbidden');
                }

      • user

        да, работает ка-то неправильно  — даже если залогинился как root то всё равно всё запрещено

        • Давайте рассмотрим весь процесс по шагам.
          1) Устанавливаем yii, распаковываем архив, перезаписываем папку protected и создаем базу и импортируем дамп. Указываем в конфиге параметры подключения к базе.

          2) Заходим на URL
          index.php?r=site/install
          Появится сообщение — «Установка завершена» информация о правах должна быть записана в файл auth.php (protected/data).

          3) Входим под root'om
          r=site/login
          test@test.ru
          11111
          Обратите внимание! root не имеет прав на создание контактов, т.к. он наследует права админа (admin), но не пользователя (user).

          4) Создаём пользователя (роль — user)
          index.php?r=users/create

          5) Входим под новым пользователем. И для него будет доступна страница
          index.php?r=contacts/create

        • user

           А щас всё получилось, непонятно почему первый раз не получалось : ))

        • user

           Спасибо за хороший пример более менее разобрался

  • Александр

    Владимир, огромное спасибо за статью! (особенно за архив с примером и пошаговое руководство по распаковке архива в комментариях)

    У меня к вам просьба…

    Не могли бы вы расписать как организовать RBAC в Yii но все настройки прав доступа что бы лежали не в файлике 
    protected/data/auth.php, а в базе данных.Желательно так же с рабочим примером, который можно было бы себе поставить…

  • Maxyer

    А почему createContact — это operation, а deleteContact — это task ?

    • Опечатка. В теории в task (задачу) входит набор операций. Технически разницы практически нет.

  • Максим

    Вот еще может быть очень общий вопрос по поводу написания бизнес-правил.
    В Вашем примере все достаточно просто — таблица contacts содержит поле, по которому можно узнать, кому принадлежит данный контакт.
    В моем же приложении, чтобы определить принадлежит ли запись текущему пользователю нужно сделать SQL-запрос к двум другим таблицам.
    Чтобы было понятнее поясню.
    Пользователь — это преподаватель.
    Ему нужно редактировать оценки ученика в таблице оценок.
    Ученик принадлежит классу.
    Преподаватель может редактировать оценки только тех учеников, в чьих классах он ведет занятия.
    Что-то я пока не представляю, как будет выглядеть соответствующее бизнес-правило.

    • Бизнес правила это только один из возможных вариантов ограничения доступа. Вам ничто не мешает реализовать более сложную логику в соответствующих методах. Или написать свою реализацию checkAccess.

      • Максим

        Т.е. Вы хотите сказать, что реализовать подобную проверку с помощью бизнес-правил нельзя ?
        Если так, то это очень печально.

  • Максим

    Объясните пожалуйста, что происходит в результате исполнения  $auth->assign($this->u_role, $this->u_id); и последующего $auth->save(); ?Я думал, что пользователю u_id назначается роль u_role и запись об этом назначении сохраняется в auth.php.Однако, протестировав Ваше приложение, обнаружил, что это не так, т.е. файл auth.php остается неизменным после инсталляции приложения.В таком случае, что же происходит на самом деле и куда сохраняется информация о назначении ?

    • Максим

      прошу прощения, вопрос исчерпан.
      Информация о назначениях сохраняется в auth.php 😉

      • Максим

        Кстати, для чего в таком случае существует поле u_role в таблице users ?

        • Для хранения роли присвоенной пользователю. Роль определяет набор доступных пользователю операций.

        • Максим

          но ведь та же самая информация находится в файле auth.php
          Я имею ввиду связь между id пользователя и ролью

        • В конфиге Yii (настройки authManager) вы указываете где будут храниться правила, в базе данных или php файле.

  • Максим

    Вы сказали, что методы filters и accessRules можно убрать, но ведь при задании правил проверки доступа можно указывать роли пользователей …
    В этом случае, как я понимаю, самому вызывать checkAccess в действиях контроллера не придется.
    Как же на самом деле правильнее ?

    • RBAC позволяет создавать более гибкие правила чем с помощью фильтра, например, объединять пользователей в группы, использовать бизнес правила и т.п.
      Стандартный фильтр проверяет имеет ли конкретный пользователь право вызвать какой-то из методов контроллера (использовать этот фильтр, конечно, проще).

      Например, редактор сайта хочет удалить статью. У обычных редакторов есть право удалять только свои статьи, у главного редактора — любые. Очевидно, что у всех пользователей должна быть возможность вызвать метод delete, но внутри метода нужно дополнительно проверить имеет ли право редактор удалить конкретную статью. С помощью стандартного фильтра сделать это не получится.

      • Александр Шестаков

        Ну так для этого же не обязательно отказываться от метода accessRule() — достаточно добавить проверку в метод Delete — на то что статья принадлежит данному пользователю — но куча других методов будет привязана к ролям в accessRule

        • Правильно, вы выбираете тот вариант, который для вас удобнее.
          В случае, когда нужно просто ограничить доступ, проще использовать фильтры.
          Но если вам нужно сделать интерфейс для управления правами пользователей? Т.е. сегодня есть две группы пользователей, а завтра их будет 3 или 4, и права у них могут изменяться. В этом случае количество кода в методе delete увеличится. А при использовании RBAC в методе контроллера проверка доступа всегда будет занимать 3 строчки кода.

  • Максим

    Владимир, вы сказали, «В конфиге Yii (настройки authManager) вы указываете где будут храниться правила, в базе данных или php файле.»
    Но в вашем примере ведь используется phpauthmanager, а т.к. он хранит данные о назначениях в файле, зачем все-таки поле u_role в таблице ?

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

      • Максим

        В данной статье мне вроде бы понятно, для чего это поле в таблице — там и UserIdentity переопределен т.о, что при аутентификации пользователя ему назначается определенная роль.
        В вашем же примере, доступном для скачивания, этого не сделано, поэтому я к сожалению не могу понять, где и как происходит назначение роли пользователю, когда он логинится на сайте, если оно, конечно, вообще происходит при этом, хотя пример работает.
        По поводу того, что файл auth.php не содержит информации о конкретных пользователях, мне кажется, вы ошибаетесь. Посмотрите на этот фрагмент
        'user' =>
          array (
            'type' => 2,
            'description' => »,
            'bizRule' => NULL,
            'data' => NULL,
            'children' =>
            array (
              0 => 'createContact',
              1 => 'viewContacts',
              2 => 'readContact',
              3 => 'updateContact',
              4 => 'deleteContact',
              5 => 'updateOwnData',
            ),
            'assignments' =>
            array (
              5 => // это user_id
              array (
                'bizRule' => NULL,
                'data' => NULL,
              ),
              3 => // и это тоже user_id
              array (
                'bizRule' => NULL,
                'data' => NULL,
              ),
            ),

        Кстати, пытаясь разобраться в вашей и др. статьях  о RBAC, и применить это в своем приложении, все, что я сделал для начала, — это по вашему примеру определил иерархию элементов авторизации в SiteController/Install и определил параметр roles в правилах accessRules нужного мне контроллера. Пока, требуемый эффект достигнут.
        Возможно, что со временем придется вносить правки, но пока мой пример работает.

        • ОК, вы частично правы 🙂
          Я написал эту статью почти 2 года назад и подзабыл что именно здесь делал.

          Как происходит назначение роли.
          1) В actionInstall мы создаём пользователя root и связываем его с ролью ($auth->assign). Здесь всё просто, т.к. пользователь только один.

          2) Когда мы создаём пользователя через web интерфейс, для связи пользователя с ролью используется метод afterSave модели Users. В этом методе вызываются $auth->revoke и $auth->assign для сброса старой роли и установки новой. Здесь тоже всё просто, т.к. пользователей мы добавляем по одному. И, в принципе, можно обойтись без поля u_role (но в форме поле для ввода роли должно быть, иначе нельзя будет указать роль нового пользователя).

          3) Теперь мы решили немного изменить структуру ролей и хотим переименовать роль user в editor. Как быстро найти всех пользователей, которым назначена роль user? Можно, конечно, получить эти данные из auth.php, но из базы удобнее 😉

  • dffd

    Методы filters и accessRules можно убрать, т.к. их мы не используем. В остальные методы добавляем проверку прав пользователя.

    Ололо, пусть наши контроллеры распухают.

  • Незнакомец

    Автор статья конечно так себе …. но судя из комментов вы еще не готовы писать рецепты для других пользователей. И вообще то что я читал на ващем блоге, мегко сказано недоработаное га**о … вы лучще изначально как все делают изучите хорошо фрейм … и только потом начинате писать …а не парьте всем совю … ху**ю.

    Надеюсь морально никого не обидел.

  • Никола

    Огромное человеческое спасибо!

    Благодаря этому материалу — легко разобрался в возможностях RBAC. Документации мне не хватило. Успехов Вам.

  • Юлия

    Спасибо, статья очень помогла