需求:简单,正交,用户可完全控制
我没有使用任何 Gem 实现用户权限控制功能。因为那些 Gem 我不能一下就理解,貌似也满足不了我的需求
说干就干,这里参考学习了 the_role 这个 gem. 也是基于角色 -> 资源/动作来抽象整个权限控制 先来看个场景,有 Articles 和 Comments 这两种 resource, 一个 Aricle 可以有多个 Comments, 我们需要 让 Aritcle 的创建者可以删除隶属于这个 Aritcle 的所有 Comments, 而其他人只能删除自己的 Aritile,
class CommentsController < ApplicationController
before_action :set_comment, :_checkOwnerIfNeed, only: [:show, :edit, :update, :destroy]
permissions :login, [:new, :create, :show, :index ]
permissions :owner, [:edit, :update]
permissions :belongs, [:destroy] ## belongs 说明destroy 操作不仅owner有权限执行, 这个comment所从属的Aritcle 的owner 也可以
## 注意这些设置只是默认值, 用户可以动态的为每个action重写定义权限
end
module ActionPermisson
##给action 设定默认权限
def permission(action, option = :root)
name = self.name.sub /Controller/, '' # MyBooksController -> my_books
Rails.application.defActionPermission(name.underscore, action.to_s, option.to_s)
logger.debug("defined action permisson: #{name}:#{action} #{option}")
end
# permissions :all, [:action1, :action2,...]
def permissions(option, actions)
actions.each { |a| permission(a, option) }
end
class DenyError < StandardError; end ## 权限不足异常
end
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :_permissionAllow? ## 用来检测权限
extend ActionPermisson
rescue_from ActionPermisson::DenyError do |e|
logger.debug 'permission deny'
render plain:'权限不足', status:403
end
private
def _permissionAllow?
if _checkPermisson
true
else
raise ActionPermisson::DenyError
end
end
def _checkPermisson
controller = request[:controller]
action = request[:action]
if !current_user
return Rails.application.default_permissions[controller].try(:[], action) == 'all'
end
if @current_user.id == 1 or @current_user.role.id == 1
return true; ## 系统管理员
end
option = @current_user.role_rules[controller].try(:[], action)
case option
when 'allow' then true
when 'owner', 'belongs'
puts 'permission is owner'
@checkOwner = option
when 'deny' then false
when nil ## 取默认值
case option1 = Rails.application.default_permissions[controller].try(:[], action)
when 'all', 'login'then true
when 'owner', 'belongs'
puts 'defalut permission is owner'
@checkOwner = option1
when 'root' then false
when nil then false
else
raise '不应该执行这里'
end
end
end
def _checkOwnerIfNeed
return unless @checkOwner
return if current_user.isOwner?(@_resource)
if @checkOwner == :owner
raise ActionPermisson::DenyError
end
## 向上查找
obj = @_resource.try(:belongs)
while (obj)
if current_user.isOwner?(obj)
return
end
obj = obj.try(:belongs)
end
raise ActionPermisson::DenyError
end
系统中有一个 role 表,用来存储不同角色的权限定义
每个用户有一个 role_id 来关联一个角色(同一时刻只能关联一个)
最后看一下 owner 权限的实现,owner 的意思是,只有所属人才能执行操作. 所以对 owner 的实现,并不能仅仅通过权限查表,必须要检测当前操作的资源, 也就是在查找资源后进行,通过设置
before_action :set_comment, :_checkOwnerIfNeed, only: [:show, :edit, :update, :destroy]
我们看到首先 set_comment 会查找当前使用的资源 @comment, 然后在_checkOwnerIfNeed 中,我们检测是否是 owner 权限,如果是我们才检测用户是否为 owner, 这里检测的函数,并不是侵入式的,所有我们必须对检测函数同一资源命名,这里我们把需要检测的资源同一命名为!@_resource 这样就可以分类权限检测罗辑和正常罗辑
最后是 belongs 权限,他的意思是当前资源的owner,和当前资源的从属对象的 owner, 以及从属对象的从属对象的 owner, 一直向上,都有权限进行操作. 我把这种关系叫做#从属链# 也就是当前资源的从属链上的任何一个 owner 都可以进行操作. 具体的实现在 _checkIfNeedOwner 中,请大家自行查看. 为了查找从属链,需要在有从属关系的 Modle 上定义 belongs 这个函数,返回当前资源的从属资源 通过用一天实现这个原型,我成功的达到了我的实现目标. 很简单也很清晰.