top_logo

Gem Punditの基本的な使い方まとめ

2022年 04月 06日

認可の仕組みを構築する Gem Pundit について公式ドキュメントや、自分が使用した経験をもとにまとめました。


参考: https://github.com/varvet/pundit

Pundit とは?

公式にもあるとおり、Ruby のオブジェクトパターンを活用して、シンプルかつ堅牢な認可システムを構築することができます。

導入

Gemfile に記述


gem 'pundit'

bundle install実行


bundle install

利用したいコントローラーの継承元で Pundit を include


app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  include Pundit::Authorization
end

generator 実行。これで、app/policies/配下に application_policy.rb というファイルが作成されます。


rails g pundit:install

基本的なルール


pundit ではユーザーの役割ごとに、尚且つ、オブジェクトの種類ごとに、コントローラーの一つ一つのアクションに対して権限を持っているかどうかを確認することができます。


application_policy.rbには以下のように記述されています。


モデルクラスごとにポリシーを作成して、ユーザーがそのアクションを行う権限を持っているかどうか判定します。


usercurrnet_userを参照しrecordは指定したモデルオブジェクトを参照します。


app/policies/application_policy.rb
class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end

  def index?
    false
  end
end

実際にPostモデルで使う方法を見ていきます。


コントローラー、ポリシーはそれぞれ以下のようになります。


app/policies/post_policy.rb
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
app/controllers/posts_controller.rb
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_useradminまたは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 です


app/policies/user/s3_presigned_urls_policy.rb
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?でメソッドを指定しています。


app/controllers/api/v1/user/s3_presigned_urls_controller.rb
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
authorize 漏れを検知

Pundit を使ってアプリケーションを開発していると、あるアクションを承認するのを忘れてしまいます。


Pundit では、各コントローラのアクションに手動で authorize追加することを推奨しているため、 どうしても見逃してしまいがちです。


pundit ではアクションに authorize を実行させていないときに例外を発生させるメソッドがあります。


以下のようにafter_action :verify_authorizedを追加することによって、authorizeを実行せずにアクションを実行すると例外を発生させることができます。

app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  include Pundit::Authorization
  after_action :verify_authorized
end

まとめは以上になります!


分かりにくい、または間違っているところあれば連絡いただければと思います。

連絡先

何か連絡をしたい際は以下 SNS、メールでお願いいたします。

Twitter:  https://twitter.com/naka_ryo_z

Github:  https://github.com/ryotaro-tenya0727

メール:   ryotaro123110@gmail.com

見出しへのリンク

© 2024, written by Nakayama

Powered by Gatsby