我刚刚用 devise/cancan/rolify 的 rails 作曲家创建了一个 rails 应用程序,我正在查看它生成的一些代码,我不确定@user
来自这个片段:
controllers/users_controller.rb
class UsersController < ApplicationController
def index
authorize! :index, @user, :message => 'Not authorized as an administrator.'
@users = User.all
end
end
/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :authenticate_user!
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_path, :alert => exception.message
end
end
/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
end
end
@user
变量在哪里设置?我本来希望在那里看到current_user
。
编辑
我觉得我一定没有清楚地解释自己,所以让我们试试这个。
1)这行得通
def index
authorize! :index, @user, :message => 'Not authorized as an administrator.'
@users = User.all
end
2)这有效
def index
authorize! :index, current_user, :message => 'Not authorized as an administrator.'
@users = User.all
end
3)这不起作用
def index
authorize! :index, @my_user, :message => 'Not authorized as an administrator.'
@users = User.all
end
我的问题是为什么 1) 有效,但 3) 不起作用?
正如康康自述文件中提到的:
CanCanCan期望控制器中存在current_user方法。
所以cancan确实依赖于devise的授权current_user
,它检查current_user
是否authorize!
d来执行:action
,并且不(正如你可能已经猜到的那样)检查第二个参数(在本例中为@user
)是否被授权执行此操作。
那么授权的第二个参数是什么!?
authorize!
的第二个参数实际上是要检查操作(第一个参数)的对象。换句话说(并假设代码中的大小写):
CanCan将检查current_user
是否有权:index
@user
在您的评论中解释案例:
最奇怪的是,它也以某种方式起作用。当我以没有管理员角色的用户身份登录时,我收到消息"未授权为管理员",但是当我以管理员身份登录时,我没有收到该消息
由于您没有向/models/ability.rb
中的非:admin
用户授予任何权限,因此他们将无法执行任何操作,无论它是什么操作以及在其上执行的对象,即使在nil
上也是如此!另一方面,您已经授予具有:admin
角色的用户对/models/ability.rb 中所有 (:all
) 对象的所有 ( :manage
) 权限,因此他们能够对任何对象执行任何操作,无论它是什么,甚至是nil
!
您在此处没有实例化@user
变量。您只是在使用一个将user
局部变量传递给的方法。
这个user
变量包含的内容实际上取决于cancan
宝石。它将读取常用的current_user
辅助方法,以便cancan
与devise
和其他类似宝石一起工作。
事实上,当没有用户登录时,你会强制
user ||= User.new
以便能够处理来宾用户。但这都是方法局部变量的问题。
获取更详细的见解
定义能力
您可能误解了authorize!
和can?
方法的工作原理。
因此,cancan
将尝试验证current_user
是否有能力做某事。当前登录用户(current_user
)能够做什么必须在ability.rb
文件中定义。
能力有这种形式(最简单的情况):
_user_ can _action_ on _model_or_instance_
当你在Ability
类上这样做时:
def initialize(user)
can :read, Document
end
您实际上是在说当前登录用户可以读取Document
模型的所有实例:
user can :read Document #pseudo-code
将传递给 initialize
方法的user
将始终是 current_user
或 nil
.
检查/授权能力
在控制器中,您要检查能力。好的。所以你去:
authorize! :index, @user
这将转换为类似
verify if current_user can :index @user #pseudo-code
当您将其更改为:
authorize! :index, current_user
这相当于
verify if current_user can :index current_user #pseudo-code
你想做什么
您正在尝试授权current_user
做什么? :index
什么控制器/型号?
例如。我希望仅当当前登录用户是管理员时才能管理用户
最好的方法是保留Ability
定义:
authorize! :index, User
以便您授权current_user对整个User
模型进行操作。
警告
不应将要检查的用户传递给authorize!
和can?
方法。该用户将始终处于current_user
。例如。您无法获取User
实例并检查该实例是否具有这种或那种能力。 cancan
将始终使用登录用户作为"主要字符"(如果没有,则为 nil)。
为什么3)不起作用
3) 将按预期(以及 1)和 2))工作,当且仅当您登录到应用程序并且当前用户是管理员时。因此,如果出现以下情况,它将不起作用:
- 您尚未登录,并且
- 您不是管理员
在所有其他情况下(您以管理员身份登录),它将起作用。请先检查一下。
奖金
如果您是管理员(给定您的Ability
定义),则始终如此:
can? :any_action_of_your_choice, nil
既然你说管理员
can :manage, :all
从您的示例中,无论您在 authorize! :index,
之后设置了什么,如果您以管理员身份登录,它将始终授权(例如。 current_user.has_role? admin
)。
伙计 - 我应该工作不回答有关 SO 的问题,但是......
TLDR;没有设置@user
变量,这就是您的代码奇数的原因。
您将资源与访问它的人混为一谈
让我以您的第一个用例为例,并解释正在发生的事情:
# Case 1
class UsersController < ApplicationController
def index
authorize! :index, @user, :message => 'Not authorized as an administrator.'
@users = User.all
end
end
你已经有真实(但令人困惑)的错误
在这个阶段,你实际上有一个错误。您会在访问资源的人current_user
与您授予他们访问权限的资源(@user
)之间感到困惑。我是彼得(current_user
),但我可能正在尝试访问约翰(@user
)的用户页面。John 是我请求访问的资源。
在代码中,您尚未实际加载要授予访问权限的资源。让我一步一步地分解正在发生的事情。
我将使用 show 动作,因为它更容易看到正在发生的事情,但同样的事情也适用于index
让我们假设我,彼得正在尝试访问您的(鲶鱼)页面:
class UsersController < ApplicationController
def show
# first let's get explicit about who's accessing the page
@current_user = current_user
# now we need to load up the page I'm trying to access
@user = User.find params[:id]
# can I, Peter, access the :show action of this profile?
authorize! :show, @user, :message => 'Not authorized as an administrator.'
# rest of your controller...
@users = User.all
end
end
这会加载我,current_user
然后加载您,@user
然后询问我是否有权访问您的:show
操作。
您的案件中发生了什么
您使用索引操作的事实令人倍感困惑,因为那里没有任何资源可以加载。因此,我将解释使用 show 动作发生了什么:
# case 1
# this requests access FOR current_user TO @user
# I have no idea what the value of @user is (probably nil)
def show
authorize! :show, @user, :message => 'Not authorized as an administrator.'
@users = User.all
end
# case 2
# this requests access FOR current_user TO current_user
def show
# this is not what you want
authorize! :show, current_user, :message => 'Not authorized as an administrator.'
@users = User.all
end
# case 3)
# This requests access FOR current_user TO @my_user
# I have no idea what the value of @my_user is (probably nil)
def show
authorize! :show, @my_user, :message => 'Not authorized as an administrator.'
@users = User.all
end
您需要先加载资源并将该资源传递给能力文件
通过authenticate_user!
方法为您加载当前用户。但是,您需要确保尝试访问的资源也已加载。在:show
操作的情况下,这将发生如下:
class UsersController < ApplicationController
def show
# load the resource you want to protect access to
@user = params[:id]
# use cancan to see whether anyone can access it
authorize! :show, @user
@users = User.all
end
end
但是,根据这是嵌套资源的最后一级,可能会id
或user_id
。在:index
操作中,根本不会有这些参数中的任何一个。所有这些都很痛苦,但是康康使用load_resource
很好地为您处理了这一点
class UsersController < ApplicationController
def show
# CanCan figures out that the resource is a User and then gets it using the params
load_resource
# @user is now set.
# If the URL is http://yoursite.com/users/5, then @user.id == 5
authorize! :show, @user
@users = @user # not actually necessary since load_resource has already done this
end
end
现在,cancan通过结合加载和授权步骤使其变得更加容易
class UsersController < ApplicationController
def show
# figures out what you want to load and authorizes it (sweet)
load_and_authorize_resource
# @user will now be equal to User.find(params[id])
# no need to load @user - it's already been done
end
end
最后,您可以将这些内容拉到before_filter中,这样您就不必编写它们
class UsersController < ApplicationController
before_filter load_and_authorize_resource :user
def show
# in a pure CRUD app you literally won't need any code in your show action
end
end
使这一切更容易理解的方法
不要使用用户资源来学习康康使用页面、文章或其他任何内容。这将有助于您清楚哪个部分是资源,
@page
,哪个部分是当前用户@current_user
不要学习索引操作索引操作实际上不会加载特定资源,因为没有要加载的资源。这再次使事情更加难以理解。查看
:show
或:edit
上发生了什么,然后转到不需要资源的操作(:new
、:index
、:create
)