【问题标题】:CanCan accessibly_by returns different responses in spec and controllerCanCan accessibly_by 在规范和控制器中返回不同的响应
【发布时间】:2023-10-22 04:53:01
【问题描述】:

我正在尝试测试Farm 模型的用户身份验证,在这种情况下,:user 角色在登录时具有对所有场的读取访问权限(作为访客user aka.anonymous 也有)。

# /models/ability.rb
class Ability
  include CanCan::Ability

  def initialize(user)
    # Create guest user aka. anonymous (not logged in) when user is nil.
    user ||= User.new

    if user.has_role? :admin
      can :manage, :all
    else # guest user aka. anonymous
      can :read, :all
      # logged in user
      if user.has_role? :user
        can :create, Farm
        can :manage, Farm, :user_id => user.id
      end
    end

  end
end

...

# /controllers/api/v1/farms_controller.rb
class Api::V1::FarmsController < ActionController::Base

    load_and_authorize_resource
    rescue_from CanCan::AccessDenied do |exception|
        redirect_to farms_path, alert: exception.message
    end
    respond_to :json

    def index
        # Next line might be redundant refering to the CanCan wiki. See below..
        @farms = Farm.accessible_by(current_ability, :read)
        respond_with(@farms)
    end
end

...

# /spec/api/v1/farm_spec.rb
require "spec_helper"

describe "/api/v1/farms" do
    let(:user) { create(:user) } # lets call this user1 in the discussion
    let(:token) { user.authentication_token }

    before do
        user.add_role :user
        create(:farm, user: user, name: "Testfarm")
        create(:farm, name: "Access denied")
        @ability = Ability.new(user)
    end

    context "farms viewable by this logged-in user" do
        let(:url) { "/api/v1/farms" }
        it "json" do
            get "#{url}.json"

            farms_json = Farm.accessible_by(@ability, :read).to_json

            assert last_response.ok?
            last_response.body.should eql(farms_json)
            last_response.status.should eql(200)

            farms = JSON.parse(last_response.body)

            farms.any? do |farm|
                farm["name"] == "Testfarm"
            end.should be_true

            farms.any? do |farm|
                farm["name"] == "Access denied"
            end.should be_true

        end
    end
end

问题

当我检查 farms_json 时,我可以看到它只包含 Testfarm。当我检查last_response 时,我可以看到它包含both Testfarm Access denied。这很奇怪,因为我在规范和 index 操作中都使用了相同的 accessible_by 方法。我使用的设置在名为 Fetching Records 的 CanCan gem 的 wiki 中进行了描述。

无用的解决方法

当我将用户user添加到农场Access denied时,比如...

create(:farm, user: user, name: "Access denied")

...那么测试成功。

问题

  1. 为什么任何用户(包括来宾用户)都可以读取“访问被拒绝”场返回?
  2. get "#{url}.json"真的会考虑用户的状态吗?这一切都是由FarmsController 中的load_and_authorize_resource 完成的吗?
  3. wiki mentions@farms = Farm.accessible_by(current_ability, :read) 可以省略,因为“这是由load_resource 为索引操作自动完成的”。这适用于我的情况吗?

实验

我创建了另一个用户“user2”和另一个农场“我的小农场”。我把它们联系起来了。这样,示例中的数据库总共包含三个农场:

  • 与 user1 关联的农场“Testfarm”
  • 农场“拒绝访问”与任何用户关联
  • 与 user2 关联的农场“我的小农场”。

当我运行Farm.accessible_by(Ability.new(user1), :read) 时,我仍然只收到“Testfarm”。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 rspec cancan rolify


    【解决方案1】:

    我的问题的答案由多个部分组成。我希望这能让所有处理类似配置的人清楚地了解设置。

    1。技能优先级

    首先请注意order of ability rules does matter as described in Ability Precedence。在意识到这个事实后,我想出了一套更新的能力规则。

    # /models/ability.rb
    class Ability
      include CanCan::Ability
    
      def initialize(user)
        # Create guest user aka. anonymous (not logged-in) when user is nil.
        user ||= User.new
    
        if user.has_role? :admin
          can :manage, :all
        else
          # logged in user
          if user.has_role? :user
            can :manage, Farm, :user_id => user.id
            can :create, Farm
          end
           # guest user aka. anonymous
          can :read, :all
        end
      end
    end
    

    2。农场控制器

    在索引操作中保持简单。 load_and_authorize_resource 是你的朋友。

    # /controllers/api/v1/farms_controller.rb
    class Api::V1::FarmsController < ActionController::Base
    
        load_and_authorize_resource
        rescue_from CanCan::AccessDenied do |exception|
            redirect_to farms_path, alert: exception.message
        end
        respond_to :json
    
        def index
            respond_with(@farms)
        end
    end
    

    3。获取带有身份验证令牌的请求

    当您从农场控制器请求数据时,不要忘记传递令牌。

    # # /spec/api/v1/farm_spec.rb
    get "#{url}.json", auth_token: :token
    

    token 必须按如下方式添加到 User 模型中。

    # app/models/user.rb
    class User < ActiveRecord::Base
        before_save :ensure_authentication_token
    

    并且方法的名称可以在Devise的初始化器中配置。

    # config/initializers/devise.rb
    config.token_authentication_key = :auth_token
    

    【讨论】:

      最近更新 更多