権限付与

Note|注意: この節はまだ執筆中です。

権限付与は、ユーザが何かをするのに十分な許可を得ているか否かを確認するプロセスです。 Yii は二つの権限付与の方法を提供しています。すなわち、アクセス制御フィルタ (ACF) と、ロールベースアクセス制御 (RBAC) です。

アクセス制御フィルタ (ACF)

アクセス制御フィルタ (ACF) は、何らかの単純なアクセス制御だけを必要とするアプリケーションで使うのに最も適した、単純な権限付与の方法です。 その名前が示すように、ACF は、コントローラまたはモジュールにビヘイビアとしてアタッチすることが出来るアクションフィルタです。 ACF は一連の [[yii\filters\AccessControl::rules|アクセス規則]] をチェックして、現在のユーザがリクエストしたアクションにアクセスすることが出来るかどうかを確認します。

下記のコードは、[[yii\filters\AccessControl]] として実装された ACF の使い方を示すものです。

use yii\filters\AccessControl;

class SiteController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['login', 'logout', 'signup'],
                'rules' => [
                    [
                        'allow' => true,
                        'actions' => ['login', 'signup'],
                        'roles' => ['?'],
                    ],
                    [
                        'allow' => true,
                        'actions' => ['logout'],
                        'roles' => ['@'],
                    ],
                ],
            ],
        ];
    }
    // ...
}

上記のコードにおいて、ACF は site コントローラにビヘイビアとしてアタッチされています。 これはアクションフィルタを使用する典型的な方法です。 only オプションは、ACF が loginlogoutsignup のアクションにのみ適用されるべきであることを指定しています。 rules オプションは [[yii\filters\AccessRule|アクセス規則]] を指定するものであり、以下のように読むことが出来ます。

  • 全てのゲストユーザ (まだ認証されていないユーザ) に、'login' と 'singup' のアクションにアクセスすることを許可します。 roles オプションに疑問符 ? が含まれていますが、これは「ゲスト」として認識される特殊なトークンです。
  • 認証されたユーザに、'logout' アクションにアクセスすることを許可します。 @ という文字はもう一つの特殊なトークンで、認証されたユーザとして認識されるものです。

ACF が権限のチェックを実行するときには、規則を一つずつ上から下へ、適用されるものを見つけるまで調べます。 そして、適用される規則の allow の値が、ユーザが権限を有するか否かを判断するのに使われます。 適用される規則が一つもなかった場合は、ユーザが権限をもたないことを意味し、ACF はアクションの継続を中止します。

デフォルトでは、ユーザが現在のアクションにアクセスする権限を持っていないと判定した場合は、ACF は以下のことだけを行います。

  • ユーザがゲストである場合は、[[yii\web\User::loginRequired()]] を呼び出します。 このメソッドで、ブラウザをログインページにリダイレクトすることが出来ます。
  • ユーザが既に認証されている場合は、[[yii\web\ForbiddenHttpException]] を投げます。

この動作は、[[yii\filters\AccessControl::denyCallback]] プロパティを構成することによって、カスタマイズすることが出来ます。

[
    'class' => AccessControl::className(),
    'denyCallback' => function ($rule, $action) {
        throw new \Exception('このページにアクセスする権限がありません。');
    }
]

[[yii\filters\AccessRule|アクセス規則]] は多くのオプションをサポートしています。 以下はサポートされているオプションの要約です。 [[yii\filters\AccessRule]] を拡張して、あなた自身のカスタマイズしたアクセス規則のクラスを作ることも出来ます。

  • [[yii\filters\AccessRule::allow|allow]]: これが「許可」の規則であるか、「禁止」の規則であるかを指定します。

  • [[yii\filters\AccessRule::actions|actions]]: どのアクションにこの規則が適用されるかを指定します。 これはアクション ID の配列でなければなりません。 比較は大文字と小文字を区別します。 このオプションが空であるか指定されていない場合は、規則が全てのアクションに適用されることを意味します。

  • [[yii\filters\AccessRule::controllers|controllers]]: どのコントローラにこの規則が適用されるかを指定します。 これはコントローラ ID の配列でなければなりません。 比較は大文字と小文字を区別します。 このオプションが空であるか指定されていない場合は、規則が全てのコントローラに適用されることを意味します。

  • [[yii\filters\AccessRule::roles|roles]]: どのユーザロールにこの規則が適用されるかを指定します。 二つの特別なロールが認識されます。 これらは、[[yii\web\User::isGuest]] によって判断されます。

    • ?: ゲストユーザ (まだ認証されていないユーザ) を意味します。
    • @: 認証されたユーザを意味します。

