Skip to content
Permalink
Browse files
Remove default match without specified method
In the current router DSL, using the +match+ DSL
method will match all verbs for the path to the
specified endpoint.

In the vast majority of cases, people are
currently using +match+ when they actually mean
+get+. This introduces security implications.

This commit disallows calling +match+ without
an HTTP verb constraint by default. To explicitly
match all verbs, this commit also adds a
:via => :all option to +match+.

Closes #5964
  • Loading branch information
wycats committed Apr 25, 2012
1 parent 0cc32c5 commit 56cdc81c08b1847c5c1f699810a8c3b9ac3715a6
Show file tree
Hide file tree
Showing 57 changed files with 463 additions and 455 deletions.
@@ -59,6 +59,16 @@ def initialize(set, scope, path, options)
@options = (@scope[:options] || {}).merge(options)
@path = normalize_path(path)
normalize_options!

via_all = @options.delete(:via) if @options[:via] == :all

if !via_all && request_method_condition.empty?
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
"If you want to expose your action to GET, use `get` in the router:\n\n" \
" Instead of: match \"controller#action\"\n" \
" Do: get \"controller#action\""
raise msg

This comment has been minimized.

Copy link
@tenderlove

tenderlove Dec 18, 2012

Member

Unfortunately, I found someone using this in the wild. I'll file a ticket up stream, but should we make this a warning?

This comment has been minimized.

Copy link
@steveklabnik

steveklabnik via email Dec 19, 2012

Member

This comment has been minimized.

Copy link
@tenderlove

tenderlove Dec 19, 2012

Member

Ya, the only annoying thing is we never deprecated the behavior, just removed it. Anyway, I sent a PR here.

This comment has been minimized.

Copy link
@steveklabnik

steveklabnik Dec 19, 2012

Member

That's fair. It's in that weird gray zone; a 'feature' that's a big security issue...

end
end

def to_route
@@ -264,7 +274,7 @@ module Base
# of most Rails applications, this is beneficial.
def root(options = {})
options = { :to => options } if options.is_a?(String)
match '/', { :as => :root }.merge(options)
match '/', { :as => :root, :via => :get }.merge(options)
end

# Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -417,7 +427,7 @@ def mount(app, options = nil)

options[:as] ||= app_name(app)

match(path, options.merge(:to => app, :anchor => false, :format => false))
match(path, options.merge(:to => app, :anchor => false, :format => false, :via => :all))

define_generate_prefix(app, options[:as])
self
@@ -125,11 +125,11 @@ class TestCase
# have been loaded.
setup_once do
SharedTestRoutes.draw do
match ':controller(/:action)'
get ':controller(/:action)'
end

ActionDispatch::IntegrationTest.app.routes.draw do
match ':controller(/:action)'
get ':controller(/:action)'
end
end
end
@@ -259,7 +259,7 @@ def test_incoming_invalid_session_id_via_parameter_should_be_ignored
def with_test_route_set(options = {})
with_routing do |set|
set.draw do
match ':action', :to => 'active_record_store_test/test'
get ':action', :to => 'active_record_store_test/test'
end

@app = self.class.build_app(set) do |middleware|
@@ -162,17 +162,17 @@ def test_get_post_request_switch
def test_string_constraint
with_routing do |set|
set.draw do
match "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"}
get "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"}
end
end
end

def test_assert_redirect_to_named_route_failure
with_routing do |set|
set.draw do
match 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
match 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two
match ':controller/:action'
get 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
get 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two
get ':controller/:action'
end
process :redirect_to_named_route
assert_raise(ActiveSupport::TestCase::Assertion) do
@@ -192,8 +192,8 @@ def test_assert_redirect_to_nested_named_route

with_routing do |set|
set.draw do
match 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module
match ':controller/:action'
get 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module
get ':controller/:action'
end
process :redirect_to_index
# redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}>
@@ -206,8 +206,8 @@ def test_assert_redirected_to_top_level_named_route_from_nested_controller

