来自具有复杂条件的数据库的动态康康舞

dynamic cancan from database with complex conditions

我正在尝试从权限列表中定义具有 select 权限的用户创建角色。 我想要这样的权限:

def initialize(user)
  user.projects_users.each do |project_user|
    project_user.role.privileges do |privilege|
      can :create, ProjectsUser, :project_id => project_user.project_id
    end
  end
end

但我正在尝试将权限保存在数据库中,这样我就可以这样做以获得上述结果

def initialize(user)
  user.projects_users.each do |project_user|
    project_user.role.privileges do |privilege|
      can privilege.action.to_sym, privilege.subject_class.constantize, privilege.conditions
    end
  end
end

问题出在'privilege.conditions'部分。我无法将必须只执行的条件存储在 Ability.rb 文件中。如果我尝试存储:

{ :project_id => project_user.project_id }

它会说没有名为'project_user'的变量。我可以将它保存为字符串并在我的能力文件中执行 eval(privilege.condition),但是我只需要对值执行此操作。我试过这样的事情:

def initialize(user)
  user.projects_users.each do |project_user|
    project_user.role.privileges do |privilege|
      can privilege.action.to_sym, privilege.subject_class.constantize, privilege.conditions.each do |subject, id|
        subject => eval(id)
      end
    end
  end
end

我得到的错误是 'syntax error, unexpected =>, expecting keyword_end' 'subject =>' 部分。

不知道具体怎么做...

我正在使用这行命令对其进行测试:

@user_id = 4
@role = Role.create(name: "Tester", project_id: 4)
@priv = Privilege.create(:action => :create, :subject_class => 'ProjectsUser', :conditions => { :project_id => 'project_user.project_id' })
@role.privileges << @priv
@project_user = ProjectsUser.create(:user_id => @user_id, :role_id => @role.id, :project_id => @role.project_id)
@a = Ability.new(User.find(@user_id))
@a.can?(:create, ProjectsUser.new(:user_id => @user_id + 1, :role_id => @role.id, :project_id => @role.project_id))

有什么建议吗?

我肯定会说你的做法有些倒退。由于测试套件中到处都需要大量设置,而且过于复杂,因此从数据库记录动态创建一堆身份验证规则将非常难以测试。

而是使用块来声明复杂的关系和角色来存储权限。

使用 awesome Rolify gem 和作用域为资源的角色的示例:

can :moderate, Forum do |forum|
  user.has_role?(:moderator, forum)
end

您还可以使用角色为群组分配权限:

if user.has_role? :admin
  can :crud, User
  # ...
end 

我会说,如果您真的需要一个复杂的完全动态的解决方案,管理员可以从 Web 界面创建授权规则、角色定义等,那么您肯定已经超越了 CanCan。但你真的需要它吗?

好的,所以我找到了一个非常简单的解决方法。本质上,条件上的 do 块没有被正确评估。这是工作代码:

user.projects_users.each do |project_user|
  project_user.role.privileges.each do |privilege|
    can privilege.action.to_sym, privilege.subject_class.constantize, Hash[privilege.conditions.map {|subject, condition| [subject, eval(condition)] }]
  end
end

注意 Hash[privilege.conditions.map {|subject, condition| [subject, eval(condition)] }]

这样做是将一个符号作为条件中的键,例如 :subject_id 并将其映射到评估的条件,该条件评估为特定的 id。

在我的模型中我有

class Privilege < ActiveRecord::Base
    has_and_belongs_to_many :roles

  serialize :conditions, Hash
end

示例模型是:

Privilege.create(
  :action => :create, 
  :subject_class => 'ProjectsUser', 
  :conditions => { :project_id => 'project_user.project_id' }
)

更新

此方法仅适用于一层深度的条件。像这样的条件是行不通的。你会得到一个:TypeError: no implicit conversion of Hash into String

:conditions => { 
  :project => { 
    :location_id => 'project_user.project.location_id'
  }
}

这不是最佳解决方案,但可以解决此问题

:conditions => { 
  :project => "{ 
    :location_id => eval(\"project_user.project.location_id'\")
  }"
}