その他のロール名を使う場合には、RBAC (次の節で説明します) が必要とされ、判断のために [[yii\web\User::can()]] が呼び出されます。 このオプションが空であるか指定されていない場合は、規則が全てのロールに適用されることを意味します。

  • [[yii\filters\AccessRule::ips|ips]]: どの [[yii\web\Request::userIP|クライアントの IP アドレス]] にこの規則が適用されるかを指定します。 IP アドレスは、最後にワイルドカード * を含むことが出来て、同じプレフィクスを持つ IP アドレスに合致させることが出来ます。 例えば、'192.168.*' は、'192.168.' のセグメントに属する全ての IP アドレスに合致します。 このオプションが空であるか指定されていない場合は、規則が全ての IP アドレスに適用されることを意味します。

  • [[yii\filters\AccessRule::verbs|verbs]]: どのリクエストメソッド (HTTP 動詞、例えば GETPOST) にこの規則が適用されるかを指定します。 比較は大文字と小文字を区別しません。

  • [[yii\filters\AccessRule::matchCallback|matchCallback]]: この規則が適用されるべきか否かを決定するために呼び出されるべき PHP コーラブルを指定します。

  • [[yii\filters\AccessRule::denyCallback|denyCallback]]: この規則がアクセスを禁止する場合に呼び出されるべき PHP コーラブルを指定します。

下記は、matchCallback オプションを利用する方法を示す例です。 このオプションによって、任意のアクセス制御ロジックを書くことが可能になります。

use yii\filters\AccessControl;

class SiteController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['special-callback'],
                'rules' => [
                    [
                        'actions' => ['special-callback'],
                        'allow' => true,
                        'matchCallback' => function ($rule, $action) {
                            return date('d-m') === '31-10';
                        }
                    ],
                ],
            ],
        ];
    }

    // matchCallback が呼ばれる。このページは毎年10月31日だけアクセス出来ます。
    public function actionSpecialCallback()
    {
        return $this->render('happy-halloween');
    }
}

ロールベースアクセス制御 (RBAC)

ロールベースアクセス制御 (RBAC) は、単純でありながら強力な集中型のアクセス制御を提供します。 RBAC と他のもっと伝統的なアクセス制御スキーマとの比較に関する詳細については、Wiki 記事 を参照してください。

Yii は、NIST RBAC モデル に従って、一般的階層型 RBAC を実装しています。 RBAC の機能は、[[yii\rbac\ManagerInterface|authManager]] アプリケーションコンポーネント を通じて提供されます。

RBAC を使用することには、二つの作業が含まれます。 最初の作業は、RBAC 権限付与データを作り上げることであり、第二の作業は、権限付与データを使って必要とされる場所でアクセスチェックを実行することです。

説明を容易にするために、まず、いくつかの基本的な RBAC の概念を導入します。

基本的な概念

ロール (役割) は、許可 (例えば、記事を作成する、記事を更新するなど) のコレクションです。 一つのロールを一人または複数のユーザに割り当てることが出来ます。 ユーザが特定の許可を有しているか否かをチェックするためには、その許可を含むロールがユーザに割り当てられているか否かをチェックすればよいのです。

