在Rails中对关系应用权限范围

时间:2015-08-04 01:48:09

标签: ruby-on-rails ruby permissions rolify

让我们说我正在使用rolify宝石。 rolify gem处理用户可以在资源实例或资源类上拥有的权限。这很简单。如果需要确定用户是否可以执行某项任务,则会产生类似于此的内容:

if user.has_role? :admin
  @article.update(article_params)
else
  fail # just as an example
end

但是,我希望能够在范围上应用这些权限,例如Article.all - 具体来说,我希望能够确定用户可以阅读哪些文章。假设只有角色为:admin的用户才能阅读文章,我将如何进行此操作?

编辑:我面临的问题是这个。在运行时,我不知道任何给定用户可以阅读哪些文章。该信息存储在数据库中。因此,使用像cancancan或pundit这样的解决方案不会 - 也不能 - 在这种情况下帮助(注意:我实际上使用的是权威)。基本上,我必须弄清楚如何实现Pundit的Scope类。

在我找到rolify宝石之前,我向朋友提出了这样的问题:

  

所以我为了解决它而做的是:每个用户都有一组组;权限可以在用户或组上定义(两者都有效)。权限影响一组通用记录(我只存储记录类型,没有ID),特定记录(记录类型和ID),或作为整体记录(记录类型,没有ID,带有全局标记) )。权限也可以被否定,有效地删除用户对主题的任何权限。所以我的数据库看起来像这样:

Permissions
   id: Integer
   actor_id: Integer
   actor_type: String
   subject_id: Integer
   subject_type: String
   action: String
   negated: Boolean
   global: Boolean
   precedence: Integer

Users
   id: Integer

Groups
   id: Integer

Articles
   id: Integer
     

我收录的文章有助于展示我尝试做的事情。因此,如果我应用这些权限:

Permission.create(actor: Group.find(1), subject: Article.find(1), action: "show")
Permission.create(actor: Group.find(1), subject_type: "Article", action: "show")
Permission.create(actor: User.find(1), subject: Article.find(1), action: "show", negated: true, precedence: 1)
     

当我去permission_scope(Article, User.find(1))时,我应该得到这个:

[#<Article id=2>, #<Article id=3>, ...]
     

问题是,我不知道permission_scope的内容是什么,所以我不知道如何制作它。

2 个答案:

答案 0 :(得分:1)

这些是适用于Rolify的Pundit自述文件的基本政策示例。

对于您只需要检查用户角色的简单情况:

class PostPolicy < ApplicationPolicy
  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user = user
      @scope = scope
    end

    def resolve
      if user.has_role?(:admin)
        scope.all
      else
        scope.where(:published => true)
      end
    end
  end

  def update?
    user.has_role?(:admin) or not post.published?
  end
end

如果您有资源范围的角色,则可以使用scopes provided by Rolify

class PostPolicy < ApplicationPolicy
  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user = user
      @scope = scope
    end

    def resolve
      Post.with_role(:author, user)
      # or
      Post.with_all_roles([:author, :editor], user)
    end
  end

  def update?
    user.has_role?(:author, post)
  end
end

为什么它很复杂:

要使用权限系统执行相同的范围设定,您需要以下内容:

user = User.joins(:permissions, groups: [:permissions]).find(1)

article_ids = Permission.where(
  actor_id: [user.id] + groups.ids,
  subject_type: 'Article',
  action: :show
).pluck(:subject_id)

Article.where(id: article_ids)

答案 1 :(得分:0)

根据您的更新,您肯定希望这个逻辑在一个单独的类中,并且由于感知复杂性,可能在不同的模块中。如果这是在任何开源的东西让我知道,因为看到它在使用将有助于更多。这不是一个容易的问题,但希望这会让你更接近。

module Scope
  class Permission < Struct.new(:relation, :entity, :action)
    def call
      relation.merge(::Query::Permission.new(:relation, :entity, :action)
    end
  end
end

module Query
  class Permission < Struct.new(:relation, :entity, :action)
    def call 
      #Here is where you implement the logic to find records related to a relation/entity/action
    end
  end
end

所以现在你有了一些需要处理范围的东西以及一些可以搜索你的权限的东西来找到正确的记录。