Skip to content

Latest commit

 

History

History
572 lines (438 loc) · 29.1 KB

security-authorization.md

File metadata and controls

572 lines (438 loc) · 29.1 KB

権限付与

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

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

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

下記のコードは、site コントローラで ACF を使う方法を示すものです。

use yii\web\Controller;
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 のアクションにのみ適用されるべきであることを指定しています。 site コントローラの他の全てのアクションには ACF の影響は及びません。 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 の配列でなければなりません。 コントローラがモジュールに属する場合は、モジュール ID をコントローラ ID の前に付けます。 比較は大文字と小文字を区別します。 このオプションが空であるか指定されていない場合は、規則が全てのコントローラに適用されることを意味します。

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

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

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

  • [[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 スクリプトファイルを使いますが、後者は権限付与データをデータベースに保存します。 あなたのアプリケーションが非常に動的なロールと許可の管理を必要とするのでなければ、前者を使うことを考慮するのが良いでしょう。

PhpManager を使用する

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

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

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

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

DbManager を使用する

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

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

Note: yii2-basic-app テンプレートを使おうとする場合は、config/web.php に加えて、config/console.php 構成ファイルにおいても uathManager を宣言する必要があります。 yii2-advanced-app の場合は、authManagercommon/config/main.php で一度だけ宣言されなければなりません。

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

  • [[yii\rbac\DbManager::$itemTable|itemTable]]: 権限アイテムを保存するためのテーブル。デフォルトは "auth_item"。
  • [[yii\rbac\DbManager::$itemChildTable|itemChildTable]]: 権限アイテムの階層を保存するためのテーブル。デフォルトは "auth_item_child"。
  • [[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
        // IdentityInterface::getId() は、通常は User モデルの中で実装される
        $auth->assign($author, 2);
        $auth->assign($admin, 1);
    }
}

Note: アドバンストテンプレートを使おうとするときは、RbacControllerconsole/controllers ディレクトリの中に置いて、名前空間を console\controllers に変更する必要があります。

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|int $user ユーザ ID
     * @param Item $item この規則が関連付けられているロールまたは許可
     * @param array $params ManagerInterface::checkAccess() に渡されたパラメータ
     * @return bool 関連付けられたロールまたは許可を認めるか否かを示す値
     */
    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\filters\AccessControl]] を使います。

public function behaviors()
{
    return [
        'access' => [
            'class' => AccessControl::className(),
            'rules' => [
                [
                    'allow' => true,
                    'actions' => ['index'],
                    'roles' => ['managePost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['view'],
                    'roles' => ['viewPost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['create'],
                    'roles' => ['createPost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['update'],
                    'roles' => ['updatePost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['delete'],
                    'roles' => ['deletePost'],
                ],
            ],
        ],
    ];
}

全ての CRUD 操作がまとめて管理される場合は、managePost のような単一の許可を使い、 [[yii\web\Controller::beforeAction()]] の中でそれをチェックするのが良いアイデアです。

デフォルトロールを使う

デフォルトロールというのは、全て のユーザに 黙示的 に割り当てられるロールです。 [[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 ロールが適用されるということを意味します。