学習記録

アウトプット用に作りました

Punditを使って権限の管理をする

Punditとは

リソースに対して、どのユーザーであれば処理が許可されるのかを定義するgemです。

参考 : GitHub - varvet/pundit: Minimal authorization through OO design and pure Ruby classes

コントローラの各アクションで authorizeリソースオブジェクト を呼ぶと対象のリソースに対して権限があるかを確認してくれます。そのポリシーをapp/policiesにあるポリシーファイルで細かく定義することができます。


Punditの使用方法

まずGemfileにPunditのgemを追加してbundle installします。

gem "pundit"


継承元のコントローラ(application_controller.rb)にPunditをincludeします。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  include Pundit
end


generatorを使ってapplication_policy.rbファイルを作成します。

$ rails g pundit:install

app/policies/配下にポリシーファイルというものをオブジェクト別に作成していきます。


例題 : 記事(Article)アプリで管理者だけが記事の一覧画面、また記事の作成、削除、更新ができるように設定する。※ユーザーには管理者(admin)と一般(general)をenumで分けてある。


Policyファイルに認可を与えるユーザー(管理者)を設定していきます。

app/policies/article_policy.rb

class ArticlePolicy < ApplicationPolicy
  def index?
    user.admin?
  end

  def create?
    user.admin?
  end

  def update?
    user.admin?
  end

  def destroy?
    user.admin?
  end
end


articles_controller.rbの各アクションの実行前にauthorizeを実行して認可の許可を判断します。

class ArticlesController < ApplicationController
  def index
      authorize(Article)
  end

  def create
      authorize(Article)
  end

  def edit
      authorize(@article)
  end

  def update
      authorize(@article)
  end

  def destroy
      authorize(@article)
  end
end

authorize(@article)authorize(Article)は、特定のarticleがあるかないかで使い分けます。記事一覧と記事新規作成画面では特定のarticleはないのでauthorize(Article)を使っています。

view/articles/index.html.erbでこのようにビューファイルで権限によってリンクを表示するかしないか判定することができます。

<% if policy(Article).index? %>
  <%= link_to "記事一覧", articles_path %>
<% end %>

403 Forbiddenのエラー画面を表示

403 Forbiddenのステータスコードは、要求されたリソースはサーバー上にありますが、アクセス権やIPアドレス制限などの問題でアクセス拒否されたことを表します。
今回はgeneralでアクセスするとこの403のエラー画面が出力されるはずです。

まずpublicディレクトリ配下に403.htmlファイルを作成しておきます。

<html>
 <head>
   <title>権限がありません(403)</title>
   <meta name="viewport" content="width=device-width,initial-scale=1">
 </head>
 
 <body>
 <p>権限がありません。</p>
 </body>
 </html>


Pundit::NotAuthorizedError例外を403HTTPステータスにします。

config/application.rb

config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden


development環境でアプリの動作を確認するときは、config.consider_all_requests_localの値をtrueから一時的にfalseに変更します。

config/environments/development.rb

config.consider_all_requests_local = false

trueのままだと開発用のエラーページが表示されています。
falseに変えることで本番環境の画面を表示することができます。

参考 : カスタム例外を小さな exceptions_app でハンドリングする - Qiita

私は最初application_controller.rb内に以下のような権限のないユーザーがアクセスすると403エラーページが表示される実装をしていました。

class ApplicationController < ActionController::Base
  include Pundit
  
  rescue_from Pundit::NotAuthorizedError, with::user_not_authorized

  private

  def user_not_authorized
    flash[:alert] = '許可されていないユーザーです'
    redirect_to(request.refferrer || root_path)
  end
end

しかしこのように記載していると開発用のエラーページが表示されないのでこの記載は開発には向いていません。


RSpecステータスコードを確認する

ステータスコード確認のためにシステムスペック内にてdriven_by(:rack_test)を使用します。この記述がないと、Capybara::NotSupportedByDriverErrorが現れます。

before do
  driven_by(:rack_test)
end

以下はエラーページが出力されていることを確認するテストの書き方です。

expect(page).to have_http_status(403)