LoginSignup
10
10

More than 5 years have passed since last update.

Yiiのアクセスコントロールの簡単な作り方

Last updated at Posted at 2013-07-03

Yiiには、DBなどの外部データに定義を置けるロールベースのアクセスコントロールの仕組みが搭載されています。

認証と権限付与 6. ロールベースアクセスコントロール

が、これがちょっと回りくどいんですよね。

そこで、もっと簡単なアクセスコントロールを作ってみましょう。

まず、protected/config/main.php の中で、Yii::app()->user のインスタンスをデフォルトの CWebUser から独自のものにすり替えます。

<?php

    // application components
    'components'=>array(
        'user'=>array(
            'class'=>'WebUser', // これ
            // enable cookie-based authentication
            'allowAutoLogin'=>true,
        ),
        // 他のコンポーネントいろいろ
    ),

デフォルトの CWebUser を継承して protected/components/WebUser.php に自分の WebUser を作ります。

CWebUser::isGuest みたいなものをもっといろいろ

<?php
/**
 * Class WebUser
 * 
 * @property bool isAdmin
 */
class WebUser extends CWebUser
{
    /**
     * @return bool
     */
    public function getIsAdmin()
    {
        if ($this->isGuest) {
            return false;
        }
        $dbUser = MyUser::model()->findByAttributes(array(
            'name' => $this->name,
        ));
        return $dbUser && $dbUser->isAdmin;
    }
}

これで、Yii::app()->user->isGuest 以外に、Yii::app()->user->isAdmin なんて問い合わせ方もOKになりました。

この方法なら、自分のモデルに合わせて任意にカスタマイズできます。ちょうど、初期プロジェクトにデフォルトである protected/components/UserIdentity.php のパスワード認証みたいな感じです。あれも、DBのパスワードと照合するように、自分独自の仕様で書き換えますよね。

ということで、これをコントローラの accessRules メソッドの実装で使うとどうなるかというと…

<?php
class MyController extends Controller {

    public function accessRules()
    {
        return array(
            array('allow', // メンバーは表示だけ
                'actions'=>array('index', 'view'),
                'users'=>array('@'),
            ),
            array('allow', // はいここでAdminだけ更新可能っと
                'actions'=>array('create','update','delete'),
                'expression'=>'$user->isAdmin',
            ),
            array('deny',  // ログインしてね
                'users'=>array('*'),
            ),
        );
    }

ユーザ名がズバリ admin じゃないとダメだったのが、任意のロジックでログインユーザが管理者かどうかを聞けるようになりました。一般ユーザが管理者権限を付与されるなんてことも…

IAuthManager::checkAccess() への委譲をやめる

さらにそこから、CWebUser::checkAccess() をオーバーライドして IAuthManager への委譲を奪うと、アプリケーションから見たら高級なロールベースのアクセスコントロールに見えながら、その実は自分のモデル独自の実装で動いているという形にできます。

<?php
/**
 * Class WebUser
 *
 * @property User $dbUser
 * @property bool isAdmin
 * @property bool canAccessMaterialPicture
 * @property bool canAccessFinePicture
 */
class WebUser extends CWebUser
{
    /* @var User */
    private $_dbUser;

    /**
     * @param string $operation
     * @param array $params
     * @param bool $allowCaching
     * @return bool
     */
    public function checkAccess($operation, $params = array(), $allowCaching = true)
    {
        $dbUser = $this->getDbUser($allowCaching);
        // ユーザのモデルインスタンスが operation ができるロールを持っているか
        return $dbUser->hasRoleToDo($operation);
    }

    /**
     * @param bool $allowCaching
     * @throws CException
     * @return User
     */
    public function getDbUser($allowCaching = true)
    {
        if ($allowCaching && isset($this->_dbUser)) {
            return $this->_dbUser;
        } else {
            $user = MyUser::model()->findByAttributes(array(
                'name' => $this->name
            ));
            if (empty($user)) {
                throw new CException("ログインユーザがデータベースに存在しません。");
            }
            if ($allowCaching) {
                $this->_dbUser = $user;
                return $user;
            }
            else {
                unset($this->_dbUser);
            }
            return $user;
        }
    }

    /**
     * @return bool
     */
    public function getIsAdmin()
    {
        // return $this->dbUser->isAdmin;
        return $ths->checkAccess('admin');
    }
}

これで、フルのRBACをするほどのことはない、もっと簡単なロールモデルでもOKになりました。階層的なロールモデルを持っていて、CDbAuthManager でDB上にそのロールの階層構造を構築する、というような方法では設計過剰になるケースも多々あります。いっぽうこの方法では、小さな仕様を MyUser:: hasRoleToDo($operation) あたりにスクラッチで書いて閉じ込めることができます。

使い方は IAuthManager がある場合と同じです。

if (Yii::app()->user->checkAccess('admin')) {
    // 大事なこと
}

ということは…

<?php
class MyController extends Controller {

    public function accessRules()
    {
        return array(
            array('allow', // メンバーは表示だけ
                'actions'=>array('index', 'view'),
                'users'=>array('@'),
            ),
            array('allow', // AdminとWriterだけ更新可能
                'actions'=>array('create','update','delete'),
                'roles'=>array('admin', 'writer'),
            ),
            array('deny',  // ログインしてね
                'users'=>array('*'),
            ),
        );
    }

やった。 expressionroles になりました。

accessRules() から eval コードを追い出せました。勝手なコンポーネントの仕様に依存することもありません。後でフルのRBACに透過的に入れ替えることもできます。めでたしめでたし。

Yiiは、オブジェクト指向的に一貫したルールで、どんなコンポーネントでも勝手に置換できるのがいいですね。

10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10