Yii 1. Авторизация - пользователи и роли в базе данных

Введение

Процесс настройки аутентификации и авторизации расписан в нескольких официальных и полуофициальных источниках:
http://www.yiiframework.com/doc/guide/1.1/ru/topics.auth
http://yiiframework.ru/doc/guide/ru/topics.auth
http://yiiframework.ru/doc/cookbook/ru/access.rbac.file
Но ни в одном из этих источников нет исчерпывающих рекомендаций для построения авторизации для случая, когда пользователи, роли и прочие правила хранятся в базе данных. Хотя схема для базы данных MySQL входит в поставку Yii: framework/web/auth/schema-mysql.sql
Эта схема содержит описание трёх таблиц: AuthAssignment, AuthItem и AuthItemChild. Схема:
До этого я завёл таблицы для пользоватлей и ролей: user и user_role:

Базовая настройка прав доступа

Модель User у меня такая:

<?php
/**
 * This is the model class for table "user".
 *
 * The followings are the available columns in table 'user':
 * @property integer $id
 * @property string $username
 * @property string $password
 * @property string $email
 * @property integer $user_role_id
 *
 * The followings are the available model relations:
 * @property UserRole $userRole
 */
class User extends CActiveRecord {
    public function tableName() {
        return 'user';
    }

    public function rules() {
        return array(
            array('username, password, email', 'required'),
            array('user_role_id', 'numerical', 'integerOnly'=>true),
            array('username, email', 'length', 'max'=>128),
            array('password', 'length', 'max'=>64),
            array('id, username, password, email, user_role_id', 'safe', 'on'=>'search'),
        );
    }

    public function relations() {
        return array(
            'userRole' => array(self::BELONGS_TO, 'UserRole', 'user_role_id'),
        );
    }

    public function attributeLabels() {
        return array(
            'id' => 'ID',
            'username' => Yii::t("main", "Username"),
            'password' => Yii::t("main", "Password"),
            'email' => Yii::t("main", "Email"),
            'user_role_id' => Yii::t("main", "Role")
        );
    }

    public function search() {
        $criteria=new CDbCriteria;

        $criteria->compare('id',$this->id);
        $criteria->compare('username',$this->username,true);
        $criteria->compare('email',$this->email,true);
        $criteria->compare('user_role_id',$this->role,true);

        return new CActiveDataProvider($this, array(
            'criteria'=>$criteria,
        ));
    }

    public static function model($className=__CLASS__) {
        return parent::model($className);
    }
        
    public function validatePassword($password) {          
        return CPasswordHelper::verifyPassword($password,$this->password);
    }

    public function hashPassword($password) {
        return CPasswordHelper::hashPassword($password);
    }


    protected function beforeSave() {
        $this->password = hashPassword($this->password);
        return parent::beforeSave();
    }
}


Переопределённый класс CUserIdentity у меня такой (/protected/components/Useridentity):

<?php

/**
 * UserIdentity represents the data needed to identity a user.
 * It contains the authentication method that checks if the provided
 * data can identity the user.
 */
class UserIdentity extends CUserIdentity
{
  private $_id;
  public function authenticate()
  {
    $username=strtolower($this->username);
    $user=User::model()->find('LOWER(username)=?',array($username));
    if($user===null)
      $this->errorCode=self::ERROR_USERNAME_INVALID;
    else if(!$user->validatePassword($this->password))
      $this->errorCode=self::ERROR_PASSWORD_INVALID;
    else
    {
      $this->_id=$user->id;
      $this->username=$user->username;
      $this->setState('role', $user->userRole->name);
      $this->errorCode=self::ERROR_NONE;
    }
    return $this->errorCode==self::ERROR_NONE;
  }
  public function getId()
  {
    return $this->_id;
  }
}

Переопределённый класс CWebUser у меня выглядит так (/protected/components/WebUser.php):

<?php
class WebUser extends CWebUser {
    private $_model = null;
    function getRole() {
        if($user = $this->getModel()){
          return $user->userRole->name;
        }
    }
    private function getModel(){
        if (!$this->isGuest && $this->_model === null){
          $this->_model = User::model()->findByPk($this->id);
        }
        return $this->_model;
    }   
}

В конфиге прописываем авторизацию через базу данных (перед этим, само собой, определены настройки подключения к базе данных). /protected/config/main.php:

'authManager'=>array(
   'class'=>'CDbAuthManager',
   'connectionID'=>'db',
   'defaultRoles' => array('guest')
),
Чтобы заполнить таблицы правил и ролей пользователей, создадим скрипт /protected/commands/AccessCommand.php и заполним его своими настройками. Я добавил в правила все интересующие меня действия из моих контроллеров. У меня получилось так:

