Punditを使用した認可機能の作成方法
前提
scaffoldで作成したようなスタンダードなRailsアプリを想定しており、UserとPostモデルがあり1対多の関係にあるものを想定しています。
class User < ApplicationRecord has_many :posts enum state: { writer: 0, editor: 1, admin: 2 } end class Post < ApplicationRecord belongs_to :user end
基本的な使い方
1. punditをインストールした後、application_controller.rbにPunditを適用させる
# Gemfile gem 'pundit' => bundle installコマンドを実行
application_controller.rbにPunditを適用させる
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base include Pundit end
2. 以下のコマンドを実行後、生成されたファイルに設定を記述する.
以下のコマンドを実行することで、app/policies/
配下にapplication_policy.rbというファイルが生成される。
% bin/rails g pundit:install
application_policy.rbに以下の設定を記述する
# app/policies/application_policy.rb class ApplicationPolicy attr_reader :user, :record def initialize(user, record) @user = user @record = record end end
initializeメソッドでインスタンス変数を定義している。
第一引数はuserを渡してある。Punditはデフォルトでコントローラーでcurrent_userメソッドを呼んで、userとして自動的に第一引数のuserに渡してある。
2番目の引数は、認証をチェックしたい何らかのモデルオブジェクトです。
3. コントローラーの各アクションの実行前にauthorizeを実行する.
# # app/controllers/articles_controller.rb class PostsController < ApplicationController before_action :set_post, only: [:show, :edit, :update, :destroy] before_action :authorize_post, only: [:index, :new, :create] def index; end def show; end def new; end def create; end def edit; end def update; end def destroy; end private def set_post @post = Post.find(params[:id]) authorize @post end def authorize_post authorize Post end end
4. 個別のPolicyファイルを作成し、設定を記述する.
Punditは、リソースに対して、どのユーザーであれば処理が許可されるのかを定義するものです。
「コントローラのアクション名 + ?」のメソッドを定義し、返り値が true なら許可、 false なら拒否になる。例えばPostモデルのオブジェクトに対して以下の設定を行う場合、
# app/policies/post_policy.rb class PostPolicy < ApplicationPolicy def index? true end def show? true end def new? true end def create? user.admin? || user.editor? end def edit? true end def update? user.admin? || user.editor? end def destroy? user.admin? || user.editor? end end
上記のようにポリシーファイルを設定すると、Postモデルのオブジェクトに対して、create、update、destoryメソッドは管理者、編集者のみが実行可能、他のアクションは全てのユーザーが実行可能、といった定義を行うことが出来る。
Scope
policy_scope
メソッドを呼ぶと、対象ポリシーファイル内で定義されたスコープを実行してくれる。
例えば、管理者はすべてのリソースにアクセスできるが、一般ユーザは自身が保持しているリソースのみといった認可を設定したい場合、以下のように実装すれば良い。
class PostPolicy < ApplicationPolicy class Scope < Scope def resolve if user.admin? scope.all else scope.where(user_id: user.id) end end end end
class PostsController < ApplicationController before_action :authorize_post, only: [:index] def index @posts = policy_scope(Post) end private def authorize_post authorize policy_scope(Post) end end
例外を403ステータスにするする方法
# config/application.rb module Post config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden end
補足:Punditが用意してあるPolicySpecでテストコードを書く
spec_helper.rbに以下のコードを記述
# spec/spec_helper.rb require "pundit/rspec"
上記の記述によりpermit
というヘルパーが使用できるようになるので、これにユーザとリソースオブジェクトを渡すだけで書ける。
require 'rails_helper' RSpec.describe TaxonomyPolicy do subject { described_class } permissions :index?, :create?, :update?, :destroy? do let(:admin) { create(:user, :admin) } let(:editor) { create(:user, :editor) } let(:writer) { create(:user, :writer) } let(:tag) { create(:tag) } let(:author) { create(:author) } let(:category) { create(:category) } it "ライターはタグ・著者・カテゴリーの一覧表示・編集・削除機能にアクセスできない" do expect(subject).not_to permit(writer, tag, author, category) end it "管理者はタグ・著者・カテゴリーの一覧表示・編集・削除機能にアクセスできない" do expect(subject).to permit(admin, tag, author, category) end it "編集者はタグ・著者・カテゴリーの一覧表示・編集・削除機能にアクセスできない" do expect(subject).to permit(editor, tag, author, category) end end end