各ロールまたは許可に関連付けられた 規則 が存在することがあり得ます。 規則とは、アクセスチェックの際に、対応するロールや許可が現在のユーザに適用されるか否かを決定するために実行されるコード断片のことです。 例えば、「記事更新」の許可は、現在のユーザが記事の作成者であるかどうかをチェックする規則を持つことが出来ます。 そして、アクセスチェックのときに、ユーザが記事の作成者でない場合は、彼/彼女は「記事更新」の許可を持っていないと見なすことが出来ます。

ロールおよび許可は、ともに、階層的に構成することが出来ます。 具体的に言えば、一つのロールは他のロールと許可を含むことが出来、許可は他の許可を含むことが出来ます。 Yii は、一般的な 半順序 階層を実装していますが、これはその特殊形として 階層を含むものです。 ロールは許可を含むことが出来ますが、許可はロールを含むことが出来ません。

RBAC マネージャを構成する

権限付与データを定義してアクセスチェックを実行する前に、[[yii\base\Application::authManager|authManager]] アプリケーションコンポーネントを構成する必要があります。 Yii は二種類の権限付与マネージャを提供しています。すなわち、[[yii\rbac\PhpManager]] と [[yii\rbac\DbManager]] です。 前者は権限付与データを保存するのに PHP スクリプトファイルを使いますが、後者は権限付与データをデータベースに保存します。 あなたのアプリケーションが非常に動的なロールと許可の管理を必要とするのでなければ、前者を使うことを考慮するのが良いでしょう。

authManager を PhpManager で構成する

次のコードは、アプリケーションの構成情報で [[yii\rbac\PhpManager]] クラスを使って authManager を構成する方法を示すものです。

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\PhpManager',
        ],
        // ...
    ],
];

これで authManager\Yii::$app->authManager によってアクセスすることが出来るようになります。

Tip|ヒント: デフォルトでは、[[yii\rbac\PhpManager]] は RBAC データを @app/rbac/ ディレクトリの下のファイルに保存します。 権限の階層をオンラインで変更する必要がある場合は、必ず、ウェブサーバのプロセスがこのディレクトリとその中の全てのファイルに対する書き込み権限を有するようにしてください。

authManager を DbManager で構成する

次のコードは、アプリケーションの構成情報で [[yii\rbac\DbManager]] クラスを使って authManager を構成する方法を示すものです。

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\DbManager',
        ],
        // ...
    ],
];

DbManager は四つのデータベーステーブルを使ってデータを保存します。

- [[yii\rbac\DbManager::$itemTable itemTable]]: 権限アイテムを保存するためのテーブル。デフォルトは "auth_item"。
- [[yii\rbac\DbManager::$assignmentTable assignmentTable]]: 権限アイテムの割り当てを保存するためのテーブル。デフォルトは "auth_assignment"。
- [[yii\rbac\DbManager::$ruleTable ruleTable]]: 規則を保存するためのテーブル。デフォルトは "auth_rule"。

先に進む前にこれらのテーブルをデータベースに作成する必要があります。 そのためには、@yii/rbac/migrations に保存されているマイグレーションを使うことが出来ます。

yii migrate --migrationPath=@yii/rbac/migrations

これで authManager\Yii::$app->authManager によってアクセスすることが出来るようになります。

権限付与データを構築する

権限付与データを構築する作業は、つまるところ、以下のタスクに他なりません。

  • ロールと許可を定義する
  • ロールと許可の関係を定義する
  • 規則を定義する
  • 規則をロールと許可に結び付ける
  • ロールをユーザに割り当てる

権限付与に要求される柔軟性の程度によって、上記のタスクのやりかたも異なってきます。

権限の階層が全く変化せず、決った数のユーザしか存在しない場合は、authManager が提供する API によって権限付与データを一回だけ初期設定する コンソールコマンド を作ることが出来ます。

<?php
namespace app\commands;

use Yii;
use yii\console\Controller;

