security-authorization.md 24.6 KB
Newer Older
1 2
権限付与
========
3

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

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


10 11
アクセス制御フィルタ (ACF)
--------------------------
12

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

17
下記のコードは、[[yii\filters\AccessControl]] として実装された ACF の使い方を示すものです。
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

```php
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' => ['@'],
                    ],
                ],
            ],
        ];
    }
    // ...
}
```

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

下記は、`matchCallback` オプションを利用する方法を示す例です。
このオプションによって、任意のアクセス制御ロジックを書くことが可能になります。
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

```php
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';
                        }
                    ],
                ],
            ],
        ];
    }

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


154 155
ロールベースアクセス制御 (RBAC)
---------------------------------------
156

157 158
ロールベースアクセス制御 (RBAC) は、単純でありながら強力な集中型のアクセス制御を提供します。
RBAC と他のもっと伝統的なアクセス制御スキーマとの比較に関する詳細については、[Wiki 記事](http://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%BC%E3%83%AB%E3%83%99%E3%83%BC%E3%82%B9%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%88%B6%E5%BE%A1) を参照してください。
159

160 161
Yii は、[NIST RBAC モデル](http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf) に従って、一般的階層型 RBAC を実装しています。
RBAC の機能は、[[yii\rbac\ManagerInterface|authManager]] [アプリケーションコンポーネント](structure-application-components.md) を通じて提供されます。
162

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

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


169
### 基本的な概念
170

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

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

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


186
### RBAC マネージャを構成する
187

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

193
次のコードは、アプリケーションの構成情報で `authManager` を構成する方法を示すものです。
194 195 196 197 198 199 200 201 202 203 204 205 206

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

207
`authManager``\Yii::$app->authManager` によってアクセスすることが出来ます。
208

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


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

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

217 218 219 220 221
- ロールと許可を定義する
- ロールと許可の関係を定義する
- 規則を定義する
- 規則をロールと許可に結び付ける
- ロールをユーザに割り当てる
222

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

225
権限の階層が全く変化せず、決った数のユーザしか存在しない場合は、`authManager` が提供する API によって権限付与データを一回だけ初期設定する [コンソールコマンド](tutorial-console.md#create-command) を作ることが出来ます。
226 227 228 229 230 231 232 233 234 235 236 237 238 239

```php
<?php
namespace app\commands;

use Yii;
use yii\console\Controller;

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

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

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

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

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

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

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

272
![単純な RBAC 階層](images/rbac-hierarchy-1.png "単純な RBAC 階層")
273

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

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

```php
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);

290
        // 次の三行が追加されたものです
291 292 293 294 295 296 297 298 299 300 301
        $auth = Yii::$app->authManager;
        $authorRole = $auth->getRole('author');
        $auth->assign($authorRole, $user->getId());

        return $user;
    }

    return null;
}
```

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


305
### 規則を使う
306

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

```php
namespace app\rbac;

use yii\rbac\Rule;

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

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

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

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

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

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

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

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

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

362
![規則を持つ RBAC 階層](images/rbac-hierarchy-2.png "規則を持つ RBAC 階層")
363

364
### アクセスチェック
365

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

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

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

378
![アクセスチェック](images/rbac-access-check-1.png "アクセスチェック")
379

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

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

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

390
![アクセスチェック](images/rbac-access-check-2.png "アクセスチェック")
391

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

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

399
![アクセスチェック](images/rbac-access-check-3.png "アクセスチェック")
400

401
### デフォルトロールを使う
402

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

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

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

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


```php
namespace app\rbac;

use Yii;
use yii\rbac\Rule;

/**
425
 * ユーザのグループが合致するかどうかをチェックする
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
 */
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);
453
// ... $author の子として許可を追加 ...
454 455 456 457 458

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

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

465
次に、`authManager` の構成情報で、この二つのロールを [[yii\rbac\BaseManager::$defaultRoles]] としてリストします。
466 467 468 469 470 471 472 473 474 475 476 477 478 479

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

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