cancancan的变量角色,如何处理变量部分?

时间:2014-08-06 15:54:40

标签: ruby-on-rails cancan

让我们说有这样的建模: 公司,有很多用户,有很多会议,通过会议有很多计划,通过计划有很多活动,通过活动有很多问题。

我们假设还有一个用户模型,其中一个角色字段混合了各种角色的二进制加法。 这些角色中很少有:

  • 跨公司。
  • 主持人。
  • 管理员(与此问题的主持人非常相同)。

然后,有这个Participation对象。参与属于用户和会议,并定义了一些角色,这些角色可以是跨公司的任何角色。基本上,它允许用户成为一组会议的主持人而不是全局会议。用户定义的任何角色都会取代参与角色。

现在,我正努力为这种模型找到正确的Cancancan能力定义。困难的部分似乎是考虑参与角色定义。

在能力声明开始时,我会分解权限以便于使用:

# Decompose rights.
cross         = user.is? :cross_company
admin         = user.is? :administrator
modo          = user.is? :moderator

只有用户角色被分解,参与角色还没有。

这是我已经开始工作的一个例子。

# Any user can read it's own company. Any cross-company user can read any company.
can     :read,                  Company, cross ? nil : { id: user.company_id }
# Only cross-company admins can create a new company.
can     :create,                Company if cross && admin
# Updating a company.
can     :update,                Company do |c|
  # User is admin and...
  admin &&
  (
    # User is cross or...
    cross ||
    # User belongs to this company.
    user.company_id==c.id
  )
end
cannot  :destroy,               Company

到目前为止,我能够做更复杂的场景。例如,事件对象的授权如下:

conditions                      = {}
# User can only see events he is participating into, unless he is admin.
conditions.deep_merge!          id: user.event_ids unless admin
# User can only see events related to his company, unless he is cross-company.
conditions.deep_merge!          planning: { conference: { company_id: user.company_id } } unless cross
can     :read,                  Event, conditions

非常好。列出事件对象的RESTful控制器仅显示授权对象。

现在,让我们继续问题对象(问题属于一个事件,属于一个计划,属于一个会议,本身属于一个公司)。 基本上,任何用户都应该能够看到他问的问题(Easy-peasy,question.user_id == user_id)。拥有全球"主持人的用户" flag应该能够看到其他用户提出的任何问题。跨公司主持人应该能够看到任何问题。

但是,这种行为有例外。当用户(非管理员非modo)有一个参与对象将他链接到一个事件时,参与者有一些重要的角色(例如,给定的用户可能是单个会议的主持人),他应该能够看到来自来自会议的事件中的任何用户,他被授权作为主持人。 我在这一点上陷入困​​境:

# Only questionable events can have questions.
conditions                      = { event: { questionable: true } }
# Administrator / Moderator can see all questions, others can't.
conditions.deep_merge!          user_id: user.id unless admin || modo
# The conference a question belongs to has to be owned by the same company as the user, unless he's cross company.
conditions.deep_merge!          event: { planning: { conference: { company_id: user.company_id } } }  unless cross
can     :read,                  Question,  conditions

我怎样才能覆盖读取行

conditions.deep_merge!          user_id: user.id unless admin || modo

因此,不仅全球管理员/版主可以做到这一点,还有参与中定义了单一角色的用户?目标当然是能够定义访问控制器时使用的SQL,但我似乎无法找到解决此问题的良好解决方案。

是否有人通过不同的方法解决了这种模式?

提示:问题控制器正在使用嵌套资源(/api/events/1/questions.json)。如何从ability.rb文件中获取正在使用的事件的ID,因此我还可以检查当前用户对此特定事件的参与情况?

谢谢! 皮尔。

1 个答案:

答案 0 :(得分:0)

我可能已经找到了解决这个问题的方法,但我认为它不是最优的。 基本上,在思考问题时,我意识到在用户级定义的角色适用于模型类,而参与中定义的角色适用于模型实例。 这意味着至少需要在运行时知道后者的实例ID才能生成SQL。这是我的解决方案。

在User模型上,定义一些帮助方法,允许快速检索由参与者授权的会议ID给定角色:

  def participations_as (*roles)
    @participations_as_cache ||= {}
    roles.flatten.map do |role|
      @participations_as_cache[role] ||= participations.select { |p| p.is?(role) ? p : false }
    end.flatten.uniq
  end
  def conference_ids_as (*roles)
    participations_as(roles).map{ |p| p.conference_id }
  end

这允许为用户获取任何给定角色的授权会议列表,如下所示:

user.conference_ids_as role1, role2, ..., roleN

现在,正如您所知,实际定义两次相同的能力OR'他们。让我们利用这个优势:

# This first block will build up authorization query hash regardless of per-instance roles defined via participations.
conditions                      = { event: { questionable: true } }
conditions.deep_merge!          event: { planning: { conference: { company_id: user.company_id } } }  unless cross
conditions.deep_merge!          user_id: user.id unless admin || modo
can     :read,                  Question,  conditions

# This second block will add an OR'ed condition to the first one, allowing to filter questions via conferences authorized by the participation roles.
conditions                      = { event: { questionable: true } }
conditions.deep_merge!          event: { planning: { conference_id: user.conference_ids_as(:moderator, :administrator) } }
can     :read,                  Question,  conditions

结果SQL似乎是正确的:

SELECT "questions"."id"            AS t0_r0, 
       "questions"."user_id"       AS t0_r1, 
       "questions"."event_id"      AS t0_r2, 
       "questions"."status"        AS t0_r3, 
       "questions"."anonymous"     AS t0_r4, 
       "questions"."contents"      AS t0_r5, 
       "questions"."created_at"    AS t0_r6, 
       "questions"."updated_at"    AS t0_r7, 
       "events"."id"               AS t1_r0, 
       "events"."planning_id"      AS t1_r1, 
       "events"."room_id"          AS t1_r2, 
       "events"."starts_at"        AS t1_r3, 
       "events"."ends_at"          AS t1_r4, 
       "events"."questionable"     AS t1_r5, 
       "events"."created_at"       AS t1_r6, 
       "events"."updated_at"       AS t1_r7, 
       "plannings"."id"            AS t2_r0, 
       "plannings"."conference_id" AS t2_r1, 
       "plannings"."created_at"    AS t2_r2, 
       "plannings"."updated_at"    AS t2_r3, 
       "conferences"."id"          AS t3_r0, 
       "conferences"."company_id"  AS t3_r1, 
       "conferences"."name"        AS t3_r2, 
       "conferences"."created_at"  AS t3_r3, 
       "conferences"."updated_at"  AS t3_r4 
FROM   "questions" 
       LEFT OUTER JOIN "events" 
                    ON "events"."id" = "questions"."event_id" 
       LEFT OUTER JOIN "plannings" 
                    ON "plannings"."id" = "events"."planning_id" 
       LEFT OUTER JOIN "conferences" 
                    ON "conferences"."id" = "plannings"."conference_id" 
WHERE  "questions"."event_id" = ? 
       AND ( ( "events"."questionable" = 't' 
               AND "plannings"."conference_id" IN ( 1 ) ) 
              OR ( "events"."questionable" = 't' 
                   AND "conferences"."company_id" = 1 
                   AND "questions"."user_id" = 1 ) ) 

因此,仅显示用户有权查看的问题,包括通过参与级角色授权的问题。

我仍在寻找更有效的解决方案,可能采用不同的方法。你知道任何? 基本上,并用换句话说:如何在每次需要检查时使用Cancancan GEM完成类级授权以及实例级授权,而不需要太多的数据库IO?