Rails权限验证工具Pundit
Rails authorization with Pundit
在人们开始使用Rails 4之后, cancan
的复杂以及兼容性修复不及时而遭人诟病,大家将目光投向了新的工具 Pundit。
Pundit
是一个纯ruby的gem,用于权限验证。
基本思路
Pundit针对当前模型对象,以及操作,会去相应的模型的policy中寻找操作验证的方法,继而实现验证。
也就是说,只要是任何一个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
假设在articlescontroller里有这么一段: ```ruby def update @article = Article.find(params[:id]) # 我们要在这里验证 currentuser对这个@atilce是否有权限 @article.updateatttributes(articlearrtibutes) 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
这其中 uesr
和 record
来自于ApplicationPolicy。
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
end
然后在ArticlesContoller里加入验证:
ruby
def update
@article = Article.find(params[:id])
authorize @article, :update?
@article.update_atttributes(article_arrtibutes)
end
由于actionname和验证方法的名字相同,所以可以简单写成: ```ruby def update @article = Article.find(params[:id]) authorize @article @article.updateatttributes(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
failuremessagefor_should do |policy| "#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}." end
failuremessageforshouldnot do |policy| "#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}." end end ```