1. 原理
Permission定义两个字段:
action
和resource
。分别与Rails的Controller和Model对应。Model关系定义
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
has_many :permissions, through: :roles
end
class Role < ActiveRecord::Base
has_many :permissions, dependent: :destroy
has_and_belongs_to_many :users
end
class Permission < ActiveRecord::Base
belongs_to :role
has_many :users, through: :role
end
定义权限
class ApplicationPolicy
class << self
def actions
@actions ||= []
end
def permit(action_or_actions)
acts = Array(action_or_actions).collect(&:to_s)
acts.each do |act|
define_method("#{act}?") {can? act}
end
actions.concat(acts)
end
end
private
def can?(action)
permission = {
action: action,
resource: record.is_a?(Class) ? record.name : record.class.name
}
user.permission.exists?(permission)
end
end
在ApplicationPolicy
里定义一个permit
方法(类方法)用来定义和保存权限点,can?
方法用来做权限检查。然后就可以像这样声明权限点:
class ResourcePolicy < ApplicationPolicy
permit [:read, :create, :update, :destroy]
end
这些Action就会被保存到ResourcePolicy.actions
里。
另外还需要两个方法policies
和resource
:
class ApplicationPolicy
class << self
def policies
@policies ||= Dir.chdir(Rails.root.join('app/policies')) do
Dir['**/*_policy.rb'].collect do |file|
file.chomp('.rb').camelize.constantize unless file == File.basename(__FILE__)
end.compact
end
end
def resource
name.chomp('Policy')
end
end
end
分别用来获取所有的 Policy 和 每个 Policy 对应的 resource (这两个方法是通过简单的命名规则实现的, 灵活性会差一点).
2. 使用pundit
class ApplicationController < ActionController::Base
include Pundit
end
添加验证
$ rails g pundit:install
生成默认的policy文件,路径为app/policies/application_policy.rb
将policies目录放到rails的自动加载路径中:config/application.rb
module BuildAnApiRailsDemo
class Application < Rails::Application
+ config.autoload_paths << Rails.root.join('app/policies')
end
end
$ rails g pundit:policy user
生成 app/policies/user_policy.rb
为User模型进行权限验证。
class UserPolicy < ApplicationPolicy
class Scope < Struct.new(:user, :scope)
def resolve
scope.all
end
end
end
如果users_controller有这么一段
def update
@user = Article.find(params[:id])
#这里验证current_user对这个@user是否有权限
@user.update_attributes(user_attributes)
end
我们给UserPolicy
中添加一个方法,来验证这个用户是否有这个权限。
class UserPolicy < ApplicationPolicy
+ def update?
+ return true if user.admin?
+ return true if record.id == user.id
+ end
+ def show?
+ return true
+ end
+ def create?
+ return true
+ end
+ def destroy?
+ return true if user.admin?
+ return true if record.id == user.id
+ end
end
其中user
和record
来自于ApplicationPolicy。
然后在UsersController
中添加验证
def update
@user = User.find(params[:id])
authorize @article, :update?
@user.update_attributes(user_attributes)
end
由于action_name和验证方法的名字相同,可以简写
def update
@user = User.find(params[:id])
authorize @article
@user.update_attributes(user_attributes)
end
这是,我们已经进行了权限验证,当用户不具备权限的时候回抛出错误,不能不处理,需要捕获错误进行处理。
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