CanCanCan
Rails でいい感じにアクセス権を確認したり、作成・更新できるかを確認したり、そんなことができる gem です。
以下のような記事が参考になると思います。
この gem を導入すると、controller で権限認証が楽にできます。
authorize!
まず、本題のauthorize_resource
の前に、元となるauthorize!
を見てみます。
#books_controller.rb
def show
@book = Book.find(params[:id])
authorize! :read, @book
end
ability.rb でよしなに設定している場合に、これで「現在のユーザはこの @book の情報にアクセスできるか?」を判定できます。
しかし、アクションごとに authorize!
を書くのはダルいので普通は次に示すようなやり方でチェックすると思います。
authorize_resource
今回フォーカスする authorize_resource
です。
コントローラに
class UsersController < ApplicationController
authorize_resource
...
end
こんな感じで書いておくと、各アクションでauthorize!
をチェックしてくれます。
しかし、ちょっと勘違いしてました。
次のように ability.rb を設定しているときを考えます。
# ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :read, Book
can :manage, Book, user_id: user.id
...
end
end
Book の一覧は見れるけど、自分の所有物の Book しか編集できないよ、的な設定ですね。
このとき、
#books_controller.rb
def update
# ここで取得するのは current_user 以外のユーザが所有している @book とする
@book = Book.find(id)
@book.title = "foo"
@book.save
end
これを阻止してくれるかと思っていたのですが、そうではないようです。
authorize_resource
でやってくれるのはあくまでモデルに対する権限チェックなので
authorize! :update, Book
をやっているのと同じことになります。
ここで、
can :manage, Book, user_id: user.id
と設定しているので、Book
モデルに対しての:update
権限はアリと判定され、CanCan::AccessDenied
は発生しません。
当然ながら
authorize! :update, @book
は権限なしで例外になります。
※ここでは@book
は自分の所有物ではない前提としているので
これを自動で判定したければ次の load_and_authorize_resource
を使うのが良いです。
load_and_authorize_resource
今回触れてないですが、よしなにレコードを検索してインスタンス変数にセットしてくれる load_resource
というヤツもあります。
このload_and_authorize_resource
はそのload_resource
とauthorize_resource
の合せ技として使えるようです。
class BooksContoller < ApplicationController
load_and_authorize_resource
...
def update
# 暗黙にレコードを検索し、権限も判定してくれる
# @book = Book.find(params[:id])
# authorize! :update, @book
# これで @book が他のユーザの所有物であれば、例外が発生する
end
これでインスタンス変数に勝手に値をセットしてくれるし、その権限も見てくれるし、ということでかなり楽しながら安全も担保できます。
Rails のレールに沿った作り方をしていれば上手に使えると思います。
まとめ
-
authorize_resource
はモデルへの権限をチェックするものなので、レコードへの権限は見てくれない - レコードの権限まで見たいなら
load_and_authorize_resource
を使うと便利