Skip to content

Commit 1bc98b1

Browse files
committed
Merge pull request rails#23752 from vipulnsward/23524-fix-button_to_delete
Fixed passing of delete method on button_to tag, creating wrong form csrf token
2 parents aa3d440 + 2b4c0ae commit 1bc98b1

File tree

2 files changed

+61
-49
lines changed

2 files changed

+61
-49
lines changed

actionpack/test/controller/request_forgery_protection_test.rb

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ def index
136136
render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: (params[:form_method] || :post) %>"
137137
end
138138

139+
def button_to
140+
render inline: "<%= button_to 'Button', (params[:form_path] || '/per_form_tokens/post_one'), method: (params[:form_method] || :post) %>"
141+
end
142+
139143
def post_one
140144
render plain: ''
141145
end
@@ -652,15 +656,9 @@ def test_per_form_token_is_same_size_as_global_token
652656
def test_accepts_token_for_correct_path_and_method
653657
get :index
654658

655-
form_token = nil
656-
assert_select 'input[name=custom_authenticity_token]' do |elts|
657-
form_token = elts.first['value']
658-
assert_not_nil form_token
659-
end
659+
form_token = assert_presence_and_fetch_form_csrf_token
660660

661-
actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
662-
expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
663-
assert_equal expected, actual
661+
assert_matches_session_token_on_server form_token
664662

665663
# This is required because PATH_INFO isn't reset between requests.
666664
@request.env['PATH_INFO'] = '/per_form_tokens/post_one'
@@ -673,15 +671,9 @@ def test_accepts_token_for_correct_path_and_method
673671
def test_rejects_token_for_incorrect_path
674672
get :index
675673

676-
form_token = nil
677-
assert_select 'input[name=custom_authenticity_token]' do |elts|
678-
form_token = elts.first['value']
679-
assert_not_nil form_token
680-
end
674+
form_token = assert_presence_and_fetch_form_csrf_token
681675

682-
actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
683-
expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
684-
assert_equal expected, actual
676+
assert_matches_session_token_on_server form_token
685677

686678
# This is required because PATH_INFO isn't reset between requests.
687679
@request.env['PATH_INFO'] = '/per_form_tokens/post_two'
@@ -693,15 +685,9 @@ def test_rejects_token_for_incorrect_path
693685
def test_rejects_token_for_incorrect_method
694686
get :index
695687

696-
form_token = nil
697-
assert_select 'input[name=custom_authenticity_token]' do |elts|
698-
form_token = elts.first['value']
699-
assert_not_nil form_token
700-
end
688+
form_token = assert_presence_and_fetch_form_csrf_token
701689

702-
actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
703-
expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
704-
assert_equal expected, actual
690+
assert_matches_session_token_on_server form_token
705691

706692
# This is required because PATH_INFO isn't reset between requests.
707693
@request.env['PATH_INFO'] = '/per_form_tokens/post_one'
@@ -710,6 +696,36 @@ def test_rejects_token_for_incorrect_method
710696
end
711697
end
712698

699+
def test_rejects_token_for_incorrect_method_button_to
700+
get :button_to, params: { form_method: 'delete' }
701+
702+
form_token = assert_presence_and_fetch_form_csrf_token
703+
704+
assert_matches_session_token_on_server form_token, 'delete'
705+
706+
# This is required because PATH_INFO isn't reset between requests.
707+
@request.env['PATH_INFO'] = '/per_form_tokens/post_one'
708+
assert_raises(ActionController::InvalidAuthenticityToken) do
709+
patch :post_one, params: { custom_authenticity_token: form_token }
710+
end
711+
end
712+
713+
%w{delete post patch}.each do |verb|
714+
test "Accepts proper token for #{verb} method on button_to tag" do
715+
get :button_to, params: { form_method: verb }
716+
717+
form_token = assert_presence_and_fetch_form_csrf_token
718+
719+
assert_matches_session_token_on_server form_token, verb
720+
721+
# This is required because PATH_INFO isn't reset between requests.
722+
@request.env['PATH_INFO'] = '/per_form_tokens/post_one'
723+
assert_nothing_raised do
724+
send verb, :post_one, params: { custom_authenticity_token: form_token }
725+
end
726+
end
727+
end
728+
713729
def test_accepts_global_csrf_token
714730
get :index
715731

