LoginSignup
1
1

More than 3 years have passed since last update.

Policyが紐づいていないModelに対してGate::deniesを実行したらtrueが返ってきた

Last updated at Posted at 2019-05-27

環境

Laravel: 5.5
PHP: 7.2

前提

同じModelでもグローバルスコープをかけたい場合とかけたくない場合で2つModelを作っていた。

グローバルスコープをかけている方はかけていない方を継承したModelになっている。

グローバルスコープをかけている方は、権限やdeleted_atの関係でかけていたのでもちろんPolicyも登録されていた。

かけていない方は、権限を気にせず参照したい場合があるという理由でかけておらずPolicyもつけていなかった。

起こったこと

URLに特定のパラメータ(hoge_id=1のような形)が入っているかでModelの持ってくる方法を変えている三項演算子があった。

$hoge = $request->has('hoge_id') ? Hoge::findOrFail($request->get('hoge_id')) : Auth::user()->fuga->hoge;

この三項演算子の:の右側に入った場合にAuth::user()で取り出される認証系のModelがグローバルスコープをかけていない方になっており、そのリレーションなので取り出されたfugaもhogeもかけていない方(=Policyがついていない)になっていた。

その場合に、Gate::deniesの第2引数に$hogeを渡すと、第1引数で指定したPolicy内で作成した関数にたどり着かずtrueで返ってきた。

Gateクラスの中身を覗いてみるとGate::deniesはGate::allowsの結果を反転させたものが返されていた。

Gate::allowsの中身は以下の通り

public function allows($ability, $arguments = [])
{
    return $this->check($ability, $arguments);
}

呼び出されているcheckの中身はこんな感じ

public function check($abilities, $arguments = [])
{
    return collect($abilities)->every(function ($ability) use ($arguments) {
        try {
            return (bool) $this->raw($ability, $arguments);
        } catch (AuthorizationException $e) {
            return false;
        }
    });
}

tryの中のrawはこんな感じ(コメントアウトは見やすさのために一旦除けました)

protected function raw($ability, $arguments = [])
{
    if (! $user = $this->resolveUser()) {
        return false;
    }

    $arguments = Arr::wrap($arguments);

    $result = $this->callBeforeCallbacks(
        $user, $ability, $arguments
    );

    if (is_null($result)) {
        $result = $this->callAuthCallback($user, $ability, $arguments);
    }

    $this->callAfterCallbacks(
        $user, $ability, $arguments, $result
    );

    return $result;
}

rawの中で呼ばれているresolveUser()を条件にしたif文の中のreturn false;が怪しいと踏んでresolveUser()を見に行く

こんな感じ

protected function resolveUser()
{
    return call_user_func($this->userResolver);
}

ここのcall_user_funcで追いかけられなくなったので終了。

以下推測

call_user_funcのphp公式ドキュメントを見に行ったが存在しない関数名を渡した場合はどうなるかはわからず。ただ一番ありそうな気がする部分なので、call_user_funcでfalseが返り、

if (! $user = $this->resolveUser()) {
        return false;
}

のif文の中に入ったことでfalseがallowsの結果として返りdeniesがtrueになったのかなと思います。

振り返り

結局のところ型の意識が抜けた状態でプログラムを書いていたのが根本の原因かなと思うのでこういう(何らかの原因で同じModelを二つ作らざるを得ない)場合には自分が今どっちのModelを扱っているのか気にしたり後からそれが明示的にわかるような書き方をする必要があるなと学びました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1