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
endgenerator 実行。これで、app/policies/配下に application_policy.rb というファイルが作成されます。
rails g pundit:installpundit ではユーザーの役割ごとに、尚且つ、オブジェクトの種類ごとに、コントローラーの一つ一つのアクションに対して権限を持っているかどうかを確認することができます。
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
enddef update
@post = Post.find(params[:id])
authorize @post
if @post.update(post_params)
redirect_to @post
else
render :edit
end
endauthorize @post は @post = Post.find(params[:id])に対してこのupdateを行うことができるかを判定するメソッドです。
authorizeはモデルクラス名から、使用するポリシーファイル名(post_policy.rb)を推測し、アクション名から使用するポリシーファイルのメソッド(update?)を推測しています。
今回であれば、currnet_userがadminまたはPostオブジェクトがpublishedではない時に編集することが可能ということになります。
def update?
user.admin? || !post.published?
endapp/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
endPundit を使ってアプリケーションを開発していると、あるアクションを承認するのを忘れてしまいます。
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
見出しへのリンク