Rails 又写了一个项目的权限系统,大家觉得怎么样?

xjz19901211 · 2013年12月18日 · 最后由 small_fish__ 回复于 2013年12月19日 · 3953 次阅读

没写成 Gem,放在项目里头,下面链接里说了这个系统的大概情况,大家觉得怎么样?

http://only-x.com/2013/12/16-rails-permission.html

说下好与不好的地方,或是新的想法,大家一起学习学习

赞一下重复造轮子的精神。

定义了三个 ability: own/read/write

只有这三个 ability,怎么控制更细精度的权限?Linux 系统权限还有 executeable 呢。

Cancan 灵活在可以根据 action 名来做验证。如果能把 Cancan 的权限配置持久化到数据库中,也不错。

@camel linux 也就 own / group + r w x 啊 我这里只搬了一点点,own + r w 没有 group 与 x 当然这里只做了对资源的控制,相对来说比 cancan 精细多了

比如,我文章里说的 cancan 要让用户可以修改自己的东西,必然要写一个类似 record.user_id == current_user.id 来判断用户是否可操作资源

而我写的方式是在创建时就让指定用户可以管理 record,方式不一样,当然优缺点也不一同

如 cancan 想把一个权限控制的代码保存的数据库是非常麻烦的,而且不能修改(相对于普通使用者),而我的方式是不关心记录是谁的,只关心用户对这个资源有没有权限操作,这个确可以随意定义,缺点也很明显,很多权限记录

这只是一时想到的东东,肯定还在很多问题的,不过正好我的项目中在使用,可以慢慢完善 ,这过程中我也同样会遇到很多坑的

@camel 好像答的有点问题,那个 ability 只是目前用到的,我觉得,如果以后有必要,可以扩展,定义做任意的字符

#3 楼 @xjz19901211 “定义” 需要一个类似"配置"的过程,Rails 鼓励"约定大于配置"。Cancan 直接用 action 名,并支持一些别名,如:read = [:index, :show]这样挺好。

我一同事一直纠结性能问题,不会每次请求都要带一次权限验证的 sql 查询吧。然后他一直抱怨 cancan 的,不知道楼主有例子分享吗?或者有打算做成 gem?很感兴趣。。

#5 楼 @small_fish__ 性能问题确实是大问题。cancan 每次 action 都会先做判断生成一张权限表然后对照这张表看某个 acion 是否被允许,会造成大量的无用 sql。

#6 楼 @liudangyi 对于基于 group+resorce 的权限,不知道有什么好的方法,主要是文档类的应用

#7 楼 @small_fish__ 权限本来就不是一件复杂的事情。如果想方便但不想用 cancan 可以去看看 pundit。自己写的话直接在 app_controller 里 before_filter 一下就可以了。

#6 楼 @liudangyi CanCan 性能是个头痛的问题,想办法做 cache 吧,把can?用 cache 包一层,通过判断 user updated_at 来判断是否过期,我还没尝试过。

@camel @small_fish__ 性能问题,我想把权限 cache 的内存中应该还好吧,原谅我一直没做过请求量灰常多的项目。。

这个 cache 可以把用户的权限 cache 一下,或是把所有权限数据也缓存起来。。

@liudangyi 权限是不复杂,不过涉及到各种业务需求时,还是很麻烦的

看下了 Pundit 感觉和 Cancan 差不多,都是制定一个规则,权限基于这个规则 我想试着做下以资源的权限为基础,在这上面随意定制规则

知识面比较窄,说的不对还请见谅

@small_fish__ 我 hack 了 cancan 的内部结构

class DelayBlock

  attr_accessor :block, :ability, :args

  def initialize(*args, &block)
    self.ability = args.shift
    self.args = args
    self.block = block
  end

  def run
    if @result
      @result
    else
      @result = self.ability.instance_exec(*args, &block)
    end
  end
end

module CanCan

  class Rule

    # replace delay hash to normal hash
    def transfer_delay_conditions!(ability)
      recurse_replace_delay_result(self.conditions, ability)
    end

    def recurse_replace_delay_result(hash, ability)
      hash.each do |k, v|
        if v.is_a?(DelayBlock)
          hash[k] = v.run
        elsif v.is_a?(Hash)
          recurse_replace_delay_result(v, ability)
        end
      end
    end

  end

  module Ability

    #override
    # used for single match
    def can?(action, subject, *extra_args)
      match = relevant_rules_for_match(action, subject).detect do |rule|
        # ********* add by ankun ***********
        rule.transfer_delay_conditions!(self)
        # ********* // add by ankun ***********
        rule.matches_conditions?(action, subject, extra_args)
      end
      match ? match.base_behavior : false
    end


    # override
    # accessible_by
    def model_adapter(model_class, action)
      adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
      # ********* add by ankun ***********
      rules = relevant_rules_for_query(action, model_class)
      rules.each do |rule|
        rule.transfer_delay_conditions! self
      end
      # ********* // add by ankun ***********
      adapter_class.new(model_class, rules)
    end

    def attributes_for(action, subject)
      attributes = {}
      relevant_rules(action, subject).map do |rule|
        # ********* add by ankun ***********
        rule.transfer_delay_conditions!(self)
        # ********* // add by ankun ***********

        attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
      end
      attributes
    end

  end
end

以后你就可以执行

db = DelayBlock.new do
  User.where(:active => true, :admin => false).pluck(:id)
end

can :close, User, :id => db

这样灵活和性能都有所体现,你只要在 DelayBlock 中返回数组就行了.

这样的好处是,只有要调用 can?(:close, @user) 或者 User.accessible_by(current_ability, :close) 的时候才会执行 DelayBlock 中的 sql 查询。希望对你有帮助

#13 楼 @yakjuly 谢谢分享,太感谢,一定认真分析下。。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册