Gem Punditの基本的な使い方まとめ
認可の仕組みを構築する Gem Pundit について公式ドキュメントや、自分が使用した経験をもとにまとめました。
参考: https://github.com/varvet/pundit
公式にもあるとおり、Ruby のオブジェクトパターンを活用して、シンプルかつ堅牢な認可システムを構築することができます。
Gemfile に記述
gem 'pundit'
bundle install
実行
bundle install
利用したいコントローラーの継承元で Pundit を include
class ApplicationController < ActionController::API
include Pundit::Authorization
end
generator 実行。これで、app/policies/
配下に application_policy.rb
というファイルが作成されます。
rails g pundit:install
pundit ではユーザーの役割ごとに、尚且つ、オブジェクトの種類ごとに、コントローラーの一つ一つのアクションに対して権限を持っているかどうかを確認することができます。
application_policy.rb
には以下のように記述されています。
モデルクラスごとにポリシーを作成して、ユーザーがそのアクションを行う権限を持っているかどうか判定します。
user
はcurrnet_user
を参照しrecord
は指定したモデルオブジェクトを参照します。
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
end
実際にPost
モデルで使う方法を見ていきます。
コントローラー、ポリシーはそれぞれ以下のようになります。
class PostPolicy < ApplicationPolicy
attr_reader :user, :post
def initialize(user, post)
raise Pundit::NotAuthorizedError unless user
super
end
def update?
user.admin? || !post.published?
end
end
def update
@post = Post.find(params[:id])
authorize @post
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
authorize @post
は @post = Post.find(params[:id])
に対してこのupdate
を行うことができるかを判定するメソッドです。
authorize
はモデルクラス名から、使用するポリシーファイル名(post_policy.rb
)を推測し、アクション名から使用するポリシーファイルのメソッド(update?
)を推測しています。
今回であれば、currnet_user
がadmin
またはPost
オブジェクトがpublished
ではない時に編集することが可能ということになります。
def update?
user.admin? || !post.published?
end
app/policies/admin
のような名前空間を使用したいときは以下のようなディレクトリ構成にします。
これは Rails のオートロードの仕組みと同じです!
app/policies/admin/post_policy.rb
ポリシーファイルにも名前空間
class Admin::PostPolicy < ApplicationPolicy
attr_reader :user, :post
def initialize(user, post)
raise Pundit::NotAuthorizedError unless user
super
end
def update?
user.admin? || !post.published?
end
end
コントローラーでは以下のようにして権限を確認
def update
@post = Post.find(params[:id])
authorize([:admin, @post])
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
次のような S3 の署名 URL を取得するようなポリシーを作るとします。
名前空間は user です
class User::S3PresignedUrlsPolicy < ApplicationPolicy
def initialize(user, record)
raise Pundit::NotAuthorizedError unless user
super
end
def diary_presigned_url?
check_current_user
end
end
この場合、名前空間と、ポリシー名、メソッド名を指定するため以下のように記述して権限を確認することができます。
%i[user s3_presigned_urls]
で名前空間とポリシーファイルを、:diary_presigned_url?
でメソッドを指定しています。
class Api::V1::User::S3PresignedUrlsController < SecuredController
def diary_presigned_url
authorize(%i[user s3_presigned_urls], :diary_presigned_url?)
presigned_url = Signer.presigned_url(:put_object,
bucket: ENV['S3_BUCKET'],
key: diary_s3_url.to_s)
render json: presigned_url: presigned_url
end
end
Pundit を使ってアプリケーションを開発していると、あるアクションを承認するのを忘れてしまいます。
Pundit では、各コントローラのアクションに手動で authorize
追加することを推奨しているため、 どうしても見逃してしまいがちです。
pundit ではアクションに authorize
を実行させていないときに例外を発生させるメソッドがあります。
以下のようにafter_action :verify_authorized
を追加することによって、authorize
を実行せずにアクションを実行すると例外を発生させることができます。
class ApplicationController < ActionController::API
include Pundit::Authorization
after_action :verify_authorized
end
まとめは以上になります!
分かりにくい、または間違っているところあれば連絡いただければと思います。
何か連絡をしたい際は以下 SNS、メールでお願いいたします。
Twitter: https://twitter.com/naka_ryo_z
見出しへのリンク