簡體   English   中英

Rails測試:確保在所有控制器和操作中強制執行授權(Pundit)

[英]Rails testing: ensure authorization (Pundit) is enforced in all controllers and actions

我正在為使用Pundit進行授權的Rails 4.2應用程序編寫RSpec測試。

我想測試是否在所有控制器的所有操作中強制執行授權,以避免在開發人員忘記調用policy_scope (在#index操作上)和authorize (在所有其他操作上)的情況下無意中提供對敏感數據的公共訪問。

一種可能的解決方案是在所有控制器單元測試中模擬這些方法。 expect(controller).to receive(:authorize).and_return(true)expect(controller).to receive(:policy_scope).and_call_original 但是,這會導致大量代碼重復。 這行可以放在spec/support的自定義匹配器或輔助方法中,但在每個控制器的每個規范中調用它也似乎是重復的。 關於如何以干燥方式實現這一目標的任何想法?

如果你想知道,權威人士的策略類,分別進行測試,如圖這篇文章

我覺得你可以在spec_helper中使用這樣的東西。 請注意,我假設一個命名約定,您在索引級別答案中有“index”一詞,因此您的規范可能如下所示:

describe MyNewFeaturesController, :type => :controller do

  describe "index" do
    # all of the index tests under here have policy_scope applied
  end

  # and these other tests have authorize applied
  describe 'show' do
  end

  describe 'destroy' do
  end
end

這是整體配置:

RSpec.configure do |config|
  config.before(:each, :type => :controller) do |spec|
    # if the spec description has "index" in the name, then use policy-level authorization
    if spec.metadata[:full_description] =~ /\bindex\b/
      expect(controller).to receive(:policy_scope).and_call_original
    else 
      expect(controller).to receive(:authorize).and_call_original
    end
  end
end

下面是一個使用shared_examples,before:suite掛鈎和元編程的示例,它可能會滿足您的需求。

RSpec.configure do |config|
  config.before(:suite, :type => :controller) do |spec|
      it_should_behave_like("authorized_controller")
  end
end

在spec_helper中結束

shared_examples_for "authorized_controller" do
  # expects controller to define index_params, create_params, etc
  describe "uses pundit" do 
    HTTP_VERB = {
      :create => :post, :update=>:put, :destroy=>:delete 
    }
    %i{ new create show edit index update destroy}.each do |action|
       if controller.responds_to action
        it "for #{action}" do
          expect(controller).to receive(:policy_scope) if :action == :index
          expect(controller).to receive(:authorize) unless :action == :index
          send (HTTP_VERB[action]||:get), action
        end
      end
    end
  end
end

Pundit已經提供了一種機制來保證開發人員在執行控制器操作時不會忘記授權:

class ApplicationController < ActionController::Base
  include Pundit
  after_action :verify_authorized, except: :index
  after_action :verify_policy_scoped, only: :index
end

如果未執行auth,則指示Pundit raise 只要測試了所有控制器,就會導致規范失敗。

https://github.com/elabs/pundit#ensuring-policies-and-scopes-are-used

我正在發布我最近嘗試的代碼。

請注意:

  • 您可能不應該使用此代碼,因為它感覺過於復雜和hacky。
  • 如果在發生異常后調用authorizepolicy_scope則它不起作用。 如果測試的操作調用Active Record方法(如findupdatedestroy而不提供有效參數,則會發生異常。 以下代碼使用空值創建偽參數。 空ID無效,將導致ActiveRecord::RecordNotFound異常。 一旦找到解決方案,我將更新代碼。

spec/controllers/all_controllers_spec.rb

# Test all descendants of this base controller controller
BASE_CONTROLLER = ApplicationController

# To exclude specific actions:
# "TasksController" => [:create, :new, :index]
# "API::V1::PostsController" => [:index]
#
# To exclude entire controllers:
# "TasksController" => nil
# "API::V1::PostsController" => nil
EXCLUDED = {
  'TasksController' => nil
}

def expected_auth_method(action)
  action == 'index' ? :policy_scope : :authorize
end

def create_fake_params(route)
  # Params with non-nil values are required to "No route matches..." error
  route.parts.map { |param| [param, ''] }.to_h
end

def extract_action(route)
  route.defaults[:action]
end

def extract_http_method(route)
  route.constraints[:request_method].to_s.delete("^A-Z")
end

def skip_controller?(controller)
  EXCLUDED.key?(controller.name) && EXCLUDED[controller.name].nil?
end

def skip_action?(controller, action)
  EXCLUDED.key?(controller.name) &&
    EXCLUDED[controller.name].include?(action.to_sym)
end

def testable_controllers
  Rails.application.eager_load!
  BASE_CONTROLLER.descendants.reject {|controller| skip_controller?(controller)}
end

def testable_routes(controller)
  Rails.application.routes.set.select do |route|
    route.defaults[:controller] == controller.controller_path &&
      !skip_action?(controller, extract_action(route))
  end
end

# Do NOT name the loop variable "controller" or it will override the
# "controller" object available within RSpec controller specs.
testable_controllers.each do |tested_controller|
  RSpec.describe tested_controller, :focus, type: :controller do
    # login_user is implemented in spec/support/controller_macros.rb
    login_user
    testable_routes(tested_controller).each do |route|
      action = extract_action(route)
      http_method = extract_http_method(route)

      describe "#{http_method} ##{action}" do
        it 'enforces authorization' do
          expect(controller).to receive(expected_auth_method(action)).and_return(true)
          begin
            process(action, http_method, create_fake_params(route))
          rescue ActiveRecord::RecordNotFound
          end
        end
      end
    end
  end
end

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM