目的
PunditはRailsにおいて認可システムに必要なヘルパーなどを提供するgemです。
punditよりシンプルな認可機能アプリを作成します
Qiita:Pundit + Railsで認可の仕組みをシンプルに作るより引用
Punditというgemを使ってRailsに認可の仕組みを作ってみます。
認可というとcancancanが有名です。
cancancanはユーザに対して、どんなアクションが許可するかを定義するのに対して、
Punditではリソースに対して誰が許可されるのかを定義します。反対からの目線ですね。
cancancanがコントローラ寄りならば、Punditはモデル寄りの責務です。また、
cancancanがDSLなのに対し、PunditはピュアRubyな書き方になっています。
学習アプリに実装する機能
管理ユーザーのみユーザー詳細(user_show)閲覧可能。
管理ユーザー以外の場合はエラーページを表示(403)。
執筆時の対象Version
Rails:5.2.6
devise:4.8
pundit:2.1.1
実装方法
セットアップ:devise導入
# 新規アプリを作成
rails new pundit_sample2021
cd pudit_sample2021
# Gemfileに追加する
+ gem 'devise'
# gemをインストール
bundle install
# deviseをセットアップ
rails g devise:install
# deviseでUserモデルをセットアップ
rails g devise user
# frozen_string_literal: true
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
---省略---
+ # roleカラムを追記
+ t.integer :role, default: 0
t.timestamps null: false
end
---省略---
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
+ # roleを設定
+ enum role: { general: 0, admin: 1 }
end
+ # ユーザーデータを作成
+ # 管理者ユーザー
+ User.create!(
+ email: "admin@admin.com",
+ password: "password",
+ password_confirmation: "password",
+ role: 1
+ )
+ # 一般ユーザー
+ User.create!(
+ email: "general@general.com",
+ password: "password",
+ password_confirmation: "password",
+ role: 0
+ )
# DB作成
rails db:create
# マイグレーションを実行
rails db:migrate
セットアップ:User_view・controllerを設定
# devise導入時にはindexとshowが設定されていないため、作成する。
rails generate controller Users index show
class UsersController < ApplicationController
def index
+ # Userの一覧を表示
+ @users = User.all
end
def show
+ # 個別のUserの情報を表示
+ @user = User.find(params[:id])
end
end
+ <h1>All users</h1>
+ <% if current_user.present? %>
+ <p>現在ログインしているユーザー:<%= current_user.email %></p>
+ <%= link_to 'Sign_out', destroy_user_session_path, method: :delete %>
+ <% else %>
+ <%= link_to 'Sign_in', new_user_session_path %>
+ <% end %>
+ <ul class="users">
+ <% @users.each do |user| %>
+ <li>
+ <%= link_to user.email, user %>
+ </li>
+ <% end %>
+ </ul>
+ <h1>My page</h1>
+ <p><%= @user.email %></p>
+ <h1>My Role</h1>
+ <p><%= @user.role %></p>
+ <%= link_to 'Back', users_path %>
Rails.application.routes.draw do
devise_for :users
+ root 'users#index'
+ resources :users, :only => [:index, :show]
end
Punditの導入
+ gem 'pundit'
# gemをインストール
bundle install
class ApplicationController < ActionController::Base
+ # Punditを適用するcontrollerの継承元でincludeする。
+ include Pundit
end
# app/policies/配下にapplication_policy.rbというファイルが作成されます。
rails g pundit:install
# frozen_string_literal: true
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
scope.all
end
private
attr_reader :user, :scope
end
end
PunditをUserに設定
# userpolicy.rbを下記の内容にて新規に作成
# 基本的に全ての機能を許可。
# ただしユーザー詳細は管理ユーザーのみ許可
class UserPolicy < ApplicationPolicy
def index?
true
end
def show?
# ユーザー詳細は管理ユーザーのみ許可
user.admin?
end
def create?
true
end
def new?
create?
end
def update?
true
end
def edit?
update?
end
def destroy?
true
end
end
class UsersController < ApplicationController
def index
@users = User.all
end
def show
@user = User.find(params[:id])
+ # punditにてauthorizeメソッドにリソースオブジェクトを渡して認可状況を確認。
+ authorize @user
end
end
# サーバー起動して、管理ユーザー(admin@admin.com)がuser_showを閲覧可能であることを確認
# 一般ユーザー(general@general.com)ではエラーになることを確認
rails s
403を設定
# 下記をfalseへ変更
# Show full error reports.
+ config.consider_all_requests_local = false
- config.consider_all_requests_local = true
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>権限がありません(401)</title>
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ </head>
+ <body>
+ <p>権限がありません。</p>
+ </body>
+ </html>
class ApplicationController < ActionController::Base
include Pundit
+ rescue_from Pundit::NotAuthorizedError, with: :render_403
+ def render_403
+ render file: Rails.root.join('public/403.html'), status: :forbidden, layout: false, content_type: 'text/html'
+ end
end
# 一般ユーザー(general@general.com)では403ページへ遷移することを確認
rails s
参考サイト・資料
Github:Pundit_公式ドキュメント
Qiita:Pundit + Railsで認可の仕組みをシンプルに作る
Qiita:Punditをなるべくやさしく解説する
Qiita:Railsの認可Gem「Pundit」で、漏れのない認可設定を上手に作るTips
1434-193:Rails configのconsider_all_requests_localとは
Qiita:Railsで404エラーメッセージを出すために