class RbacController extends Controller
{
    public function actionInit()
    {
        $auth = Yii::$app->authManager;

        // "createPost" という許可を追加
        $createPost = $auth->createPermission('createPost');
        $createPost->description = '記事を投稿';
        $auth->add($createPost);

        // "updatePost" という許可を追加
        $updatePost = $auth->createPermission('updatePost');
        $updatePost->description = '記事を更新';
        $auth->add($updatePost);

        // "author" というロールを追加し、このロールに "createPost" 許可を与える
        $author = $auth->createRole('author');
        $auth->add($author);
        $auth->addChild($author, $createPost);

        // "admin" というロールを追加し、このロールに "updatePost" 許可を与える
        // 同時に、"author" ロールの持つ許可も与える
        $admin = $auth->createRole('admin');
        $auth->add($admin);
        $auth->addChild($admin, $updatePost);
        $auth->addChild($admin, $author);

        // ロールをユーザに割り当てる。1 と 2 は IdentityInterface::getId() によって返される ID
        // 通常はユーザモデルの中で実装する
        $auth->assign($author, 2);
        $auth->assign($admin, 1);
    }
}

yii rbac/init によってコマンドを実行した後には、次の権限階層が得られます。

単純な RBAC 階層

投稿者 (author) は記事を投稿することが出来、管理者 (admin) は記事を更新することに加えて投稿者が出来る全てのことが出来ます。

あなたのアプリケーションがユーザ登録を許している場合は、新しく登録されたユーザに一度ロールを割り当てる必要があります。 例えば、アドバンストアプリケーションテンプレートにおいては、登録したユーザの全てを「投稿者」にするために、frontend\models\SignupForm::signup() を次のように修正しなければなりません。

public function signup()
{
    if ($this->validate()) {
        $user = new User();
        $user->username = $this->username;
        $user->email = $this->email;
        $user->setPassword($this->password);
        $user->generateAuthKey();
        $user->save(false);

        // 次の三行が追加されたものです
        $auth = Yii::$app->authManager;
        $authorRole = $auth->getRole('author');
        $auth->assign($authorRole, $user->getId());

        return $user;
    }

    return null;
}

動的に更新される権限付与データを持つ複雑なアクセス制御を必要とするアプリケーションについては、authManager が提供する API を使って、特別なユーザインタフェイス (つまり、管理パネル) を開発する必要があるでしょう。

規則を使う

既に述べたように、規則がロールと許可に制約を追加します。 規則は [[yii\rbac\Rule]] を拡張したクラスであり、[[yii\rbac\Rule::execute()|execute()]] メソッドを実装しなければなりません。 前に作った権限階層においては、投稿者は自分自身の記事を編集することが出来ないものでした。これを修正しましょう。 最初に、ユーザが記事の投稿者であることを確認する規則が必要です。

namespace app\rbac;

use yii\rbac\Rule;

/**
 * authorID がパラメータで渡されたユーザと一致するかチェックする
 */
class AuthorRule extends Rule
{
    public $name = 'isAuthor';

    /**
     * @param string|integer $user ユーザ ID
     * @param Item $item この規則が関連付けられているロールまたは許可
     * @param array $params ManagerInterface::checkAccess() に渡されたパラメータ
     * @return boolean 関連付けられたロールまたは許可を認めるか否かを示す値
     */
    public function execute($user, $item, $params)
    {
        return isset($params['post']) ? $params['post']->createdBy == $user : false;
    }
}

上の規則は、post$user によって作成されたかどうかをチェックします。 次に、前に使ったコマンドの中で、updateOwnPost という特別な許可を作成します。

$auth = Yii::$app->authManager;

// 規則を追加する
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);

// "updateOwnPost" という許可を作成し、それに規則を関連付ける
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = '自分の記事を更新';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);

// "updateOwnPost" は "updatePost" から使われる
$auth->addChild($updateOwnPost, $updatePost);

// "author" に自分の記事を更新することを許可する
$auth->addChild($author, $updateOwnPost);

これで、次のような権限階層になります。

規則を持つ RBAC 階層

アクセスチェック

権限付与データが準備できてしまえば、アクセスチェックは [[yii\rbac\ManagerInterface::checkAccess()]] メソッドを呼ぶだけの簡単な仕事です。 たいていのアクセスチェックは現在のユーザに関するものですから、Yii は、便利なように、[[yii\web\User::can()]] というショートカットメソッドを提供しています。 これは、次のようにして使うことが出来ます。

