actionpack 7.1.5.1 → 8.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +308 -523
- data/README.rdoc +1 -1
- data/lib/abstract_controller/asset_paths.rb +6 -2
- data/lib/abstract_controller/base.rb +104 -105
- data/lib/abstract_controller/caching/fragments.rb +50 -53
- data/lib/abstract_controller/caching.rb +8 -3
- data/lib/abstract_controller/callbacks.rb +70 -62
- data/lib/abstract_controller/collector.rb +7 -7
- data/lib/abstract_controller/deprecator.rb +2 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +71 -84
- data/lib/abstract_controller/logger.rb +4 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +13 -13
- data/lib/abstract_controller/translation.rb +12 -13
- data/lib/abstract_controller/url_for.rb +8 -6
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api/api_rendering.rb +2 -0
- data/lib/action_controller/api.rb +76 -72
- data/lib/action_controller/base.rb +199 -126
- data/lib/action_controller/caching.rb +16 -14
- data/lib/action_controller/deprecator.rb +2 -0
- data/lib/action_controller/form_builder.rb +21 -18
- data/lib/action_controller/log_subscriber.rb +23 -2
- data/lib/action_controller/metal/allow_browser.rb +133 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
- data/lib/action_controller/metal/conditional_get.rb +217 -175
- data/lib/action_controller/metal/content_security_policy.rb +25 -24
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +72 -63
- data/lib/action_controller/metal/default_headers.rb +5 -3
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
- data/lib/action_controller/metal/exceptions.rb +16 -9
- data/lib/action_controller/metal/flash.rb +13 -14
- data/lib/action_controller/metal/head.rb +15 -11
- data/lib/action_controller/metal/helpers.rb +63 -55
- data/lib/action_controller/metal/http_authentication.rb +209 -201
- data/lib/action_controller/metal/implicit_render.rb +17 -15
- data/lib/action_controller/metal/instrumentation.rb +16 -14
- data/lib/action_controller/metal/live.rb +177 -128
- data/lib/action_controller/metal/logging.rb +6 -4
- data/lib/action_controller/metal/mime_responds.rb +151 -142
- data/lib/action_controller/metal/parameter_encoding.rb +34 -32
- data/lib/action_controller/metal/params_wrapper.rb +57 -59
- data/lib/action_controller/metal/permissions_policy.rb +22 -12
- data/lib/action_controller/metal/rate_limiting.rb +92 -0
- data/lib/action_controller/metal/redirecting.rb +213 -94
- data/lib/action_controller/metal/renderers.rb +78 -57
- data/lib/action_controller/metal/rendering.rb +111 -77
- data/lib/action_controller/metal/request_forgery_protection.rb +182 -143
- data/lib/action_controller/metal/rescue.rb +20 -9
- data/lib/action_controller/metal/streaming.rb +118 -195
- data/lib/action_controller/metal/strong_parameters.rb +720 -530
- data/lib/action_controller/metal/testing.rb +2 -0
- data/lib/action_controller/metal/url_for.rb +17 -15
- data/lib/action_controller/metal.rb +86 -60
- data/lib/action_controller/railtie.rb +36 -15
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +41 -36
- data/lib/action_controller/structured_event_subscriber.rb +116 -0
- data/lib/action_controller/template_assertions.rb +4 -2
- data/lib/action_controller/test_case.rb +160 -131
- data/lib/action_controller.rb +5 -1
- data/lib/action_dispatch/constants.rb +8 -0
- data/lib/action_dispatch/deprecator.rb +2 -0
- data/lib/action_dispatch/http/cache.rb +163 -35
- data/lib/action_dispatch/http/content_disposition.rb +2 -0
- data/lib/action_dispatch/http/content_security_policy.rb +54 -39
- data/lib/action_dispatch/http/filter_parameters.rb +14 -8
- data/lib/action_dispatch/http/filter_redirect.rb +22 -1
- data/lib/action_dispatch/http/headers.rb +22 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +89 -41
- data/lib/action_dispatch/http/mime_type.rb +25 -21
- data/lib/action_dispatch/http/mime_types.rb +3 -0
- data/lib/action_dispatch/http/param_builder.rb +187 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/parameters.rb +14 -12
- data/lib/action_dispatch/http/permissions_policy.rb +25 -36
- data/lib/action_dispatch/http/query_parser.rb +55 -0
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +141 -92
- data/lib/action_dispatch/http/response.rb +137 -77
- data/lib/action_dispatch/http/upload.rb +18 -16
- data/lib/action_dispatch/http/url.rb +187 -89
- data/lib/action_dispatch/journey/formatter.rb +21 -9
- data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +34 -11
- data/lib/action_dispatch/journey/gtg/transition_table.rb +47 -53
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
- data/lib/action_dispatch/journey/nodes/node.rb +8 -6
- data/lib/action_dispatch/journey/parser.rb +99 -195
- data/lib/action_dispatch/journey/path/pattern.rb +4 -1
- data/lib/action_dispatch/journey/route.rb +54 -38
- data/lib/action_dispatch/journey/router/utils.rb +22 -27
- data/lib/action_dispatch/journey/router.rb +63 -83
- data/lib/action_dispatch/journey/routes.rb +11 -2
- data/lib/action_dispatch/journey/scanner.rb +46 -42
- data/lib/action_dispatch/journey/visitors.rb +57 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/journey.rb +2 -0
- data/lib/action_dispatch/log_subscriber.rb +7 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
- data/lib/action_dispatch/middleware/callbacks.rb +3 -1
- data/lib/action_dispatch/middleware/cookies.rb +125 -106
- data/lib/action_dispatch/middleware/debug_exceptions.rb +37 -8
- data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
- data/lib/action_dispatch/middleware/debug_view.rb +13 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +18 -23
- data/lib/action_dispatch/middleware/executor.rb +19 -4
- data/lib/action_dispatch/middleware/flash.rb +63 -51
- data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
- data/lib/action_dispatch/middleware/public_exceptions.rb +14 -12
- data/lib/action_dispatch/middleware/reloader.rb +5 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +87 -77
- data/lib/action_dispatch/middleware/request_id.rb +16 -10
- data/lib/action_dispatch/middleware/server_timing.rb +4 -2
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +30 -8
- data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +16 -16
- data/lib/action_dispatch/middleware/ssl.rb +53 -40
- data/lib/action_dispatch/middleware/stack.rb +11 -10
- data/lib/action_dispatch/middleware/static.rb +33 -31
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
- data/lib/action_dispatch/railtie.rb +23 -3
- data/lib/action_dispatch/request/session.rb +24 -21
- data/lib/action_dispatch/request/utils.rb +11 -3
- data/lib/action_dispatch/routing/endpoint.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +85 -60
- data/lib/action_dispatch/routing/mapper.rb +1031 -851
- data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
- data/lib/action_dispatch/routing/redirection.rb +47 -39
- data/lib/action_dispatch/routing/route_set.rb +79 -56
- data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
- data/lib/action_dispatch/routing/url_for.rb +130 -125
- data/lib/action_dispatch/routing.rb +150 -148
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_test_case.rb +91 -81
- data/lib/action_dispatch/system_testing/browser.rb +16 -23
- data/lib/action_dispatch/system_testing/driver.rb +2 -0
- data/lib/action_dispatch/system_testing/server.rb +2 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +34 -23
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
- data/lib/action_dispatch/testing/assertion_response.rb +9 -7
- data/lib/action_dispatch/testing/assertions/response.rb +52 -25
- data/lib/action_dispatch/testing/assertions/routing.rb +168 -87
- data/lib/action_dispatch/testing/assertions.rb +2 -0
- data/lib/action_dispatch/testing/integration.rb +233 -223
- data/lib/action_dispatch/testing/request_encoder.rb +11 -9
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +11 -8
- data/lib/action_dispatch/testing/test_request.rb +3 -1
- data/lib/action_dispatch/testing/test_response.rb +27 -26
- data/lib/action_dispatch.rb +36 -32
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +17 -16
- metadata +36 -32
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -31
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
module ActionController
|
|
4
6
|
module Redirecting
|
|
5
7
|
extend ActiveSupport::Concern
|
|
@@ -9,140 +11,212 @@ module ActionController
|
|
|
9
11
|
|
|
10
12
|
class UnsafeRedirectError < StandardError; end
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
class OpenRedirectError < UnsafeRedirectError
|
|
15
|
+
def initialize(location)
|
|
16
|
+
super("Unsafe redirect to #{location.to_s.truncate(100).inspect}, pass allow_other_host: true to redirect anyway.")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class PathRelativeRedirectError < UnsafeRedirectError
|
|
21
|
+
def initialize(url)
|
|
22
|
+
super("Path relative URL redirect detected: #{url.inspect}")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/
|
|
13
27
|
|
|
14
28
|
included do
|
|
15
29
|
mattr_accessor :raise_on_open_redirects, default: false
|
|
30
|
+
mattr_accessor :action_on_open_redirect, default: :log
|
|
31
|
+
mattr_accessor :action_on_path_relative_redirect, default: :log
|
|
32
|
+
class_attribute :_allowed_redirect_hosts, :allowed_redirect_hosts_permissions, instance_accessor: false, instance_predicate: false
|
|
33
|
+
singleton_class.alias_method :allowed_redirect_hosts, :_allowed_redirect_hosts
|
|
16
34
|
end
|
|
17
35
|
|
|
18
|
-
|
|
36
|
+
module ClassMethods # :nodoc:
|
|
37
|
+
def allowed_redirect_hosts=(hosts)
|
|
38
|
+
hosts = hosts.dup.freeze
|
|
39
|
+
self._allowed_redirect_hosts = hosts
|
|
40
|
+
self.allowed_redirect_hosts_permissions = if hosts.present?
|
|
41
|
+
ActionDispatch::HostAuthorization::Permissions.new(hosts)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Redirects the browser to the target specified in `options`. This parameter can
|
|
47
|
+
# be any one of:
|
|
48
|
+
#
|
|
49
|
+
# * `Hash` - The URL will be generated by calling url_for with the `options`.
|
|
50
|
+
# * `Record` - The URL will be generated by calling url_for with the
|
|
51
|
+
# `options`, which will reference a named URL for that record.
|
|
52
|
+
# * `String` starting with `protocol://` (like `http://`) or a protocol
|
|
53
|
+
# relative reference (like `//`) - Is passed straight through as the target
|
|
54
|
+
# for redirection.
|
|
55
|
+
# * `String` not containing a protocol - The current protocol and host is
|
|
56
|
+
# prepended to the string.
|
|
57
|
+
# * `Proc` - A block that will be executed in the controller's context. Should
|
|
58
|
+
# return any option accepted by `redirect_to`.
|
|
19
59
|
#
|
|
20
|
-
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
|
21
|
-
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
|
|
22
|
-
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
|
|
23
|
-
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
|
|
24
|
-
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
|
|
25
60
|
#
|
|
26
|
-
#
|
|
61
|
+
# ### Examples
|
|
27
62
|
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
63
|
+
# redirect_to action: "show", id: 5
|
|
64
|
+
# redirect_to @post
|
|
65
|
+
# redirect_to "http://www.rubyonrails.org"
|
|
66
|
+
# redirect_to "/images/screenshot.jpg"
|
|
67
|
+
# redirect_to posts_url
|
|
68
|
+
# redirect_to proc { edit_post_url(@post) }
|
|
34
69
|
#
|
|
35
|
-
# The redirection happens as a
|
|
70
|
+
# The redirection happens as a `302 Found` header unless otherwise specified
|
|
71
|
+
# using the `:status` option:
|
|
36
72
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
73
|
+
# redirect_to post_url(@post), status: :found
|
|
74
|
+
# redirect_to action: 'atom', status: :moved_permanently
|
|
75
|
+
# redirect_to post_url(@post), status: 301
|
|
76
|
+
# redirect_to action: 'atom', status: 302
|
|
41
77
|
#
|
|
42
|
-
# The status code can either be a standard
|
|
43
|
-
# integer, or a
|
|
44
|
-
#
|
|
78
|
+
# The status code can either be a standard [HTTP Status
|
|
79
|
+
# code](https://www.iana.org/assignments/http-status-codes) as an integer, or a
|
|
80
|
+
# symbol representing the downcased, underscored and symbolized description.
|
|
81
|
+
# Note that the status code must be a 3xx HTTP code, or redirection will not
|
|
82
|
+
# occur.
|
|
45
83
|
#
|
|
46
84
|
# If you are using XHR requests other than GET or POST and redirecting after the
|
|
47
85
|
# request then some browsers will follow the redirect using the original request
|
|
48
86
|
# method. This may lead to undesirable behavior such as a double DELETE. To work
|
|
49
|
-
# around this you can return a
|
|
87
|
+
# around this you can return a `303 See Other` status code which will be
|
|
50
88
|
# followed using a GET request.
|
|
51
89
|
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
90
|
+
# redirect_to posts_url, status: :see_other
|
|
91
|
+
# redirect_to action: 'index', status: 303
|
|
54
92
|
#
|
|
55
|
-
# It is also possible to assign a flash message as part of the redirection.
|
|
56
|
-
#
|
|
93
|
+
# It is also possible to assign a flash message as part of the redirection.
|
|
94
|
+
# There are two special accessors for the commonly used flash names `alert` and
|
|
95
|
+
# `notice` as well as a general purpose `flash` bucket.
|
|
57
96
|
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
97
|
+
# redirect_to post_url(@post), alert: "Watch it, mister!"
|
|
98
|
+
# redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
|
|
99
|
+
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
|
|
100
|
+
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
|
|
62
101
|
#
|
|
63
|
-
# Statements after
|
|
64
|
-
#
|
|
102
|
+
# Statements after `redirect_to` in our controller get executed, so
|
|
103
|
+
# `redirect_to` doesn't stop the execution of the function. To terminate the
|
|
104
|
+
# execution of the function immediately after the `redirect_to`, use return.
|
|
65
105
|
#
|
|
66
|
-
#
|
|
106
|
+
# redirect_to post_url(@post) and return
|
|
67
107
|
#
|
|
68
|
-
#
|
|
108
|
+
# ### Open Redirect protection
|
|
69
109
|
#
|
|
70
|
-
# By default,
|
|
71
|
-
#
|
|
110
|
+
# By default, Rails protects against redirecting to external hosts for your
|
|
111
|
+
# app's safety, so called open redirects.
|
|
72
112
|
#
|
|
73
113
|
# Here #redirect_to automatically validates the potentially-unsafe URL:
|
|
74
114
|
#
|
|
75
|
-
#
|
|
115
|
+
# redirect_to params[:redirect_url]
|
|
116
|
+
#
|
|
117
|
+
# The `action_on_open_redirect` configuration option controls the behavior when an unsafe
|
|
118
|
+
# redirect is detected:
|
|
119
|
+
# * `:log` - Logs a warning but allows the redirect
|
|
120
|
+
# * `:notify` - Sends an Active Support notification for monitoring
|
|
121
|
+
# * `:raise` - Raises an UnsafeRedirectError
|
|
122
|
+
#
|
|
123
|
+
# To allow any external redirects pass `allow_other_host: true`, though using a
|
|
124
|
+
# user-provided param in that case is unsafe.
|
|
125
|
+
#
|
|
126
|
+
# redirect_to "https://rubyonrails.org", allow_other_host: true
|
|
76
127
|
#
|
|
77
|
-
#
|
|
128
|
+
# See #url_from for more information on what an internal and safe URL is, or how
|
|
129
|
+
# to fall back to an alternate redirect URL in the unsafe case.
|
|
78
130
|
#
|
|
79
|
-
#
|
|
131
|
+
# ### Path Relative URL Redirect Protection
|
|
80
132
|
#
|
|
81
|
-
#
|
|
133
|
+
# Rails also protects against potentially unsafe path relative URL redirects that don't
|
|
134
|
+
# start with a leading slash. These can create security vulnerabilities:
|
|
82
135
|
#
|
|
83
|
-
#
|
|
136
|
+
# redirect_to "example.com" # Creates http://yourdomain.comexample.com
|
|
137
|
+
# redirect_to "@attacker.com" # Creates http://yourdomain.com@attacker.com
|
|
138
|
+
# # which browsers interpret as user@host
|
|
139
|
+
#
|
|
140
|
+
# You can configure how Rails handles these cases using:
|
|
141
|
+
#
|
|
142
|
+
# config.action_controller.action_on_path_relative_redirect = :log # default
|
|
143
|
+
# config.action_controller.action_on_path_relative_redirect = :notify
|
|
144
|
+
# config.action_controller.action_on_path_relative_redirect = :raise
|
|
145
|
+
#
|
|
146
|
+
# * `:log` - Logs a warning but allows the redirect
|
|
147
|
+
# * `:notify` - Sends an Active Support notification but allows the redirect
|
|
148
|
+
# (includes stack trace to help identify the source)
|
|
149
|
+
# * `:raise` - Raises an UnsafeRedirectError
|
|
84
150
|
def redirect_to(options = {}, response_options = {})
|
|
85
151
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
|
86
152
|
raise AbstractController::DoubleRenderError if response_body
|
|
87
153
|
|
|
88
|
-
allow_other_host = response_options.delete(:allow_other_host)
|
|
154
|
+
allow_other_host = response_options.delete(:allow_other_host)
|
|
89
155
|
|
|
90
|
-
|
|
156
|
+
proposed_status = _extract_redirect_to_status(options, response_options)
|
|
91
157
|
|
|
92
158
|
redirect_to_location = _compute_redirect_to_location(request, options)
|
|
93
159
|
_ensure_url_is_http_header_safe(redirect_to_location)
|
|
94
160
|
|
|
95
161
|
self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
|
|
96
162
|
self.response_body = ""
|
|
163
|
+
self.status = proposed_status
|
|
97
164
|
end
|
|
98
165
|
|
|
99
|
-
# Soft deprecated alias for #redirect_back_or_to where the
|
|
100
|
-
# of the first positional
|
|
166
|
+
# Soft deprecated alias for #redirect_back_or_to where the `fallback_location`
|
|
167
|
+
# location is supplied as a keyword argument instead of the first positional
|
|
168
|
+
# argument.
|
|
101
169
|
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
|
|
102
170
|
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
|
|
103
171
|
end
|
|
104
172
|
|
|
105
|
-
# Redirects the browser to the page that issued the request (the referrer)
|
|
106
|
-
#
|
|
107
|
-
#
|
|
173
|
+
# Redirects the browser to the page that issued the request (the referrer) if
|
|
174
|
+
# possible, otherwise redirects to the provided default fallback location.
|
|
175
|
+
#
|
|
176
|
+
# The referrer information is pulled from the HTTP `Referer` (sic) header on the
|
|
177
|
+
# request. This is an optional header and its presence on the request is subject
|
|
178
|
+
# to browser security settings and user preferences. If the request is missing
|
|
179
|
+
# this header, the `fallback_location` will be used.
|
|
108
180
|
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
181
|
+
# redirect_back_or_to({ action: "show", id: 5 })
|
|
182
|
+
# redirect_back_or_to @post
|
|
183
|
+
# redirect_back_or_to "http://www.rubyonrails.org"
|
|
184
|
+
# redirect_back_or_to "/images/screenshot.jpg"
|
|
185
|
+
# redirect_back_or_to posts_url
|
|
186
|
+
# redirect_back_or_to proc { edit_post_url(@post) }
|
|
187
|
+
# redirect_back_or_to '/', allow_other_host: false
|
|
113
188
|
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
# redirect_back_or_to "/images/screenshot.jpg"
|
|
118
|
-
# redirect_back_or_to posts_url
|
|
119
|
-
# redirect_back_or_to proc { edit_post_url(@post) }
|
|
120
|
-
# redirect_back_or_to '/', allow_other_host: false
|
|
189
|
+
# #### Options
|
|
190
|
+
# * `:allow_other_host` - Allow or disallow redirection to the host that is
|
|
191
|
+
# different to the current host, defaults to true.
|
|
121
192
|
#
|
|
122
|
-
# ==== Options
|
|
123
|
-
# * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
|
|
124
193
|
#
|
|
125
|
-
# All other options that can be passed to #redirect_to are accepted as
|
|
126
|
-
#
|
|
194
|
+
# All other options that can be passed to #redirect_to are accepted as options,
|
|
195
|
+
# and the behavior is identical.
|
|
127
196
|
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
|
|
128
197
|
if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
|
|
129
198
|
redirect_to request.referer, allow_other_host: allow_other_host, **options
|
|
130
199
|
else
|
|
131
|
-
# The method level `allow_other_host` doesn't apply in the fallback case, omit
|
|
200
|
+
# The method level `allow_other_host` doesn't apply in the fallback case, omit
|
|
201
|
+
# and let the `redirect_to` handling take over.
|
|
132
202
|
redirect_to fallback_location, **options
|
|
133
203
|
end
|
|
134
204
|
end
|
|
135
205
|
|
|
136
206
|
def _compute_redirect_to_location(request, options) # :nodoc:
|
|
137
207
|
case options
|
|
138
|
-
# The scheme name consist of a letter followed by any combination of
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
208
|
+
# The scheme name consist of a letter followed by any combination of letters,
|
|
209
|
+
# digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is
|
|
210
|
+
# terminated by a colon (":"). See
|
|
211
|
+
# https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme
|
|
212
|
+
# starts with a double slash "//".
|
|
143
213
|
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
|
|
144
214
|
options.to_str
|
|
145
215
|
when String
|
|
216
|
+
if !options.start_with?("/", "?") && !options.empty?
|
|
217
|
+
_handle_path_relative_redirect(options)
|
|
218
|
+
end
|
|
219
|
+
|
|
146
220
|
request.protocol + request.host_with_port + options
|
|
147
221
|
when Proc
|
|
148
222
|
_compute_redirect_to_location request, instance_eval(&options)
|
|
@@ -153,25 +227,30 @@ module ActionController
|
|
|
153
227
|
module_function :_compute_redirect_to_location
|
|
154
228
|
public :_compute_redirect_to_location
|
|
155
229
|
|
|
156
|
-
# Verifies the passed
|
|
157
|
-
#
|
|
230
|
+
# Verifies the passed `location` is an internal URL that's safe to redirect to
|
|
231
|
+
# and returns it, or nil if not. Useful to wrap a params provided redirect URL
|
|
232
|
+
# and fall back to an alternate URL to redirect to:
|
|
158
233
|
#
|
|
159
|
-
#
|
|
234
|
+
# redirect_to url_from(params[:redirect_url]) || root_url
|
|
160
235
|
#
|
|
161
|
-
# The
|
|
236
|
+
# The `location` is considered internal, and safe, if it's on the same host as
|
|
237
|
+
# `request.host`:
|
|
162
238
|
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
239
|
+
# # If request.host is example.com:
|
|
240
|
+
# url_from("https://example.com/profile") # => "https://example.com/profile"
|
|
241
|
+
# url_from("http://example.com/profile") # => "http://example.com/profile"
|
|
242
|
+
# url_from("http://evil.com/profile") # => nil
|
|
167
243
|
#
|
|
168
244
|
# Subdomains are considered part of the host:
|
|
169
245
|
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
246
|
+
# # If request.host is on https://example.com or https://app.example.com, you'd get:
|
|
247
|
+
# url_from("https://dev.example.com/profile") # => nil
|
|
172
248
|
#
|
|
173
|
-
# NOTE: there's a similarity with
|
|
174
|
-
#
|
|
249
|
+
# NOTE: there's a similarity with
|
|
250
|
+
# [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor#url_for), which generates
|
|
251
|
+
# an internal URL from various options from within the app, e.g.
|
|
252
|
+
# `url_for(@post)`. However, #url_from is meant to take an external parameter to
|
|
253
|
+
# verify as in `url_from(params[:redirect_url])`.
|
|
175
254
|
def url_from(location)
|
|
176
255
|
location = location.presence
|
|
177
256
|
location if location && _url_host_allowed?(location)
|
|
@@ -179,47 +258,87 @@ module ActionController
|
|
|
179
258
|
|
|
180
259
|
private
|
|
181
260
|
def _allow_other_host
|
|
182
|
-
|
|
261
|
+
return false if raise_on_open_redirects
|
|
262
|
+
|
|
263
|
+
action_on_open_redirect != :raise
|
|
183
264
|
end
|
|
184
265
|
|
|
185
266
|
def _extract_redirect_to_status(options, response_options)
|
|
186
267
|
if options.is_a?(Hash) && options.key?(:status)
|
|
187
|
-
|
|
268
|
+
ActionDispatch::Response.rack_status_code(options.delete(:status))
|
|
188
269
|
elsif response_options.key?(:status)
|
|
189
|
-
|
|
270
|
+
ActionDispatch::Response.rack_status_code(response_options[:status])
|
|
190
271
|
else
|
|
191
272
|
302
|
|
192
273
|
end
|
|
193
274
|
end
|
|
194
275
|
|
|
195
276
|
def _enforce_open_redirect_protection(location, allow_other_host:)
|
|
277
|
+
# Explictly allowed other host or host is in allow list allow redirect
|
|
196
278
|
if allow_other_host || _url_host_allowed?(location)
|
|
197
279
|
location
|
|
280
|
+
# Explicitly disallowed other host
|
|
281
|
+
elsif allow_other_host == false
|
|
282
|
+
raise OpenRedirectError.new(location)
|
|
283
|
+
# Configuration disallows other hosts
|
|
284
|
+
elsif !_allow_other_host
|
|
285
|
+
raise OpenRedirectError.new(location)
|
|
286
|
+
# Log but allow redirect
|
|
287
|
+
elsif action_on_open_redirect == :log
|
|
288
|
+
logger.warn "Open redirect to #{location.inspect} detected" if logger
|
|
289
|
+
location
|
|
290
|
+
# Notify but allow redirect
|
|
291
|
+
elsif action_on_open_redirect == :notify
|
|
292
|
+
ActiveSupport::Notifications.instrument("open_redirect.action_controller",
|
|
293
|
+
location: location,
|
|
294
|
+
request: request,
|
|
295
|
+
stack_trace: caller,
|
|
296
|
+
)
|
|
297
|
+
location
|
|
298
|
+
# Fall through, should not happen but raise for safety
|
|
198
299
|
else
|
|
199
|
-
raise
|
|
300
|
+
raise OpenRedirectError.new(location)
|
|
200
301
|
end
|
|
201
302
|
end
|
|
202
303
|
|
|
203
304
|
def _url_host_allowed?(url)
|
|
204
|
-
|
|
305
|
+
url_to_s = url.to_s
|
|
306
|
+
host = URI(url_to_s).host
|
|
205
307
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
308
|
+
if host.nil?
|
|
309
|
+
url_to_s.start_with?("/") && !url_to_s.start_with?("//")
|
|
310
|
+
else
|
|
311
|
+
host == request.host || self.class.allowed_redirect_hosts_permissions&.allows?(host)
|
|
312
|
+
end
|
|
210
313
|
rescue ArgumentError, URI::Error
|
|
211
314
|
false
|
|
212
315
|
end
|
|
213
316
|
|
|
214
317
|
def _ensure_url_is_http_header_safe(url)
|
|
215
|
-
# Attempt to comply with the set of valid token characters
|
|
216
|
-
#
|
|
217
|
-
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
|
318
|
+
# Attempt to comply with the set of valid token characters defined for an HTTP
|
|
319
|
+
# header value in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
|
218
320
|
if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
|
|
219
321
|
msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
|
|
220
322
|
"Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
|
|
221
323
|
raise UnsafeRedirectError, msg
|
|
222
324
|
end
|
|
223
325
|
end
|
|
326
|
+
|
|
327
|
+
def _handle_path_relative_redirect(url)
|
|
328
|
+
message = "Path relative URL redirect detected: #{url.inspect}"
|
|
329
|
+
|
|
330
|
+
case action_on_path_relative_redirect
|
|
331
|
+
when :log
|
|
332
|
+
logger&.warn message
|
|
333
|
+
when :notify
|
|
334
|
+
ActiveSupport::Notifications.instrument("unsafe_redirect.action_controller",
|
|
335
|
+
url: url,
|
|
336
|
+
message: message,
|
|
337
|
+
stack_trace: caller
|
|
338
|
+
)
|
|
339
|
+
when :raise
|
|
340
|
+
raise PathRelativeRedirectError.new(url)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
224
343
|
end
|
|
225
344
|
end
|