LoginSignup
3
1

More than 5 years have passed since last update.

有名なOSSのソースコードを読んで仕組みを理解する Pundit編

Posted at

rails向けの権限管理GemにPunditっていうものがあるんですが、
いい感じに権限周りの記述を1ファイルにまとめられて見通しの良いGemだなと感じたので、
ソースコードを読んでみました。

pundit (2.0.0)
https://github.com/varvet/pundit

簡単な説明

使い方の説明記事ではないのでざっと書くと、
各リソースに対してPolicyというテンプレートファイルを内で権限をメソッド単位で定義できるというもの。

たとえばHoge.rbの権限を設定したかったらApplicationControllerでPunditをincludeしたあとに、

class ApplicationController < ActionController::Base
  include Pundit
end

Policyファイルを作って

class HogePolicy
  def show?
    #ここにHogeリソースの閲覧権限を書く。
  end

  .
  .
  .
end

使うときは

class HogeController
  def show
    authorize @hoge
  end
end

みたいな感じに、よくある if current_user hoge のような条件分岐記述が分散せず1箇所にまとめられて見通しがよくなる。

Gemの中身の概要

中身は結構シンプルでコアは以下の2つのファイル
https://github.com/varvet/pundit/blob/master/lib/pundit.rb
https://github.com/varvet/pundit/blob/master/lib/pundit/policy_finder.rb

アプリケーションとGemのインターフェースは先述の通り authorize メソッドで、
これはPunditモジュールに定義されているのでアプリケーション側でincludeすると利用できるようになる。

リソースとポリシーの紐づけはauthorizeメソッドとPolicyFinderクラス内のfindメソッドで行っている。シンプルなアプリケーションであれば権限管理したいリソースのオブジェクトのみをauthorizeの引数として渡すことが多いと思うが、query つまり権限設定に利用するメソッドと policy_class 権限設定が記述されているクラスをカスタマイズできる。デフォルトでは query"#{action_name}?" であり、 policy"#{klass}#{SUFFIX}" となる。

def authorize(record, query = nil, policy_class: nil)
    query ||= "#{action_name}?"

    @_pundit_policy_authorized = true

    policy = policy_class ? policy_class.new(pundit_user, record) : policy(record)

    raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)

    record
  end

def find(subject)
    if subject.is_a?(Array)
      modules = subject.dup
      last = modules.pop
      context = modules.map { |x| find_class_name(x) }.join("::")
      [context, find(last)].join("::")
    elsif subject.respond_to?(:policy_class)
      subject.policy_class
    elsif subject.class.respond_to?(:policy_class)
      subject.class.policy_class
    else
      klass = find_class_name(subject)
      "#{klass}#{SUFFIX}"
    end
end

以上でリソースとポリシーの紐づけが済むので、

raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)

で、HogePolicyの該当アクションを実行することで権限がfalseであれば NotAuthorizedErrorを流せるというわけだ。

非常にシンプルである。

ちなみに

本題とは若干それるが、スコープごとに権限管理を行う対象(user)やポリシーファイル自体もカスタマイズできるようで policy, pundit_policy_scope, pundit_user のオーバーライドが可能なようだ。このあたりActiveSupport::Concern とかhelper_methodで動的に実現しているのもrubyらしいのかなと思いなかなか興味深かった(普段java/kotlinみたいな静的な言語を書くことが多いので尚更)

included do
  helper Helper if respond_to?(:helper)
  if respond_to?(:helper_method)
    helper_method :policy
    helper_method :pundit_policy_scope
    helper_method :pundit_user
  end
end
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1