【问题标题】:Variable roles with cancancan, how to handle the variable part?带有cancancan的可变角色,如何处理可变部分?
【发布时间】:2014-09-29 15:43:43
【问题描述】:

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

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

  • 跨公司。
  • 版主。
  • 管理员(与此问题需要的版主几乎相同)。

然后,就有了这个 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 控制器仅显示授权对象。

现在,让我们继续讨论 Question 对象(Question 属于事件,属于计划,属于会议,本身属于公司)。 基本上,任何用户都应该能够看到他提出的问题(Easy-peasy,question.user_id==user_id)。具有全局“主持人”标志的用户应该能够看到其他用户提出的任何问题。跨公司的版主应该能够看到任何问题。

但是,这种行为有一个例外。当用户(非管理员非 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,以便我还可以检查当前用户参与此特定事件的情况?

谢谢! 皮埃尔。

【问题讨论】:

    标签: ruby-on-rails cancan


    【解决方案1】:

    我可能已经为这个问题找到了一种解决方案,但我认为它不是非常理想的。 基本上,考虑到这个问题,我意识到在用户级别定义的角色适用于模型类,而在参与中定义的角色适用于模型的实例。 这意味着至少需要在运行时知道后者的实例 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 完成 Class 级别的授权以及 Instance 级别的授权,而无需每次需要进行检查时的大量数据库 IO?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-04
      • 2014-03-22
      • 2015-03-14
      • 1970-01-01
      相关资源
      最近更新 更多