Rails 我的用户权限控制系统实现方式

lilijreey · October 27, 2016 · Last by herrylauu replied at January 22, 2024 · 2986 hits

需求:简单,正交,用户可完全控制

我没有使用任何 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 这个函数,返回当前资源的从属资源 通过用一天实现这个原型,我成功的达到了我的实现目标. 很简单也很清晰.

As the game continues to gain popularity, it has created a community of enthusiasts who find joy in the camaraderie, creativity, and pure fun that A Small World Cup brings to their tabletops.

You need to Sign in before reply, if you don't have an account, please Sign up first.