Hegwin.Me

溯洄从之,道阻且长。溯游从之,宛在水中央。

Rails权限验证工具Pundit

Rails Authorization with Pundit

在人们开始使用Rails 4之后, cancan的复杂以及兼容性修复不及时而遭人诟病,大家将目光投向了新的工具 Pundit

Pundit是一个纯 Ruby的gem,用于权限验证。

基本思路

Pundit对于需要鉴权的对象,根据用户的操作,会去这个对象的对应policy中寻找和执行鉴权的方法,继而实现验证。

也就是说,对于任意的Ruby Class,需要验证用户是否有权限操作他的一个实例,只要将验证规则写在对应的policy中即可。

安装及初始化

# Gemfile
gem 'pundit'

$ bundle install

$ rails g pundit:install 生成默认的policy文件,路径为app/policies/application_policy.rb

在ApplicationController里添加:

class ApplicationController < ActionController::Base
  # ...
  include Pundit
  # ...
end

添加验证

如果要针对Article模型的实例,进行权限验证,则继续执行:

$ rails generate pundit:policy article => 生成 app/policies/article_policy.rb

# app/policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
  class Scope < Struct.new(:user, :scope)
    def resolve
      scope
    end
  end
end

假设在articles_controller里有这样一段代码:

def update
  @article = Article.find(params[:id])
  # 我们要在这里验证 current_user对这个@atilce是否有权限
  @article.update_atttributes(article_arrtibutes)
end

我们在ArticlePolicy添加一个方法,来验证用户是否有这个权限。方法的命名习惯于以问号结尾。

# app/policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
  class Scope < Struct.new(:user, :scope)
    def resolve
      scope
    end
  end

  def update?
     record.creator_id == user.id
  end
end

The uesr and record in this case inherit from ApplicationPolicy.

class ApplicationPolicy
  attr_reader :user, :record

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

然后在ArticlesContoller里加入验证:

def update
  @article = Article.find(params[:id])
  authorize @article, :update?
  @article.update_atttributes(article_arrtibutes)
end

由于action_name和验证方法的名字相同,所以可以简单写成: ruby def update @article = Article.find(params[:id]) authorize @article @article.update_atttributes(article_arrtibutes) end

这时,我们已经有了权限验证,当用户不具备权限的时候rails会抛出错误;不过我们要做的还没有结束,我们需要捕获这个错误并进行相关处理。

我们的application_controller应该变成这样:

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

  private

  def user_not_authorized
    redirect_to root_url, :alert => "You don't have permission to those resources."
  end

end

这样就可以在用户不具备权限时,跳转至root_url并给出相应提示了。

测试

参考这里: Testing Pundit Policies with RSpec

接下来我们对刚才添加的代码进行测试,以rspec为例:

首先,在spec_helper加入:

require 'pundit/rspec'

然后,生成测试文件,置于spec/policies/article_policy_spec.rb

require 'spec_helper'

describe ArticlePolicy do
  subject { ArticlePolicy.new(user, article) }

  let(:article) { create :article }

  context 'for a guest' do
    let(:user) { nil }

    it { should_not allow(:update) }
  end

  context 'for the write ' do
    let(:user) { article.creator }

    it { should     allow(:index) }
  end

  context 'for an other writer' do
    let(:user) { create :user }

    it { should_not allow(:update) }
  end
end

注意:这里的allow方法并非Pundit提供,而是我们自定义的matcher: ```ruby

spec/support/pundit_matcher.rb

RSpec::Matchers.define :allow do |action| match do |policy| policy.public_send("#{action}?") end

failure_message_for_should do |policy| "#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}." end

failure_message_for_should_not do |policy| "#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}." end end ```

< Back