动机
我尝试在其中一个控制器不与资源关联的应用程序中使用 CanCan - 非 RESTful 控制器。我希望根据其他资源和会话参数有条件地授权该控制器的操作。
背景
您不应该使用 load_and_authorize_resource 方法,因为那里 没有要加载的资源。相反,您可以致电授权!在每一个动作中 分别。
这不应该是一个问题,因为理论上我应该锁定它,并通过将
check_authorization
添加到我的 ApplicationController
来确保我的应用程序中的每个操作都发生授权。根据 CanCanCan 文档:
如果未在某个权限中执行授权,这将引发异常 行动。
问题
我遇到的问题是 check_authorization 似乎没有锁定非 RESTful 控制器的操作。未声明授权的行为!正在执行,不会引发任何 AccessDenied 异常。
下面是代表我正在做的事情的 MCVE。我是做错了什么还是发现了错误?执行此操作的“正确”方法是什么?
实施
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
check_authorization
def current_user
Object.new
end
protected
rescue_from CanCan::AccessDenied do |exception|
puts "CanCan::AccessDenied Exception thrown : message=#{exception.message}"
end
end
在我的控制器中,我尝试手动授权,但前提是满足某些条件:
class FooController < ApplicationController
def new
authorize! :foo, :bar, unless: true
puts 'FooController#new'
end
def index
authorize! :foo, :bar, if: true
puts 'FooController#index'
end
def show
puts 'FooController#show'
end
end
以及启用 :foo :bar 授权的能力类:
class Ability
include CanCan::Ability
def initialize(user)
puts 'CanCan::Ability#initialise'
can :foo, :bar
end
end
Rails 控制台中生成的输出:
2.3.0 :001 > app.get '/foo/new'
CanCan::Ability#initialise
FooController#new
2.3.0 :002 > app.get '/foo'
CanCan::Ability#initialise
FooController#index
2.3.0 :001 > app.get '/foo/1'
FooController#show
请注意,
show
操作甚至没有authorize!
声明,但仍然允许执行而不引发异常。
另外,我不明白的是,
Ability#initialize
根本没有为 show
动作调用。让我更加困惑的是 - Ability#initialize
和 new
都调用 index
方法 - 你会期望其中一个与 show
操作具有相同的行为,因为 new
操作应该'不要调用 authorize!
方法。
更顺畅的方法是在
check_authorization
中定义 ApplicationController
,如下所示:
def check_authorization
if cannot? params[:action].to_sym, params[:controller].to_sym
raise CanCan::AccessDenied.new('Some message', params[:action].to_sym, params[:controller].to_sym)
end
end
您的
rescue_from
块将完成剩下的工作。
在需要检查授权的控制器内,您实际上可能更喜欢执行以下操作,而不是在每个操作开始时调用
authorize!
:
before_action: check_authorization
在开始时,或者将
before_action
与 except
或 only
一起使用,如果您需要检查选择性操作的授权,例如:
before_action: check_authorization, except: [:create, :update]
或
before_action: check_authorization, only: [:show, :update_permissions]
我不知道它是否完全解决了您的问题,但它确实是一种更具可读性的方式,并且适用于 RESTful 和非 RESTful 控制器。尝试一下吧。
我找到了解决此问题的方法。
在这种情况下,由于条件语句的原因,您希望授权在某个操作中失败,您需要显式
authorize!
您知道不会定义的能力。例如;
condition ? (authorize! :foo, :bar) : (authorize! nil, nil)
当
AccessDenied
为 false 时,这将导致非 RESTful 控制器抛出 condition
异常,然后您可以像处理资源尚未授权的其他 RESTful 控制器一样处理该异常。
但是,这个解决方案并不理想 - 它感觉很老套,而且不像 CanCan 的做事方式。