让我们说有这样的建模: 公司,有很多用户,有很多会议,通过会议有很多计划,通过计划有很多活动,通过活动有很多问题。
我们假设还有一个用户模型,其中一个角色字段混合了各种角色的二进制加法。 这些角色中很少有:
然后,有这个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,因此我还可以检查当前用户对此特定事件的参与情况?
谢谢! 皮尔。
答案 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?