Laravel (環境によっては)認可系などの比較にご注意を
はじめに
よく、自分の所有でないブログを編集しようとしたら、エラーにする、というような処理を入れたりします。いわゆる認可ですね。
そして、以下のようなコードなどを書いたりします。
if ($post->user_id !== $request->user()->id) {
// 本人でないのでエラー
}
通常というか、多くの場合は問題は無いのですが、一部サーバ環境ではこれだとまずかったりします。
私の知る限りでは、Xserverで、PHP7.4以外の時は、まずいです。
実際、どれだけのサーバでこういう状況になるかは分かりませんが、一部環境ではあり得る、となります。
何が問題?
上記では、型もチェックする比較(!==)を使っていますが、環境によっては、ここで問題が発生します。問題が発生する際は $post->user_id が string 型で、$request->user()->id は、integer 型となります。上記の場合は、そもそも型が異なるので、本人でさえも、本人でないと判定されます。
その逆(本人以外が本人と判定される)でないだけいいですが、場合によっては、そういうマズイ判定になるケースも最後に見てみます。
通常(多くの場合)、integer型で定義していれば、$post->user_id も integer 型で取得できます。この挙動の違いは、PDOにあるようで、mysqlndが入っていない場合、又は入っていても PDO::ATTR_EMULATE_PREPARES が true の時は、string 型となってしまうようです。
試しに、config/database.php で以下のようにやって自分のサーバで試してみたら、確かに string 型で取得されました。
'mysql' => [
//
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
PDO::ATTR_EMULATE_PREPARES => true, // 追加
]) : [],
],
原因についてはさておき、Laravel的にどう対応すれば良いか、以下対処法です。
対処法
(1) 型をチェックしないで比較
!== ではなく、!= にしてしまう方法です。一番手っ取り早いかも知れませんが…。(又は明示的に型変換して比較するとか)
if ($post->user_id != $request->user()->id) {
// 本人でないのでエラー
}
(2) 比較の対象を同じにする
以下のように同じデータ(User側のID)同士を比較してやれば !== で問題ありません。
if ($post->user->id !== $request->user()->id) {
// 本人でないのでエラー
}
(3) 上記(2)をLaravelユーザーっぽくやる
if ($request->user()->isNot($post->user)) {
// 本人でないのでエラー
}
これが一番お勧めですね。しかもこちらの場合、(2)の場合と異なり、仮に $post->user が null でも「Attempt to read property "id" on null」のようなエラーは出ません。
isNot() の反対の is() もあります。
(4) cast の設定をする
以下のように user_id をキャストする設定をするという手もあります。
class Post extends Model
{
protected $casts = [
'user_id' => 'integer',
];
}
ちなみに、何故 user_id は、string 型で取得されるのに、id の方は integer で取得できるのだという話もありますが、Laravelではデフォルトで、primaryKeyについては、integer 型にキャストしているから、となります。
参考:GitHub [5.2] Cast incrementing IDs to Integer by default
場合によっては、まずい問題になる事も
if ($user->type === 1) {
// 一般の方は閲覧できないのでエラー
}
type
1:一般ユーザー
2:管理者等
として、typeでinteger型を期待しているのに、環境によってstringだった場合、まずいですね。
雑感
現状、どれ位こういうサーバがあるのでしょうか。
今回は認可に特化して話を書きましたが、認可だけでの問題ではないと思います。
間違いやより良い書き方などありましたらコメント下さい。
Discussion