Rails - 试图学习康康,但@user对象从何而来



我刚刚用 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辅助方法,以便cancandevise和其他类似宝石一起工作。

事实上,当没有用户登录时,你会强制

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_usernil .

检查/授权能力

在控制器中,您要检查能力。好的。所以你去:

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))工作,当且仅当您登录到应用程序并且当前用户是管理员时。因此,如果出现以下情况,它将不起作用:

  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

但是,根据这是嵌套资源的最后一级,可能会iduser_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

使这一切更容易理解的方法

  1. 不要使用用户资源来学习康康使用页面、文章或其他任何内容。这将有助于您清楚哪个部分是资源,@page,哪个部分是当前用户@current_user

  2. 不要学习索引操作索引操作实际上不会加载特定资源,因为没有要加载的资源。这再次使事情更加难以理解。查看:show:edit上发生了什么,然后转到不需要资源的操作(:new:index:create

相关内容

最新更新