with_routing do |set|
set.draw do
match '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level
match ':controller/:action'
get '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level
get ':controller/:action'
end
process :redirect_to_top_level_named_route
# assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return
@@ -221,8 +221,8 @@ def test_assert_redirected_to_top_level_named_route_with_same_controller_name_in
with_routing do |set|
set.draw do
# this controller exists in the admin namespace as well which is the only difference from previous test
match '/user/:id', :to => 'user#index', :as => :top_level
match ':controller/:action'
get '/user/:id', :to => 'user#index', :as => :top_level
get ':controller/:action'
end
process :redirect_to_top_level_named_route
# assert_redirected_to top_level_url('foo') would pass because of exact match early return
@@ -158,7 +158,7 @@ def setup
def test_url_for_query_params_included
rs = ActionDispatch::Routing::RouteSet.new
rs.draw do
match 'home' => 'pages#home'
get 'home' => 'pages#home'
end

options = {
@@ -174,8 +174,8 @@ def test_url_for_query_params_included
def test_url_options_override
with_routing do |set|
set.draw do
match 'from_view', :to => 'url_options#from_view', :as => :from_view
match ':controller/:action'
get 'from_view', :to => 'url_options#from_view', :as => :from_view
get ':controller/:action'
end

get :from_view, :route => "from_view_url"
@@ -189,7 +189,7 @@ def test_url_options_override
def test_url_helpers_does_not_become_actions
with_routing do |set|
set.draw do
match "account/overview"
get "account/overview"
end

assert !@controller.class.action_methods.include?("account_overview_path")
@@ -208,8 +208,8 @@ def setup
def test_default_url_options_override
with_routing do |set|
set.draw do
match 'from_view', :to => 'default_url_options#from_view', :as => :from_view
match ':controller/:action'
get 'from_view', :to => 'default_url_options#from_view', :as => :from_view
get ':controller/:action'
end

get :from_view, :route => "from_view_url"
@@ -226,7 +226,7 @@ def test_default_url_options_are_used_in_non_positional_parameters
scope("/:locale") do
resources :descriptions
end
match ':controller/:action'
get ':controller/:action'
end

get :from_view, :route => "description_path(1)"
@@ -102,8 +102,8 @@ def teardown
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
with_routing do |set|
set.draw do
match 'posts.:format', :to => 'posts#index', :as => :formatted_posts
match '/', :to => 'posts#index', :as => :main
get 'posts.:format', :to => 'posts#index', :as => :formatted_posts
get '/', :to => 'posts#index', :as => :main
end
@params[:format] = 'rss'
assert_equal '/posts.rss', @routes.url_for(@params)
@@ -560,7 +560,7 @@ def test_forbidden_is_not_cached
def test_xml_version_of_resource_is_treated_as_different_cache
with_routing do |set|
set.draw do
match ':controller(/:action(.:format))'
get ':controller(/:action(.:format))'
end

get :index, :format => 'xml'
@@ -277,7 +277,7 @@ def get(path, parameters = nil, env = {})
def with_test_route_set
with_routing do |set|
set.draw do
match ':action', :to => FlashIntegrationTest::TestController
get ':action', :to => FlashIntegrationTest::TestController
end

@app = self.class.build_app(set) do |middleware|
@@ -466,7 +466,7 @@ def with_test_route_set
end

set.draw do
match ':action', :to => controller
match ':action', :to => controller, :via => [:get, :post]
get 'get/:action', :to => controller
end

@@ -530,10 +530,10 @@ def self.routes
end

routes.draw do
match '', :to => 'application_integration_test/test#index', :as => :empty_string
get '', :to => 'application_integration_test/test#index', :as => :empty_string

match 'foo', :to => 'application_integration_test/test#index', :as => :foo
match 'bar', :to => 'application_integration_test/test#index', :as => :bar
get 'foo', :to => 'application_integration_test/test#index', :as => :foo
get 'bar', :to => 'application_integration_test/test#index', :as => :bar
end

def app
@@ -1118,7 +1118,7 @@ def with_test_route_set
resources :quiz_stores do
resources :customers
end
match ":controller/:action"
get ":controller/:action"
end
yield
end
@@ -43,7 +43,7 @@ class ExplicitContentTypeTest < Rack::TestCase
test "default response is HTML and UTF8" do
with_routing do |set|
set.draw do
match ':controller', :action => 'index'
get ':controller', :action => 'index'
end

get "/content_type/base"
@@ -164,7 +164,7 @@ class TestWithLayout < Rack::TestCase

test "rendering with implicit layout" do
with_routing do |set|
set.draw { match ':controller', :action => :index }
set.draw { get ':controller', :action => :index }

get "/render_template/with_layout"

@@ -57,7 +57,7 @@ class RenderTest < Rack::TestCase
test "render with blank" do
with_routing do |set|
set.draw do
match ":controller", :action => 'index'
get ":controller", :action => 'index'
end

get "/render/blank_render"
@@ -70,7 +70,7 @@ class RenderTest < Rack::TestCase
test "rendering more than once raises an exception" do
with_routing do |set|
set.draw do
match ":controller", :action => 'index'
get ":controller", :action => 'index'
end

assert_raises(AbstractController::DoubleRenderError) do
@@ -67,7 +67,7 @@ class RenderTextTest < Rack::TestCase

test "rendering text from a action with default options renders the text with the layout" do
with_routing do |set|
set.draw { match ':controller', :action => 'index' }
set.draw { get ':controller', :action => 'index' }

get "/render_text/simple"
assert_body "hello david"
@@ -77,7 +77,7 @@ class RenderTextTest < Rack::TestCase

test "rendering text from a action with default options renders the text without the layout" do
with_routing do |set|
set.draw { match ':controller', :action => 'index' }
set.draw { get ':controller', :action => 'index' }

get "/render_text/with_layout"

@@ -262,7 +262,7 @@ def test_redirect_to_record
with_routing do |set|
set.draw do
resources :workshops
match ':controller/:action'
get ':controller/:action'
end

get :redirect_to_existing_record
@@ -296,7 +296,7 @@ def test_redirect_to_with_block_and_assigns
def test_redirect_to_with_block_and_accepted_options
with_routing do |set|
set.draw do
match ':controller/:action'
get ':controller/:action'
end

get :redirect_to_with_block_and_options
@@ -1188,7 +1188,7 @@ def test_head_with_location_object
with_routing do |set|
set.draw do
resources :customers
match ':controller/:action'
get ':controller/:action'
end

get :head_with_location_object
@@ -72,7 +72,7 @@ def test_rendering_with_object_location_should_set_header_with_url_for
with_routing do |set|
set.draw do
resources :customers
match ':controller/:action'
get ':controller/:action'
end

get :render_with_object_location
@@ -338,9 +338,9 @@ def show_errors(exception)
def with_test_routing
with_routing do |set|
set.draw do
match 'foo', :to => ::RescueTest::TestController.action(:foo)
match 'invalid', :to => ::RescueTest::TestController.action(:invalid)
match 'b00m', :to => ::RescueTest::TestController.action(:b00m)
get 'foo', :to => ::RescueTest::TestController.action(:foo)
get 'invalid', :to => ::RescueTest::TestController.action(:invalid)
get 'b00m', :to => ::RescueTest::TestController.action(:b00m)
end
yield
end
@@ -680,7 +680,7 @@ def test_new_style_named_routes_for_resource
scope '/threads/:thread_id' do
resources :messages, :as => 'thread_messages' do
get :search, :on => :collection
match :preview, :on => :new
get :preview, :on => :new
end
end
end
@@ -698,7 +698,7 @@ def test_new_style_named_routes_for_singleton_resource
scope '/admin' do
resource :account, :as => :admin_account do
get :login, :on => :member
match :preview, :on => :new
get :preview, :on => :new
end
end
end

2 comments on commit 56cdc81

@homakov
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kudos! Very appreciate your help!

@radar
Copy link
Contributor

@radar radar commented on 56cdc81 May 8, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much!

Please sign in to comment.