2013-01-03 79 views
0

我想,以測試Farm模型中的用戶身份驗證的不同反應,在這種情況下,對於具有訪問時被登錄的所有農場的:user角色(如來賓用戶aka。anonymous也有)。慘慘accessibly_by返回規範和控制

# /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我可以看到它包含TestfarmAccess denied。這很奇怪,因爲我在規範和index操作中都使用了相同的accessible_by方法。我使用的設置在名爲Fetching Records的CanCan gem的wiki中描述。

無用的解決辦法

當我添加用戶user農場Access denied,如...

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

...然後測試成功。

的問題

  1. 爲什麼是「拒絕訪問」農場返回儘管它可以被任何用戶(包括來賓用戶)來讀取?
  2. get "#{url}.json"實際上是否考慮用戶的狀態?這是否全部由load_and_authorize_resource完成FarmsController
  3. wiki mentions@farms = Farm.accessible_by(current_ability, :read)可以,因爲「這是自動load_resource該指數一蹴而就」被排除在外。這是否適用於我的情況?

實驗

我創造了另一個用戶「用戶2」和另一場「我的小農場」。我把這些聯繫起來。這樣,在本例中的數據庫包含產品總數三個農場:

  • 農場「Testfarm」相關的user1
  • 農場「拒絕訪問」相關聯沒有相關user2的用戶
  • 農場「我的小農場」。

當我運行Farm.accessible_by(Ability.new(user1), :read)我仍然只收到「Testfarm」。

回答

0

我的問題的答案由多個部分組成。我希望這可以澄清設置給所有處理類似配置的人。

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

保持它的指標動作簡單。 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.認證令牌

Get請求不要忘了通過令牌當您從農場控制器請求數據。

# # /spec/api/v1/farm_spec.rb 
get "#{url}.json", auth_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