2

我刚刚使用带有 devise/cancan/rolify 的 rails composer 创建了一个 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

/控制器/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) 无效?

4

3 回答 3

5

cancan README中所述:

CanCan 期望控制器中存在 current_user 方法。

所以 cancan确实依赖于设计的current_user授权,它检查是否current_userauthorize!d 来执行:action,并且不(正如您可能已经猜到的)检查第二个参数(在这种情况下@user是 )是否被授权执行该操作。

那么authorize的第二个参数是什么!?

的第二个参数authorize!实际上是要检查动作(第一个参数)的对象。换句话说(并假设您的代码中的情况):

cancan 将检查是否有current_user权限:index@user

在您的评论中解释案例:

最奇怪的是它也能以某种方式工作。当我以没有管理员角色的用户身份登录时,我收到消息“未授权为管理员”,但是当我以管理员身份登录时,我没有收到该消息

由于您没有向 中的非:admin用户授予任何权限/models/ability.rb,因此他们将无法执行任何操作,无论它是什么操作以及它在什么对象上执行,即使在nil! - 这似乎是你的情况。另一方面,您已授予具有:admin角色 all ( :manage) 权限的用户对:all/models/ability.rb 中的所有 () 对象的权限,因此他们可以对任何对象执行任何操作,无论它是什么,甚至是nil

于 2013-11-17T00:32:45.583 回答
3

您没有在@user这里实例化变量。您只是在使用一种获取user传递给的局部变量的方法。

这个user变量包含的内容实际上取决于cancangem。它将读取常用的current_user辅助方法,以便cancandevise其他类似的 gem 一起工作。

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

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

user将传递给initialize方法的将始终是current_useror 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))。因此,如果出现以下情况,它将不起作用:

  1. 您尚未登录,并且
  2. 你不是管理员

在所有其他情况下(您以管理员身份登录)它将起作用。请先检查一下。

奖金

如果您是管理员(根据您的Ability定义),这将始终是正确的:

can? :any_action_of_your_choice, nil

既然你说管理员

can :manage, :all

从您的示例中,无论您在 之后设置什么,如果您以管理员身份登录(例如)authorize! :index,,它将始终授权。current_user.has_role? admin

于 2013-11-18T22:18:38.293 回答
1

啊,伙计-我应该工作而不是回答有关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. 我是 Peter ( current_user),但我可能正在尝试访问 John 的 ( @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操作。

你的情况发生了什么

您正在使用 index 操作这一事实令人倍感困惑,因为那里没有任何资源可供加载。因此,我将解释使用 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 可能是iduser_id。在:index动作中根本不会有这些参数中的任何一个。所有这一切都很痛苦,但是 cancan 可以很好地为您使用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. 不要使用用户资源来学习 cancan 使用页面、文章或其他任何内容。它将帮助您清楚哪个部分是资源,@page哪个部分是当前用户@current_user

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

于 2013-11-19T00:02:23.427 回答