我在简单的 cancancan 授权中遇到了意外的行为。
能力.rb
class Ability
include CanCan::Ability
def initialize(user)
# Define abilities for the passed in user here. For example:
#
user ||= User.new # guest user (not logged in)
if user.is_admin?
can :manage, :all
elsif user.is_standard?
can :manage, ServiceOrder, {user_id: user.id}
can :manage, ServiceOrderDetail, :service_order => { :user_id => user.id }
end
service_order.rb 控制器(部分显示)
class ServiceOrdersController < ApplicationController
authorize_resource
def show
@service_order = ServiceOrder.includes(:service_order_details).find(params[:id])
end
end
这不起作用,因为它让控制器显示任何 service_order 记录,而不仅仅是 current_user 拥有的记录。
唯一有效的方法是我手动授权控制器添加:
authorize! :show, @service_order
像这样:
def show
@service_order = ServiceOrder.includes(:service_order_details).find(params[:id])
authorize! :show, @service_order
end
这毫无意义,因为
authorize_resource
应该这样做。
发生的事情是
authorize_resource
在显示操作之前发生,并且由于 @service_order
尚未设置,因此它正在针对类进行检查,并且用户确实有权在约束下显示 ServiceOrder
。
添加authorize_resource将安装一个调用authorize!的before_action回调,并传递资源实例变量(如果存在)。如果未设置实例变量(例如在索引操作中),它将传入类名。例如,如果我们有一个 ProductsController,它将在每个操作之前执行此操作。
授权!(params[:action].to_sym,@product || 产品)
您需要做的是
load_and_authorize_resource
,按照widjajayd 的建议。或者(如果您不想使用 cancancan 默认加载操作)执行 before_filter
,在 authorize_resource
调用之前使用您的自定义方法手动加载资源。
我的建议:使用 load_and_authorize_resource 代替authorize_resource,下面是控制器的示例 只需确保您的strong_parameters声明:service_order_params
load_and_authorize_resource param_method: :service_order_params
当在控制器顶部使用
authorize_resource
时,它实际上设置了一个before_action,它将在控制器操作之前完成授权检查,因此必须在控制器操作之前设置实例变量(而不是在控制器操作内部) ),即您必须使用 before_action 来设置实例变量。
示例
class PostsController < ApplicationController
authorize_resource
# rest of controller
authorize_resource
创建的before_action将查找必须在before_action中创建的
@post
实例变量(或索引操作的@posts
)(而不是在控制器操作中,因为授权检查已经在控制器操作运行的时间)。资源
authorize_resource
方法的 cancancan 源代码/文档:
设置一个使用实例变量授权资源的 before 过滤器。例如,如果您有一个 ArticlesController,它将检查 @article 实例变量并确保用户可以对其执行当前操作。在幕后它正在做类似以下的事情:authorize!(params[:action].to_sym, @article || Article)