Контроль доступа с использованием ролей (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 } ?>
…

На этом мы остановимся.
Теги:
контроль доступа, RBAC
Добавлено: 22 Апреля 2018 11:37:12 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...