if (\Yii::$app->user->can('createPost')) {
    // 記事を作成する
}

現在のユーザが ID=1 である Jane であるとすると、createPost からスタートして Jane まで到達しようと試みます。

アクセスチェック

ユーザが記事を更新することが出来るかどうかをチェックするためには、前に説明した AuthorRule によって要求される追加のパラメータを渡す必要があります。

if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
    // 記事を更新する
}

現在のユーザが John であるとすると、次の経路をたどります。

アクセスチェック

updatePost からスタートして、updateOwnPost を通過します。 通過するためには、AuthorRuleexecute メソッドで true を返さなければなりません。 execute メソッドは can メソッドの呼び出しから $params を受け取りますので、その値は ['post' => $post] です。 すべて OK であれば、John に割り当てられている author に到達します。

Jane の場合は、彼女が管理者であるため、少し簡単になります。

アクセスチェック

デフォルトロールを使う

デフォルトロールというのは、全て のユーザに 黙示的 に割り当てられるロールです。 [[yii\rbac\ManagerInterface::assign()]] を呼び出す必要はなく、権限付与データはその割り当て情報を含みません。

デフォルトロールは、通常、そのロールが当該ユーザに適用されるかどうかを決定する規則と関連付けられます。

デフォルトロールは、たいていは、何らかのロールの割り当てを既に持っているアプリケーションにおいて使われます。 例えば、アプリケーションによっては、ユーザのテーブルに "group" というカラムを持って、個々のユーザが属する特権グループを表している場合があります。 それぞれの特権グループを RBAC ロールに対応付けることが出来るのであれば、デフォルトロールの機能を使って、それぞれのユーザに RBAC ロールを自動的に割り当てることが出来ます。 どのようにすればこれが出来るのか、例を使って説明しましょう。

ユーザのテーブルに group というカラムがあって、1 は管理者グループ、2 は投稿者グループを示していると仮定しましょう。 これら二つのグループの権限を表すために、それぞれ、adminauthor という RBAC ロールを作ることにします。 このとき、次のように RBAC データをセットアップすることが出来ます。

namespace app\rbac;

use Yii;
use yii\rbac\Rule;

/**
 * ユーザのグループが合致するかどうかをチェックする
 */
class UserGroupRule extends Rule
{
    public $name = 'userGroup';

    public function execute($user, $item, $params)
    {
        if (!Yii::$app->user->isGuest) {
            $group = Yii::$app->user->identity->group;
            if ($item->name === 'admin') {
                return $group == 1;
            } elseif ($item->name === 'author') {
                return $group == 1 || $group == 2;
            }
        }
        return false;
    }
}

$auth = Yii::$app->authManager;

$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);

$author = $auth->createRole('author');
$author->ruleName = $rule->name;
$auth->add($author);
// ... $author の子として許可を追加 ...

$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $author);
// ... $admin の子として許可を追加 ...

上記において、"author" が "admin" の子として追加されているため、規則クラスの execute() メソッドを実装する時には、この階層関係にも配慮しなければならないことに注意してください。 このために、ロール名が "author" である場合には、execute() メソッドは、ユーザのグループが 1 または 2 である (ユーザが "admin" グループまたは "author" グループに属している) ときに true を返しています。

次に、authManager の構成情報で、この二つのロールを [[yii\rbac\BaseManager::$defaultRoles]] としてリストします。

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\PhpManager',
            'defaultRoles' => ['admin', 'author'],
        ],
        // ...
    ],
];

このようにすると、アクセスチェックを実行すると、adminauthor の両方のロールは、それらと関連付けられた規則を評価することによってチェックされるようになります。 規則が true を返せば、そのロールが現在のユーザに適用されることになります。 上述の規則の実装に基づいて言えば、ユーザの group の値が 1 であれば、admin ロールがユーザに適用され、group の値が 2 であれば author ロールが適用されるということを意味します。