diff options
Diffstat (limited to 'actionpack/test/controller/request_forgery_protection_test.rb')
-rw-r--r-- | actionpack/test/controller/request_forgery_protection_test.rb | 225 |
1 files changed, 152 insertions, 73 deletions
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 2a5aad9c0e..94ffbe3cd0 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'digest/sha1' require "active_support/log_subscriber/test_helper" # common controller actions @@ -12,30 +11,14 @@ module RequestForgeryProtectionActions render :inline => "<%= button_to('New', '/') %>" end - def external_form - render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => 'external_token') {} %>" - end - - def external_form_without_protection - render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => false) {} %>" - end - def unsafe - render :text => 'pwn' + render plain: 'pwn' end def meta render :inline => "<%= csrf_meta_tags %>" end - def external_form_for - render :inline => "<%= form_for(:some_resource, :authenticity_token => 'external_token') {} %>" - end - - def form_for_without_protection - render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>" - end - def form_for_remote render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>" end @@ -70,7 +53,6 @@ module RequestForgeryProtectionActions negotiate_same_origin end - def rescue_action(e) raise e end end # sample controllers @@ -89,17 +71,42 @@ class RequestForgeryProtectionControllerUsingNullSession < ActionController::Bas def signed cookies.signed[:foo] = 'bar' - render :nothing => true + head :ok end def encrypted cookies.encrypted[:foo] = 'bar' - render :nothing => true + head :ok end def try_to_reset_session reset_session - render :nothing => true + head :ok + end +end + +class PrependProtectForgeryBaseController < ActionController::Base + before_action :custom_action + attr_accessor :called_callbacks + + def index + render inline: 'OK' + end + + protected + + def add_called_callback(name) + @called_callbacks ||= [] + @called_callbacks << name + end + + + def custom_action + add_called_callback("custom_action") + end + + def verify_authenticity_token + add_called_callback("verify_authenticity_token") end end @@ -124,9 +131,7 @@ end # common test methods module RequestForgeryProtectionTests def setup - @token = "cf50faa3fe97702ca1ae" - - SecureRandom.stubs(:base64).returns(@token) + @token = Base64.strict_encode64('railstestrailstestrailstestrails') @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token ActionController::Base.request_forgery_protection_token = :custom_authenticity_token end @@ -136,17 +141,21 @@ module RequestForgeryProtectionTests end def test_should_render_form_with_token_tag - assert_not_blocked do - get :index + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :index + end + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_button_to_with_token_tag - assert_not_blocked do - get :show_button + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :show_button + end + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_without_token_tag_if_remote @@ -190,17 +199,21 @@ module RequestForgeryProtectionTests end def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested - assert_not_blocked do - get :form_for_remote_with_token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :form_for_remote_with_token + end + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_with_token_tag_with_authenticity_token_requested - assert_not_blocked do - get :form_for_with_token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :form_for_with_token + end + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_allow_get @@ -236,41 +249,57 @@ module RequestForgeryProtectionTests end def test_should_not_allow_xhr_post_without_token - assert_blocked { xhr :post, :index } + assert_blocked { post :index, xhr: true } end def test_should_allow_post_with_token - assert_not_blocked { post :index, :custom_authenticity_token => @token } + session[:_csrf_token] = @token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked { post :index, params: { custom_authenticity_token: @token } } + end end def test_should_allow_patch_with_token - assert_not_blocked { patch :index, :custom_authenticity_token => @token } + session[:_csrf_token] = @token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked { patch :index, params: { custom_authenticity_token: @token } } + end end def test_should_allow_put_with_token - assert_not_blocked { put :index, :custom_authenticity_token => @token } + session[:_csrf_token] = @token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked { put :index, params: { custom_authenticity_token: @token } } + end end def test_should_allow_delete_with_token - assert_not_blocked { delete :index, :custom_authenticity_token => @token } + session[:_csrf_token] = @token + @controller.stub :form_authenticity_token, @token do + assert_not_blocked { delete :index, params: { custom_authenticity_token: @token } } + end end def test_should_allow_post_with_token_in_header + session[:_csrf_token] = @token @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { post :index } end def test_should_allow_delete_with_token_in_header + session[:_csrf_token] = @token @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { delete :index } end def test_should_allow_patch_with_token_in_header + session[:_csrf_token] = @token @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { patch :index } end def test_should_allow_put_with_token_in_header + session[:_csrf_token] = @token @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { put :index } end @@ -314,21 +343,22 @@ module RequestForgeryProtectionTests get :negotiate_same_origin end - assert_cross_origin_not_blocked { xhr :get, :same_origin_js } - assert_cross_origin_not_blocked { xhr :get, :same_origin_js, format: 'js' } + assert_cross_origin_not_blocked { get :same_origin_js, xhr: true } + assert_cross_origin_not_blocked { get :same_origin_js, xhr: true, format: 'js'} assert_cross_origin_not_blocked do @request.accept = 'text/javascript' - xhr :get, :negotiate_same_origin + get :negotiate_same_origin, xhr: true end end # Allow non-GET requests since GET is all a remote <script> tag can muster. def test_should_allow_non_get_js_without_xhr_header - assert_cross_origin_not_blocked { post :same_origin_js, custom_authenticity_token: @token } - assert_cross_origin_not_blocked { post :same_origin_js, format: 'js', custom_authenticity_token: @token } + session[:_csrf_token] = @token + assert_cross_origin_not_blocked { post :same_origin_js, params: { custom_authenticity_token: @token } } + assert_cross_origin_not_blocked { post :same_origin_js, params: { format: 'js', custom_authenticity_token: @token } } assert_cross_origin_not_blocked do @request.accept = 'text/javascript' - post :negotiate_same_origin, custom_authenticity_token: @token + post :negotiate_same_origin, params: { custom_authenticity_token: @token} end end @@ -340,11 +370,17 @@ module RequestForgeryProtectionTests get :negotiate_cross_origin end - assert_cross_origin_not_blocked { xhr :get, :cross_origin_js } - assert_cross_origin_not_blocked { xhr :get, :cross_origin_js, format: 'js' } + assert_cross_origin_not_blocked { get :cross_origin_js, xhr: true } + assert_cross_origin_not_blocked { get :cross_origin_js, xhr: true, format: 'js' } assert_cross_origin_not_blocked do @request.accept = 'text/javascript' - xhr :get, :negotiate_cross_origin + get :negotiate_cross_origin, xhr: true + end + end + + def test_should_not_raise_error_if_token_is_not_a_string + assert_blocked do + patch :index, params: { custom_authenticity_token: { foo: 'bar' } } end end @@ -386,10 +422,13 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController end test 'should emit a csrf-param meta tag and a csrf-token meta tag' do - SecureRandom.stubs(:base64).returns(@token + '<=?') - get :meta - assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token' - assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae<=?' + @controller.stub :form_authenticity_token, @token + '<=?' do + get :meta + assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token' + assert_select 'meta[name=?]', 'csrf-token' + regexp = "#{@token}<=\?" + assert_match(/#{regexp}/, @response.body) + end end end @@ -429,35 +468,75 @@ class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::T end end +class PrependProtectForgeryBaseControllerTest < ActionController::TestCase + PrependTrueController = Class.new(PrependProtectForgeryBaseController) do + protect_from_forgery prepend: true + end + + PrependFalseController = Class.new(PrependProtectForgeryBaseController) do + protect_from_forgery prepend: false + end + + PrependDefaultController = Class.new(PrependProtectForgeryBaseController) do + protect_from_forgery + end + + def test_verify_authenticity_token_is_prepended + @controller = PrependTrueController.new + get :index + expected_callback_order = ["verify_authenticity_token", "custom_action"] + assert_equal(expected_callback_order, @controller.called_callbacks) + end + + def test_verify_authenticity_token_is_not_prepended + @controller = PrependFalseController.new + get :index + expected_callback_order = ["custom_action", "verify_authenticity_token"] + assert_equal(expected_callback_order, @controller.called_callbacks) + end + + def test_verify_authenticity_token_is_prepended_by_default + @controller = PrependDefaultController.new + get :index + expected_callback_order = ["verify_authenticity_token", "custom_action"] + assert_equal(expected_callback_order, @controller.called_callbacks) + end +end + class FreeCookieControllerTest < ActionController::TestCase def setup @controller = FreeCookieController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new @token = "cf50faa3fe97702ca1ae" - - SecureRandom.stubs(:base64).returns(@token) + super end def test_should_not_render_form_with_token_tag - get :index - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + SecureRandom.stub :base64, @token do + get :index + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + end end def test_should_not_render_button_to_with_token_tag - get :show_button - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + SecureRandom.stub :base64, @token do + get :show_button + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + end end def test_should_allow_all_methods_without_token - [:post, :patch, :put, :delete].each do |method| - assert_nothing_raised { send(method, :index)} + SecureRandom.stub :base64, @token do + [:post, :patch, :put, :delete].each do |method| + assert_nothing_raised { send(method, :index)} + end end end test 'should not emit a csrf-token meta tag' do - get :meta - assert @response.body.blank? + SecureRandom.stub :base64, @token do + get :meta + assert @response.body.blank? + end end end @@ -466,7 +545,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase super @old_logger = ActionController::Base.logger @logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new - @token = "foobar" + @token = Base64.strict_encode64(SecureRandom.random_bytes(32)) @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token ActionController::Base.request_forgery_protection_token = @token end @@ -478,11 +557,11 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase def test_should_not_warn_if_form_authenticity_param_matches_form_authenticity_token ActionController::Base.logger = @logger - SecureRandom.stubs(:base64).returns(@token) - begin - post :index, :custom_token_name => 'foobar' - assert_equal 0, @logger.logged(:warn).size + @controller.stub :valid_authenticity_token?, :true do + post :index, params: { custom_token_name: 'foobar' } + assert_equal 0, @logger.logged(:warn).size + end ensure ActionController::Base.logger = @old_logger end @@ -492,7 +571,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase ActionController::Base.logger = @logger begin - post :index, :custom_token_name => 'bazqux' + post :index, params: { custom_token_name: 'bazqux' } assert_equal 1, @logger.logged(:warn).size ensure ActionController::Base.logger = @old_logger |