class AccessCommand extends CConsoleCommand {
    public function actionAddRules() {
      $auth=Yii::app()->authManager;
      $auth->createOperation('login',Yii::t("main", "Login"));
      $auth->createOperation('indexAuthor',Yii::t("main", "View list of authors"));
      $auth->createOperation('createAuthor',Yii::t("main", "Create author"));
      $auth->createOperation('adminAuthor',Yii::t("main", "Administrate authors"));
      $auth->createOperation('updateAuthor',Yii::t("main", "Update author"));
      $auth->createOperation('deleteAuthor',Yii::t("main", "Delete author"));
      $auth->createOperation('makePageAuthor',Yii::t("main", "Make site's page for that author"));
     
      $auth->createOperation('indexBook',Yii::t("main", "View list of books"));
      $auth->createOperation('createBook',Yii::t("main", "Create book"));
      $auth->createOperation('adminBook',Yii::t("main", "Administrate books"));
      $auth->createOperation('updateBook',Yii::t("main", "Update book"));
      $auth->createOperation('deleteBook',Yii::t("main", "Delete book"));
      $auth->createOperation('selectImageBook',Yii::t("main", "Make list of book's images"));
      $auth->createOperation('getAuthorsBook',Yii::t("main", "Get authors of book"));
     
      $auth->createOperation('indexAuthorBook',Yii::t("main", "View list of links authors and books"));
      $auth->createOperation('createAuthorBook',Yii::t("main", "Create link author and book"));
      $auth->createOperation('adminAuthorBook',Yii::t("main", "Administrate link author and books"));
      $auth->createOperation('updateAuthorBook',Yii::t("main", "Update link author and book"));
      $auth->createOperation('deleteAuthorBook',Yii::t("main", "Delete link author and book"));
     
      $role=$auth->createRole('guest');
      $role->addChild('login');
     
      $role=$auth->createRole('user');
      $role->addChild('guest');
     
      $role=$auth->createRole('manager');
      $role->addChild('user');
      $role->addChild('indexAuthor');
      $role->addChild('createAuthor');
      $role->addChild('adminAuthor');
      $role->addChild('updateAuthor');
      $role->addChild('deleteAuthor');
      $role->addChild('makePageAuthor');
      $role->addChild('indexBook');
      $role->addChild('createBook');
      $role->addChild('adminBook');
      $role->addChild('updateBook');
      $role->addChild('deleteBook');
      $role->addChild('selectImageBook');
      $role->addChild('getAuthorsBook');
      $role->addChild('indexAuthorBook');
      $role->addChild('createAuthorBook');
      $role->addChild('adminAuthorBook');
      $role->addChild('updateAuthorBook');
      $role->addChild('deleteAuthorBook');
      $role=$auth->createRole('administrator');
      $role->addChild('manager');
    }
   
    public function actionAssignUsers() {
      Yii::app()->authManager->assign('administrator', '2');
      Yii::app()->authManager->assign('manager', '3');
      Yii::app()->authManager->assign('manager', '4');
    }
}
Как видно из кода, пользователи с ролями guest и user могут только логиниться, пользователи manager и administrator обладают разными другими правами, которые можно будет в контроллерах обрабатывать примерно так:

public function actionIndex() {
  if (Yii::app()->user->checkAccess('indexAuthor')) {
    $dataProvider=new CActiveDataProvider('Author');
    $this->render('index', array('dataProvider' => $dataProvider));
  } else { // Если прав нет, то перенаправляем на авторизацию
    $this->redirect(array('site/login'));
  }
}
Но для моей задачи достаточно общей проверки (роли пользователя, а не каждого отдельного действия) в таком виде (прописывается в контроллерах):

public function filters() {
  return array('accessControl');
}
public function accessRules() {
  return array(
    array('allow',
      'actions'=>array('index', 'view', 'create', 'update', 'makePage'),
      'roles' => array('manager')
    ),
    array('allow',
      'actions' => array('admin', 'delete'),
      'roles' => array('administrator')
    ),
    array('deny',
      'users' => array('*'),
    ),
  );
}
Для того, чтобы добавить настройки ролей из скрипта, выполним его из директории /protected (предварительно в настройках консоли Yii надо прописать настройки доступа к базе данных и настройки авторизации - всё как в основном файле настройки):

yiic access addRules
Чтобы применить настройки ролей к созданным пользователям:

yiic access assignUsers
В результате работы скрипта таблицы AuthAssignment, AuthItem и AuthItemChild заполнятся соответствующими данными.
Такие настройки авторизации подходят в том случае, если правила поведения и состав пользователей системы меняются редко. В этом случае нет особой необходимости в форме заведения новых пользователей (хотя лучше сделать; с доступом только администратору), в форме управления правами доступа.

Комментарии

Популярные сообщения из этого блога

Пропорциональное распределение суммы

Битрикс: своя геолокация

Bitrix24 API - разбор демо приложения третьего типа