Rails 使用 Pundit 權限管理

使用 pundit gem 當作權限管理的物件,透過一些簡單範例來理解使用方法。

Rails 使用 Pundit 權限管理
Photo by Tobias Tullius / Unsplash

在開發 Rails 專案,我們時常會碰到需要權限控管的時候,如果在 controller 裡面寫一堆 before_action 決定是否權限通過,反而會讓檔案變得肥大,這時候如果用另一個 class 來專門處理權限邏輯的話,那該有多好。pundit 這個 gem 就是專門來處理這件事情的。

範例

當安裝完之後我們會統一將檔案放在 app/policies/ 之下,安裝完之後會有兩件事情需要注意:

  • pundit 會主動去找 controller 對應的 policy
PostsController -> PostPolicy
  • pundit 會要求使用 policy_scope 對撈取資料進行控管,否則會噴錯誤
Pundit::PolicyScopingNotPerformedError

除非使用 skip_after_action

skip_after_action :verify_authorized
skip_after_action :verify_policy_scoped

剛剛提到了會主動去找對應的 policy 檔案,如果想要手動指定的話,也是可以的。
直接在 controller 內呼叫

class FooController
  def show
    authorize Post # 他會去找 PostPolicy#show
    
    # 帶參數的方式
    @post = Post.find(params[:id])
    authorize @post, :update?
    
    # 手動指定 policy_class
    # 不然他會優先去找參數的 class
    # https://github.com/varvet/pundit/blob/main/lib/pundit/policy_finder.rb
    authorize @post, policy_class: PublicationPolicy
  end
end

以上是關於 controller policy 的部分。接著我們可以來看看針對資料控管範例。

通常會應用在 不同的角色可以看到不同的資料欄位,或是不同撈取資料方式判斷使用者能否通過權限控管。使用方法於原本的 policy class 內新增 Scope class

class FooPolicy
  class Scope
    attr_reader :user, :scope

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

    def resolve
      if user.admin?
        scope.where(receipt_active: true)
      else
        organization_ids = user.organization_members.admin.pluck(:organization_id)
        scope.where(receipt_active: true).where(id: organization_ids)
      end
    end
  end
end

user 參數不代入的話會先去找 current_user 所以在寫法上我們可以忽略這個參數,這個範例就是使用者不同 role 時資料撈法的範例。接著我們只要在 controller 呼叫即可。

def index
    @organization_list = policy_scope(Organization, policy_scope_class: ReceiptSystemPolicy::Scope).pluck(:title, :id)
end

以上為平常使用 pundit 當權限控管的範例。

參考資料

https://github.com/varvet/pundit