我注意到用户可以访问他们不应该访问的操作。
我在rails控制台上用之类的东西调试
user = User.first
physician = Physician.first
ability = Ability.new(user)
ability.can?(:send_message, physician)
# => false
上面说,用户不能访问该医生的send_message操作,这是所需的行为,但我知道他们可以在应用程序中访问!
我认为这将原因缩小到由于某种原因无法加载错误的模型实例。这在cancan文档中也有暗示:
注意:这假设模型实例加载正确。
但问题是我不确定如何从这里诊断问题,因为控制台说它应该可以工作。我不知道如何查看cancancan设置的模型实例,也不知道还能尝试什么。
有什么想法吗?
更新
我确实通过在控制器中使用authorize! :send_message, physician
解决了这个问题,但由于我只是偶然发现了这种行为,我认为更重要的是弄清楚为什么加载了错误的模型实例(尤其是这样我就可以看到其他地方是否也发生了这种情况)。
我弄清楚了为什么会发生这种事(但我仍然不知道如何否认)
我认为发生这种情况是因为我有很多自定义操作,有些操作有@physician = Physician.find(current_user.physician.id)
(即他们是当前用户),而另一些操作更像@physician = Physician.find_by_id(physician_params[:id])
。我不确定cancan是如何设置实例模型的,但我知道它不是通灵的,所以它不知道是将其设置为当前用户的医生实例,还是传入的医生id的医生实例。
剩下什么
cancancan如何为自定义方法设置模型实例(我认为它尝试了一些东西,如果不起作用,就尝试其他东西,等等)?
有帮助的小笔记:
load_and_authorize_resource
尝试加载非RESTful操作的模型实例- 文档中的一些有用信息
- 这可能与我的经历有关:
当我返回slug时,它打破了这种行为,我可以编辑所有pokemon。
把我的笔记留在这里,以防它们对其他人有帮助。
TL;DR,cancancan有很多细微的假设,你从一开始就不知道。我通过彻底阅读cancancan自述文件、代码和定义能力文档中的注释发现了其中的许多
好了。。
cancancan的工作原理
- 如果在控制器操作本身中调用
authorize!
,cancancan将在每个控制器操作中查找一个实例变量 - 如果您只是在控制器的开头添加
load_and_authorize_resource
,则会做两件事:- 加载cancancan认为应该加载的实例变量,并且
- 检查该模型实例的授权
- 请注意,对于自定义操作,
load_and_authorize_resource
仍然会尝试加载模型实例,但它如何知道要加载什么?它猜测不会,对我来说,我不喜欢,所以要注意这一点。- 对我来说,我更喜欢自己分两个步骤来完成
load_and_authorize_resource
的工作,所以我确切地知道发生了什么。- 确保@article是通过每个控制器操作的before操作生成的(或通过@articles生成索引操作)
- 只需在控制器顶部写一行,在设置模型实例的before操作之后写
load_and_authorize_resource
- 请注意,现在唯一的区别是开发人员负责加载正确的模型实例,而cancancan并没有试图猜测。我更喜欢这种方法,因为它只需要一个错误就可以意外地允许访问不应该授予的地方
- 对我来说,我更喜欢自己分两个步骤来完成
- 还要记住,
load_and_authorize_resource
应该始终在设置模型实例变量的任何操作之前
可能也有帮助的随机笔记
-
实例变量的名称取决于操作。如果我们有一个物品控制器,那么:
- 对于索引操作,authorize查找@articles
- 对于所有其他操作,授权查找@article
- 然后它检查用户是否被允许访问该资源
-
load_and_authorize_resource
检查模型实例是否已设置,如果未设置,则设置一个。因此,如果您有一个创建@article/@articles的before操作,那么load_and_authorize_resource
不会为您执行该操作(即不会覆盖它),但如果您没有设置,cancan将尝试设置一个。请参阅源代码的注释:
如果实例变量已设置,则不会加载资源。这使得通过对某些操作执行before_action来覆盖行为变得容易。
- 能力规则将覆盖以前的规则。(示例请参见此处)
- 最后一件事,永远不要在ability.rb中使用
current_user
,它会无声地出错(!!),所以一定要使用user
:)
以下是发生的情况:https://github.com/CanCanCommunity/cancancan/blob/585e5ea54c900c6afd536f143cde962ccdf68607/lib/cancan/controller_additions.rb#L342-L355
# Creates and returns the current user's ability and caches it. If you
# want to override how the Ability is defined then this is the place.
# Just define the method in the controller to change behavior.
#
# def current_ability
# # instead of Ability.new(current_user)
# @current_ability ||= UserAbility.new(current_account)
# end
#
# Notice it is important to cache the ability object so it is not
# recreated every time.
def current_ability
@current_ability ||= ::Ability.new(current_user)
end