diff options
Diffstat (limited to 'actionpack/test/controller/request_forgery_protection_test.rb')
-rw-r--r-- | actionpack/test/controller/request_forgery_protection_test.rb | 368 |
1 files changed, 218 insertions, 150 deletions
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 1984ad8825..90d5ab3c67 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -1,42 +1,42 @@ -require 'abstract_unit' +require "abstract_unit" require "active_support/log_subscriber/test_helper" # common controller actions module RequestForgeryProtectionActions def index - render :inline => "<%= form_tag('/') {} %>" + render inline: "<%= form_tag('/') {} %>" end def show_button - render :inline => "<%= button_to('New', '/') %>" + render inline: "<%= button_to('New', '/') %>" end def unsafe - render plain: 'pwn' + render plain: "pwn" end def meta - render :inline => "<%= csrf_meta_tags %>" + render inline: "<%= csrf_meta_tags %>" end def form_for_remote - render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>" + render inline: "<%= form_for(:some_resource, :remote => true ) {} %>" end def form_for_remote_with_token - render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>" + render inline: "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>" end def form_for_with_token - render :inline => "<%= form_for(:some_resource, :authenticity_token => true ) {} %>" + render inline: "<%= form_for(:some_resource, :authenticity_token => true ) {} %>" end def form_for_remote_with_external_token - render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>" + render inline: "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>" end def same_origin_js - render js: 'foo();' + render js: "foo();" end def negotiate_same_origin @@ -52,30 +52,29 @@ module RequestForgeryProtectionActions def negotiate_cross_origin negotiate_same_origin end - end # sample controllers class RequestForgeryProtectionControllerUsingResetSession < ActionController::Base include RequestForgeryProtectionActions - protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :reset_session + protect_from_forgery only: %w(index meta same_origin_js negotiate_same_origin), with: :reset_session end class RequestForgeryProtectionControllerUsingException < ActionController::Base include RequestForgeryProtectionActions - protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :exception + protect_from_forgery only: %w(index meta same_origin_js negotiate_same_origin), with: :exception end class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base - protect_from_forgery :with => :null_session + protect_from_forgery with: :null_session def signed - cookies.signed[:foo] = 'bar' + cookies.signed[:foo] = "bar" head :ok end def encrypted - cookies.encrypted[:foo] = 'bar' + cookies.encrypted[:foo] = "bar" head :ok end @@ -90,65 +89,68 @@ class PrependProtectForgeryBaseController < ActionController::Base attr_accessor :called_callbacks def index - render inline: 'OK' + render inline: "OK" end protected - def add_called_callback(name) - @called_callbacks ||= [] - @called_callbacks << name - end - + def add_called_callback(name) + @called_callbacks ||= [] + @called_callbacks << name + end - def custom_action - add_called_callback("custom_action") - end + def custom_action + add_called_callback("custom_action") + end - def verify_authenticity_token - add_called_callback("verify_authenticity_token") - end + def verify_authenticity_token + add_called_callback("verify_authenticity_token") + end end class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession self.allow_forgery_protection = false def index - render :inline => "<%= form_tag('/') {} %>" + render inline: "<%= form_tag('/') {} %>" end def show_button - render :inline => "<%= button_to('New', '/') %>" + render inline: "<%= button_to('New', '/') %>" end end class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsingResetSession def form_authenticity_param - 'foobar' + "foobar" end end class PerFormTokensController < ActionController::Base - protect_from_forgery :with => :exception + protect_from_forgery with: :exception self.per_form_csrf_tokens = true def index - render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: (params[:form_method] || :post) %>" + render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: params[:form_method] %>" + end + + def button_to + render inline: "<%= button_to 'Button', (params[:form_path] || '/per_form_tokens/post_one'), method: params[:form_method] %>" end def post_one - render plain: '' + render plain: "" end def post_two - render plain: '' + render plain: "" end end # common test methods module RequestForgeryProtectionTests def setup - @token = Base64.strict_encode64('railstestrailstestrailstestrails') + @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 @@ -162,7 +164,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :index end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token end end @@ -171,7 +173,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :show_button end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token end end @@ -202,7 +204,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_external_token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token" ensure ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original end @@ -212,7 +214,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_external_token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token" end def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested @@ -220,7 +222,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token end end @@ -229,7 +231,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_with_token end - assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token end end @@ -250,7 +252,7 @@ module RequestForgeryProtectionTests end def test_should_not_allow_post_without_token_irrespective_of_format - assert_blocked { post :index, format: 'xml' } + assert_blocked { post :index, format: "xml" } end def test_should_not_allow_patch_without_token @@ -299,25 +301,25 @@ module RequestForgeryProtectionTests def test_should_allow_post_with_token_in_header session[:_csrf_token] = @token - @request.env['HTTP_X_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 + @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 + @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 + @request.env["HTTP_X_CSRF_TOKEN"] = @token assert_not_blocked { put :index } end @@ -326,7 +328,7 @@ module RequestForgeryProtectionTests session[:_csrf_token] = @token @controller.stub :form_authenticity_token, @token do assert_not_blocked do - @request.set_header 'HTTP_ORIGIN', 'http://test.host' + @request.set_header "HTTP_ORIGIN", "http://test.host" post :index, params: { custom_authenticity_token: @token } end end @@ -349,7 +351,7 @@ module RequestForgeryProtectionTests session[:_csrf_token] = @token @controller.stub :form_authenticity_token, @token do assert_blocked do - @request.set_header 'HTTP_ORIGIN', 'http://bad.host' + @request.set_header "HTTP_ORIGIN", "http://bad.host" post :index, params: { custom_authenticity_token: @token } end end @@ -389,50 +391,81 @@ module RequestForgeryProtectionTests def test_should_only_allow_same_origin_js_get_with_xhr_header assert_cross_origin_blocked { get :same_origin_js } - assert_cross_origin_blocked { get :same_origin_js, format: 'js' } + assert_cross_origin_blocked { get :same_origin_js, format: "js" } assert_cross_origin_blocked do - @request.accept = 'text/javascript' + @request.accept = "text/javascript" get :negotiate_same_origin end 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 { get :same_origin_js, xhr: true, format: "js" } assert_cross_origin_not_blocked do - @request.accept = 'text/javascript' + @request.accept = "text/javascript" get :negotiate_same_origin, xhr: true end end + def test_should_warn_on_not_same_origin_js + old_logger = ActionController::Base.logger + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + ActionController::Base.logger = logger + + begin + assert_cross_origin_blocked { get :same_origin_js } + + assert_equal 1, logger.logged(:warn).size + assert_match(/<script> tag on another site requested protected JavaScript/, logger.logged(:warn).last) + ensure + ActionController::Base.logger = old_logger + end + end + + def test_should_not_warn_if_csrf_logging_disabled_and_not_same_origin_js + old_logger = ActionController::Base.logger + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + ActionController::Base.logger = logger + ActionController::Base.log_warning_on_csrf_failure = false + + begin + assert_cross_origin_blocked { get :same_origin_js } + + assert_equal 0, logger.logged(:warn).size + ensure + ActionController::Base.logger = old_logger + ActionController::Base.log_warning_on_csrf_failure = 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 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 { 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, params: { custom_authenticity_token: @token} + @request.accept = "text/javascript" + post :negotiate_same_origin, params: { custom_authenticity_token: @token } end end def test_should_only_allow_cross_origin_js_get_without_xhr_header_if_protection_disabled assert_cross_origin_not_blocked { get :cross_origin_js } - assert_cross_origin_not_blocked { get :cross_origin_js, format: 'js' } + assert_cross_origin_not_blocked { get :cross_origin_js, format: "js" } assert_cross_origin_not_blocked do - @request.accept = 'text/javascript' + @request.accept = "text/javascript" get :negotiate_cross_origin end 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 { get :cross_origin_js, xhr: true, format: "js" } assert_cross_origin_not_blocked do - @request.accept = 'text/javascript' + @request.accept = "text/javascript" 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' } } + patch :index, params: { custom_authenticity_token: { foo: "bar" } } end end @@ -474,20 +507,11 @@ end class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController::TestCase include RequestForgeryProtectionTests - setup do - @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token - ActionController::Base.request_forgery_protection_token = :custom_authenticity_token - end - - teardown do - ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token - end - - test 'should emit a csrf-param meta tag and a csrf-token meta tag' do - @controller.stub :form_authenticity_token, @token + '<=?' do + test "should emit a csrf-param meta tag and a csrf-token meta tag" do + @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' + assert_select "meta[name=?][content=?]", "csrf-param", "custom_authenticity_token" + assert_select "meta[name=?]", "csrf-token" regexp = "#{@token}<=\?" assert_match(/#{regexp}/, @response.body) end @@ -497,7 +521,7 @@ end class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase class NullSessionDummyKeyGenerator def generate_key(secret) - '03312270731a2ed0d11ed091c2338a06' + "03312270731a2ed0d11ed091c2338a06" end end @@ -505,17 +529,17 @@ class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController: @request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new end - test 'should allow to set signed cookies' do + test "should allow to set signed cookies" do post :signed assert_response :ok end - test 'should allow to set encrypted cookies' do + test "should allow to set encrypted cookies" do post :encrypted assert_response :ok end - test 'should allow reset_session' do + test "should allow reset_session" do post :try_to_reset_session assert_response :ok end @@ -575,26 +599,26 @@ class FreeCookieControllerTest < ActionController::TestCase def test_should_not_render_form_with_token_tag SecureRandom.stub :base64, @token do get :index - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + assert_select "form>div>input[name=?][value=?]", "authenticity_token", @token, false end end def test_should_not_render_button_to_with_token_tag SecureRandom.stub :base64, @token do get :show_button - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false + assert_select "form>div>input[name=?][value=?]", "authenticity_token", @token, false end end def test_should_allow_all_methods_without_token SecureRandom.stub :base64, @token do [:post, :patch, :put, :delete].each do |method| - assert_nothing_raised { send(method, :index)} + assert_nothing_raised { send(method, :index) } end end end - test 'should not emit a csrf-token meta tag' do + test "should not emit a csrf-token meta tag" do SecureRandom.stub :base64, @token do get :meta assert @response.body.blank? @@ -621,7 +645,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase ActionController::Base.logger = @logger begin @controller.stub :valid_authenticity_token?, :true do - post :index, params: { custom_token_name: 'foobar' } + post :index, params: { custom_token_name: "foobar" } assert_equal 0, @logger.logged(:warn).size end ensure @@ -633,7 +657,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase ActionController::Base.logger = @logger begin - post :index, params: { custom_token_name: 'bazqux' } + post :index, params: { custom_token_name: "bazqux" } assert_equal 1, @logger.logged(:warn).size ensure ActionController::Base.logger = @old_logger @@ -642,30 +666,33 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase end class PerFormTokensControllerTest < ActionController::TestCase + def setup + @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token + ActionController::Base.request_forgery_protection_token = :custom_authenticity_token + end + + def teardown + ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token + end + def test_per_form_token_is_same_size_as_global_token get :index expected = ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH - actual = @controller.send(:per_form_csrf_token, session, '/path', 'post').size + actual = @controller.send(:per_form_csrf_token, session, "/path", "post").size assert_equal expected, actual end def test_accepts_token_for_correct_path_and_method get :index - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + form_token = assert_presence_and_fetch_form_csrf_token - actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) - expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') - assert_equal expected, actual + assert_matches_session_token_on_server form_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token} + post :post_one, params: { custom_authenticity_token: form_token } end assert_response :success end @@ -673,40 +700,72 @@ class PerFormTokensControllerTest < ActionController::TestCase def test_rejects_token_for_incorrect_path get :index - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + form_token = assert_presence_and_fetch_form_csrf_token - actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) - expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') - assert_equal expected, actual + assert_matches_session_token_on_server form_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_two' + @request.env["PATH_INFO"] = "/per_form_tokens/post_two" assert_raises(ActionController::InvalidAuthenticityToken) do - post :post_two, params: {custom_authenticity_token: form_token} + post :post_two, params: { custom_authenticity_token: form_token } end end def test_rejects_token_for_incorrect_method get :index - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token + form_token = assert_presence_and_fetch_form_csrf_token + + assert_matches_session_token_on_server form_token + + # This is required because PATH_INFO isn't reset between requests. + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" + assert_raises(ActionController::InvalidAuthenticityToken) do + patch :post_one, params: { custom_authenticity_token: form_token } end + end - actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) - expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') - assert_equal expected, actual + def test_rejects_token_for_incorrect_method_button_to + get :button_to, params: { form_method: "delete" } + + form_token = assert_presence_and_fetch_form_csrf_token + + assert_matches_session_token_on_server form_token, "delete" # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" assert_raises(ActionController::InvalidAuthenticityToken) do - patch :post_one, params: {custom_authenticity_token: form_token} + patch :post_one, params: { custom_authenticity_token: form_token } + end + end + + test "Accepts proper token for implicit post method on button_to tag" do + get :button_to + + form_token = assert_presence_and_fetch_form_csrf_token + + assert_matches_session_token_on_server form_token, "post" + + # This is required because PATH_INFO isn't reset between requests. + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" + assert_nothing_raised do + post :post_one, params: { custom_authenticity_token: form_token } + end + end + + %w{delete post patch}.each do |verb| + test "Accepts proper token for #{verb} method on button_to tag" do + get :button_to, params: { form_method: verb } + + form_token = assert_presence_and_fetch_form_csrf_token + + assert_matches_session_token_on_server form_token, verb + + # This is required because PATH_INFO isn't reset between requests. + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" + assert_nothing_raised do + send verb, :post_one, params: { custom_authenticity_token: form_token } + end end end @@ -716,47 +775,50 @@ class PerFormTokensControllerTest < ActionController::TestCase token = @controller.send(:form_authenticity_token) # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: token} + post :post_one, params: { custom_authenticity_token: token } end assert_response :success end def test_ignores_params - get :index, params: {form_path: '/per_form_tokens/post_one?foo=bar'} + get :index, params: { form_path: "/per_form_tokens/post_one?foo=bar" } - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + form_token = assert_presence_and_fetch_form_csrf_token - actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) - expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') - assert_equal expected, actual + assert_matches_session_token_on_server form_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one?foo=baz' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one?foo=baz" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token, baz: 'foo'} + post :post_one, params: { custom_authenticity_token: form_token, baz: "foo" } end assert_response :success end def test_ignores_trailing_slash_during_generation - get :index, params: {form_path: '/per_form_tokens/post_one/'} + get :index, params: { form_path: "/per_form_tokens/post_one/" } - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token + form_token = assert_presence_and_fetch_form_csrf_token + + # This is required because PATH_INFO isn't reset between requests. + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" + assert_nothing_raised do + post :post_one, params: { custom_authenticity_token: form_token } end + assert_response :success + end + + def test_ignores_origin_during_generation + get :index, params: { form_path: "https://example.com/per_form_tokens/post_one/" } + + form_token = assert_presence_and_fetch_form_csrf_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token} + post :post_one, params: { custom_authenticity_token: form_token } end assert_response :success end @@ -764,34 +826,40 @@ class PerFormTokensControllerTest < ActionController::TestCase def test_ignores_trailing_slash_during_validation get :index - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + form_token = assert_presence_and_fetch_form_csrf_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one/' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one/" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token} + post :post_one, params: { custom_authenticity_token: form_token } end assert_response :success end def test_method_is_case_insensitive - get :index, params: {form_method: "POST"} - - form_token = nil - assert_select 'input[name=custom_authenticity_token]' do |elts| - form_token = elts.first['value'] - assert_not_nil form_token - end + get :index, params: { form_method: "POST" } + form_token = assert_presence_and_fetch_form_csrf_token # This is required because PATH_INFO isn't reset between requests. - @request.env['PATH_INFO'] = '/per_form_tokens/post_one/' + @request.env["PATH_INFO"] = "/per_form_tokens/post_one/" assert_nothing_raised do - post :post_one, params: {custom_authenticity_token: form_token} + post :post_one, params: { custom_authenticity_token: form_token } end assert_response :success end + + private + def assert_presence_and_fetch_form_csrf_token + assert_select 'input[name="custom_authenticity_token"]' do |input| + form_csrf_token = input.first["value"] + assert_not_nil form_csrf_token + return form_csrf_token + end + end + + def assert_matches_session_token_on_server(form_token, method = "post") + actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) + expected = @controller.send(:per_form_csrf_token, session, "/per_form_tokens/post_one", method) + assert_equal expected, actual + end end |