@@ -726,15 +742,9 @@ def test_accepts_global_csrf_token
726742
def test_ignores_params
727743
get :index, params: {form_path: '/per_form_tokens/post_one?foo=bar'}
728744

729-
form_token = nil
730-
assert_select 'input[name=custom_authenticity_token]' do |elts|
731-
form_token = elts.first['value']
732-
assert_not_nil form_token
733-
end
745+
form_token = assert_presence_and_fetch_form_csrf_token
734746

735-
actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
736-
expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post')
737-
assert_equal expected, actual
747+
assert_matches_session_token_on_server form_token
738748

739749
# This is required because PATH_INFO isn't reset between requests.
740750
@request.env['PATH_INFO'] = '/per_form_tokens/post_one?foo=baz'
@@ -747,11 +757,7 @@ def test_ignores_params
747757
def test_ignores_trailing_slash_during_generation
748758
get :index, params: {form_path: '/per_form_tokens/post_one/'}
749759

750-
form_token = nil
751-
assert_select 'input[name=custom_authenticity_token]' do |elts|
752-
form_token = elts.first['value']
753-
assert_not_nil form_token
754-
end
760+
form_token = assert_presence_and_fetch_form_csrf_token
755761

756762
# This is required because PATH_INFO isn't reset between requests.
757763
@request.env['PATH_INFO'] = '/per_form_tokens/post_one'
@@ -764,11 +770,7 @@ def test_ignores_trailing_slash_during_generation
764770
def test_ignores_trailing_slash_during_validation
765771
get :index
766772

767-
form_token = nil
768-
assert_select 'input[name=custom_authenticity_token]' do |elts|
769-
form_token = elts.first['value']
770-
assert_not_nil form_token
771-
end
773+
form_token = assert_presence_and_fetch_form_csrf_token
772774

773775
# This is required because PATH_INFO isn't reset between requests.
774776
@request.env['PATH_INFO'] = '/per_form_tokens/post_one/'
@@ -781,17 +783,27 @@ def test_ignores_trailing_slash_during_validation
781783
def test_method_is_case_insensitive
782784
get :index, params: {form_method: "POST"}
783785

784-
form_token = nil
785-
assert_select 'input[name=custom_authenticity_token]' do |elts|
786-
form_token = elts.first['value']
787-
assert_not_nil form_token
788-
end
789-
786+
form_token = assert_presence_and_fetch_form_csrf_token
790787
# This is required because PATH_INFO isn't reset between requests.
791788
@request.env['PATH_INFO'] = '/per_form_tokens/post_one/'
792789
assert_nothing_raised do
793790
post :post_one, params: {custom_authenticity_token: form_token}
794791
end
795792
assert_response :success
796793
end
794+
795+
private
796+
def assert_presence_and_fetch_form_csrf_token
797+
assert_select 'input[name="custom_authenticity_token"]' do |input|
798+
form_csrf_token = input.first['value']
799+
assert_not_nil form_csrf_token
800+
return form_csrf_token
801+
end
802+
end
803+
804+
def assert_matches_session_token_on_server(form_token, method = 'post')
805+
actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token))
806+
expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', method)
807+
assert_equal expected, actual
808+
end
797809
end

actionview/lib/action_view/helpers/url_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def button_to(name = nil, options = nil, html_options = nil, &block)
312312
form_options[:'data-remote'] = true if remote
313313

314314
request_token_tag = if form_method == 'post'
315-
token_tag(nil, form_options: form_options)
315+
token_tag(nil, form_options: form_options.merge(method: method))
316316
else
317317
''
318318
end

0 commit comments

Comments
 (0)