tohokuaikiのチラシの裏

技術的ネタとか。

LaravelのPolicyで丸二日ほど悩む…効かないってか効きすぎるというか…

やりたいこと

apiのrouteに対して適宜認可を掛ける。対象になるのは、usersテーブル。これはLaravelのデフォルトのユーザーテーブル。

show/editできるのは、管理者とその当該ユーザーのみとか。

やったこと

routes/api.php

<?php
Route::middleware('auth')->group(function () {
    Route::resources([
        'user' => 'UserController',

として、(このauthはログイン状態を見ているだけ)

app/Http/Controllers/UserController.php にリソースコントローラの認可を設定

<?php
class UserController extends Controller
{
    /**
     * UserController constructor.
     */
    public function __construct()
    {
        $this->authorizeResource(User::class, 'user');
    }

で、ポリシーファイル app/Policies/UserPolicy.php

<?php
class UserPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view the model.
     *
     * @param  \App\User  $user
     * @param  \App\User  $model
     * @return mixed
     */
    public function view(User $user, User $model)
    {
         return $this->viewAny($user) || $user->id === $model->id;
    }

を作ってサービスプロバイダーに登録 app/Providers/AuthServiceProvider.php

<?php
class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        App\User::class => App\Policies\UserPolicy::class,

症状

403エラーが返ってくる

{ exception: "Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException",
  file: "/home/vagrant/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php"
  line: 223
  message: "This action is unauthorized." }
  trace : {...}
}

みたいな。

対処方法

ControllerのメソッドDIを変えると全然違う

<?php
public function show($id)

の場合だと、403になる。というか、そもそもPolicyのview()まで来ていない。

<?php
public function show($id)

の場合だと、403になる。というか、そもそもUserPolicy::view()まで来ていない。

<?php
public function show(Request $request, User $user)

とすると、Policy::view()まで来るようになる。

ということで

Policyを使って認可する場合は、Controllerのメソッド引数も考慮しなければならないようだ。
LaravelってDIの魔法みたいなことするけど、こういうわけわからない挙動もあるのどうなんだ…

俺的な静的Facadeの作り方

LaravelでオレオレUtililyクラスを使いたいと思った。Staticメソッドを自分が使う分だけのもの。

で、Facadeを使うと良いかなと思って、LaravelのFacadeを作り方を調べたんだけど、なんか、ServiceProviderを登録したりFacadeクラスと実行クラスを分けたりとかなんだりかんだりとややこしそうだった。

参考にしたのはこのあたり。 LaravelのFacade(ファサード)でオリジナルの処理クラスを定義する入門編
【Laravel 5.4】ファサード の作り方(でも多用は良くない) - Qiita LaravelのFacadeの作り方 - Qiita

で、結局これだけでいいじゃんっていうのが以下。

Laravelバージョン

Laravel Framework 6.18.26

Facade敬称クラスを作る

app/Facades/ITTUtil.php

<?php
namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class ITTUtil extends Facade
{
    const FACADE_BIND_KEY = 'ittutil';

    protected static function getFacadeAccessor() {
        return self::FACADE_BIND_KEY;
    }

    /**
     * @return array List of Japanese Prefectures.
     */
    public static function pref()
    {
        return [
            1 => '北海道', 2 => '青森県', 3 => '岩手県', 4 => '宮城県',
        ];
    }
}

ServiceProviderに登録(これ、不要だった)

なんか、参考記事ではServiceProviderを新しく作って…ってやってたけど、AppServiceProviderがあるのでこれ使えばいいんじゃないか?と思って app/Providers/AppServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(
            'ittutil'
        );
    }

で、次にconfig/app.phpに登録

config/app.php

<?php

    'aliases' => [

        'ITTUtil' => App\Facades\ITTUtil::class,

なんか、これだけでイケた。

たぶん、DIとか使わなければいけるんだろう。

LaravelのPolicyを二重にしたらダメだった

$ ./artisan --version
Laravel Framework 6.18.26

です。

Policy使ってアクセス制限

こんな感じ。

<?php
class UserController extends Controller
{
    /**
     * UserController constructor.
     */
    public function __construct()
    {
        $this->authorizeResource(User::class);
    }


    /**
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function index(Request $request)
    {
        $this->authorize('viewAny');
        $users = User::all();
        return response()->json(['users' => $users]);
    }

そしたら、UserPolicyで

<?php
class UserPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view any models.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function viewAny(User $user)
    {
        return true;
    }

ってしてるのにエラーが出る。 なんでUserController::index()で$this->authorize('viewAny')をわざわざ入れたかって言うのは、Controllerメソッドとの対応が https://reffect.co.jp/laravel/laravel-gate-policy-understand#Policy-3 このページの表を見ても無かったから。

あれー、って思って、実装している
laravel\framework\src\Illuminate\Foundation\Auth\Access\AuthorizesRequests.php を見てみると、

<?php
trait AuthorizesRequests
{
...
    protected function resourceAbilityMap()
    {
        return [
            'index' => 'viewAny',
            'show' => 'view',
            'create' => 'create',
            'store' => 'create',
            'edit' => 'update',
            'update' => 'update',
            'destroy' => 'delete',
        ];
    }

なー、indexとviewAnyが対応してるやん…

2回やってもture返すならいいんじゃないかと思ったけど、2回やるとダメなんね。

ということで、 $this->authorize('viewAny'); を取って解決。

JetBrainのPhpStormがアップデートに失敗してたのを直した件

PhpStormが自動アップデートするのがずっと失敗するのをずっと放置してたのだけど、やっと直した。

なんか、しょっちゅうこんなエラー出てて「ま、いいか…」としてたんだけど。

f:id:tohokuaiki:20200720103641p:plain

Failed to extract packeage.Failed to find 7zip executable in the package at C:.....

で、このエラー見たら単に7z.exeが無いだけなんで、 https://sevenzip.osdn.jp/ からダウンロードしたファイルの中にある7z.exeを上記のエラーメッセージのパスに入れたら無事アップデートできた。

最初、7zipを普通にインストールしてPATHを通せばいいのかな?とか思ってやったらダメで実はもっと簡単な話だった。