CanCan accessibility_by возвращает разные ответы в спецификации и контроллере

Я пытаюсь проверить аутентификацию пользователя для модели Farm, в данном случае для роли :user, которая имеет доступ чтение ко всем фермам при входе в систему (как и гостевой пользователь, также известный как аноним) .

# /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, я вижу, что он содержит оба Testfarm и Access denied. Это странно, так как я использую один и тот же метод accessible_by и в спецификации, и в действии index. Настройка, которую я использую, описана в вики драгоценного камня CanCan под названием Извлечение записей.

Бесполезный обходной путь

Когда я добавляю пользователя user в ферму Access denied, например...

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

... тогда тест проходит успешно.

Вопросы

  1. Почему ферма «Отказано в доступе» не возвращается, хотя ее может прочитать любой пользователь (включая гостевых пользователей)?
  2. Действительно ли get "#{url}.json" учитывает статус пользователя? Это все сделано load_and_authorize_resource в FarmsController?
  3. В вики упоминается, что @farms = Farm.accessible_by(current_ability, :read) можно опустить, поскольку "это делается автоматически load_resource для индексного действия». Это применимо к моей ситуации?

Эксперименты

Я создал еще одного пользователя «user2» и еще одну ферму «Моя маленькая ферма». Я связал их друг с другом. Таким образом, база данных в примере содержит всего три фермы:

  • Ферма "Testfarm", связанная с user1
  • Ферма «Отказано в доступе», не связанная ни с одним пользователем
  • Ферма «Моя маленькая ферма», связанная с пользователем2.

Когда я запускаю Farm.accessible_by(Ability.new(user1), :read), я все равно получаю только "Testfarm".


person JJD    schedule 03.01.2013    source источник


Ответы (1)


Ответ на мой вопрос состоит из нескольких частей. Я надеюсь, что это проясняет настройку для всех, кто имеет дело с аналогичной конфигурацией.

1. Приоритет способностей

Прежде всего, имейте в виду, что порядок правил способностей имеет значение, как описано в Приоритете способностей. Осознав этот факт, я придумал обновленный набор правил способностей.

# /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. ФермыКонтоллер

Будьте проще в действии index. 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

Токен должен быть добавлен в модель пользователя следующим образом.

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

И имя метода можно настроить в инициализаторе Devise.

# config/initializers/devise.rb
config.token_authentication_key = :auth_token
person JJD    schedule 18.01.2013