s平面の左側

左側なので安定してます(制御工学の話は出てきません)

続・Laravel 5.4 におけるユーザ権限管理の実装に関する考察

このエントリーの続き。

blog.okashoi.net

このエントリーを書いた後早々に「Laravel のドキュメントを全部読め」という旨のツッコミを頂いた。

そして、Laravel の機能でやりたいことが実現できることを学んだ。

laravel.com

日本語訳はこちら→ 認可 5.4 Laravel

ということで、当初は続きのエントリーとして「それぞれの方法のメリット・デメリットの比較」を書こうと思っていたのだが、公式の方法がどちらの方法にも勝るので単に Laravel の認可の機能について説明をする。

実現したいこと

前回のエントリーの内容では説明のためには不充分なので、より具体的な例を挙げる。

前回のエントリーで示した条件(再掲)

前提:Laravel 5.4 で開発している web システム

ユーザごとに「このデータは編集できる」「このデータは閲覧のみ可」といった権限管理をしたい。

登場するテーブルは以下の2つ。

(テーブル名は実際のものとは異なる。あくまでイメージ)

  • users
    • ユーザのテーブル。後述する groups テーブルの id を外部キーとして持つ。
  • groups
    • 権限グループのマスタテーブル。id と「閲覧のみ」「管理者」といった表示用の名前 name のみを格納。

より具体的な実現したいこと

前提

以下に説明するような posts というテーブルがあるとする。

  • 「所有者」としてuser テーブルの id を外部キーとして持つ。
  • content, category というカラムを持つ。

目的

posts に対応する Post という Model に対して次のような権限管理したいを考える。

○:可 ×:不可

操作 閲覧のみ 所有者 管理者
閲覧
新規作成 × -
content の更新 ×
category の更新 × ×
削除 × ×

また、「閲覧のみ」ユーザのgroups.id は 1 、「管理者」の groups.id は 9 とする。

実装方法

Policy の作成

app/Policies/PostPolicy.php

<?php

namespace App\Policies;

use App\Models\User;
use App\Models\Post;

class PostPolicy
{
    /**
     * 全てのメソッドの前に呼ばれる
     * null を返した場合、各メソッドに委ねられる
     *
     * @params User $user
     * @return bool|null
     */
    public function before(User $user)
    {
        // 「管理者」は全ての操作が可能
        if ($user->group_id === 9) {
            return true;
        }

        return null;
    }

    /**
     * 閲覧が可能かどうか
     *
     * @params User $user
     * @params Post $post
     * @return bool
     */
    public function view(User $user, Post $post): bool
    {
        // 全てのユーザが可能
        return true;
    }

    /**
     * 新規作成が可能かどうか
     *
     * @params User $user
     * @return bool
     */
    public function create(User $user): bool
    {
        // 「閲覧のみ」ユーザでないこと
        return $user->group_id !== 1;
    }

    /**
     * content の更新が可能か
     *
     * @params User $user
     * @params Post $post
     * @return bool
     */
    public function updateContent(User $user, Post $post): bool
    {
        // 所有者であること
        return $user->id === $post->user_id;
    }

    /**
     * category の更新が可能か
     *
     * @params User $user
     * @params Post $post
     * @return bool
     */
    public function updateCategory(User $user, Post $post): bool
    {
        // (「管理者」ユーザ以外)全てのユーザが不可
        return false;
    }

    /**
     * 削除が可能か
     *
     * @params User $user
     * @params Post $post
     * @return bool
     */
    public function delete(User $user, Post $post): bool
    {
        // (「管理者」ユーザ以外)全てのユーザが不可
        return false;
    }
}

※実際は「管理者かどうか」といった判定を Model のメソッドにしておくのがよい

Policy の登録

app/Providers/AuthServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * アプリケーションにマップ付されたポリシー
     *
     * @var array
     */
    protected $policies = [
        App\Models\Post::class => App\Policies\PostPolicy::class,
    ];

    /**
     * アプリケーションの全認証/認可サービスの登録
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // ...
    }
}

ロジック内で利用

<?php

use App\Models\Post;

$user = \Auth::user();

// $user が $post を閲覧できるか
$user->can('view', $post);


// $user が Post を新規作成できるか
$user->can('create', Post::class);

Middleware として利用

許可されていない場合は 403 を返す。

routes/web.php

<?php

Route::post('/posts', 'PostController@create')->middleware('can:create, App\Models\Post');

ControllerHelper

authorize() メソッドが利用可能。こちらも許可されていない場合 403 を返す。

app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PostController extends Controller
{
    /**
     * ポストの削除
     *
     * @param  Request  $request
     * @param  Post  $post
     * @return Response
     */
    public function delete(Request $request, Post $post)
    {
        $this->authorize('delete', $post);

        //...
    }
}

Blade テンプレート内で利用

@can('updateCategory', $post)
    {{-- カテゴリ編集可の場合 --}}
@else
    {{-- 不可の場合 --}}
@endcan

反省

勉強不足で本来不必要な考察・議論をしていた。

まずはドキュメントを調べる、というのは基本であるはずだが、つい気が緩んでいたようだ。