[英]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
我正在發布我最近嘗試的代碼。
請注意:
authorize
或policy_scope
則它不起作用。 如果測試的操作調用Active Record方法(如find
, update
和destroy
而不提供有效參數,則會發生異常。 以下代碼使用空值創建偽參數。 空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.