Skip to content

Commit fd6e86c

Browse files
authored
Merge pull request rails#55420 from byroot/allow_redirect_from_configuration_hosts
Allow hosts redirects from `hosts` Rails configuration
2 parents f551712 + acc1124 commit fd6e86c

File tree

5 files changed

+61
-5
lines changed

5 files changed

+61
-5
lines changed

actionpack/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
* Allow hosts redirects from `hosts` Rails configuration
2+
3+
```ruby
4+
config.action_controller.allowed_redirect_hosts << "example.com"
5+
```
6+
7+
*Kevin Robatel*
8+
19
* `rate_limit.action_controller` notification has additional payload
210

311
additonal values: count, to, within, by, name, cache_key

actionpack/lib/action_controller/metal/redirecting.rb

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ class UnsafeRedirectError < StandardError; end
1616
included do
1717
mattr_accessor :raise_on_open_redirects, default: false
1818
mattr_accessor :action_on_path_relative_redirect, default: :log
19+
class_attribute :_allowed_redirect_hosts, :allowed_redirect_hosts_permissions, instance_accessor: false, instance_predicate: false
20+
singleton_class.alias_method :allowed_redirect_hosts, :_allowed_redirect_hosts
21+
end
22+
23+
module ClassMethods # :nodoc:
24+
def allowed_redirect_hosts=(hosts)
25+
hosts = hosts.dup.freeze
26+
self._allowed_redirect_hosts = hosts
27+
self.allowed_redirect_hosts_permissions = if hosts.present?
28+
ActionDispatch::HostAuthorization::Permissions.new(hosts)
29+
end
30+
end
1931
end
2032

2133
# Redirects the browser to the target specified in `options`. This parameter can
@@ -254,12 +266,14 @@ def _enforce_open_redirect_protection(location, allow_other_host:)
254266
end
255267

256268
def _url_host_allowed?(url)
257-
host = URI(url.to_s).host
269+
url_to_s = url.to_s
270+
host = URI(url_to_s).host
258271

259-
return true if host == request.host
260-
return false unless host.nil?
261-
return false unless url.to_s.start_with?("/")
262-
!url.to_s.start_with?("//")
272+
if host.nil?
273+
url_to_s.start_with?("/") && !url_to_s.start_with?("//")
274+
else
275+
host == request.host || self.class.allowed_redirect_hosts_permissions&.allows?(host)
276+
end
263277
rescue ArgumentError, URI::Error
264278
false
265279
end

actionpack/lib/action_controller/railtie.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Railtie < Rails::Railtie # :nodoc:
1616
config.action_controller.action_on_path_relative_redirect = :log
1717
config.action_controller.log_query_tags_around_actions = true
1818
config.action_controller.wrap_parameters_by_default = false
19+
config.action_controller.allowed_redirect_hosts = []
1920

2021
config.eager_load_namespaces << AbstractController
2122
config.eager_load_namespaces << ActionController

actionpack/test/controller/redirect_test.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,26 @@ def test_redirect_to_absolute_url_does_not_raise
815815
ActionController::Base.action_on_path_relative_redirect = old_config
816816
end
817817

818+
def test_redirect_with_allowed_redirect_hosts
819+
with_raise_on_open_redirects do
820+
with_allowed_redirect_hosts(hosts: ["www.rubyonrails.org"]) do
821+
get :redirect_to_url
822+
assert_response :redirect
823+
assert_redirected_to "http://www.rubyonrails.org/"
824+
end
825+
end
826+
end
827+
828+
def test_not_redirect_with_allowed_redirect_hosts
829+
with_raise_on_open_redirects do
830+
with_allowed_redirect_hosts(hosts: ["www.ruby-lang.org"]) do
831+
assert_raise ActionController::Redirecting::UnsafeRedirectError do
832+
get :redirect_to_url
833+
end
834+
end
835+
end
836+
end
837+
818838
private
819839
def with_raise_on_open_redirects
820840
old_raise_on_open_redirects = ActionController::Base.raise_on_open_redirects
@@ -823,6 +843,14 @@ def with_raise_on_open_redirects
823843
ensure
824844
ActionController::Base.raise_on_open_redirects = old_raise_on_open_redirects
825845
end
846+
847+
def with_allowed_redirect_hosts(hosts:)
848+
old_allowed_redirect_hosts = ActionController::Base.allowed_redirect_hosts
849+
ActionController::Base.allowed_redirect_hosts = hosts
850+
yield
851+
ensure
852+
ActionController::Base.allowed_redirect_hosts = old_allowed_redirect_hosts
853+
end
826854
end
827855

828856
module ModuleTest

guides/source/configuring.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,6 +2021,11 @@ The default value depends on the `config.load_defaults` target version:
20212021

20222022
[params_wrapper]: https://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html
20232023

2024+
#### `config.action_controller.allowed_redirect_hosts`
2025+
2026+
Specifies a list of allowed hosts for redirects. `redirect_to` will allow redirects to them without raising an
2027+
`UnsafeRedirectError` error.
2028+
20242029
#### `ActionController::Base.wrap_parameters`
20252030

20262031
Configures the [`ParamsWrapper`](https://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html). This can be called at

0 commit comments

Comments
 (0)