actionpack 6.1.4.1 → 7.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +191 -378
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +7 -21
- data/lib/abstract_controller/caching/fragments.rb +2 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +21 -7
- data/lib/abstract_controller/collector.rb +4 -2
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +3 -2
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/translation.rb +3 -2
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/action_controller/api.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +4 -3
- data/lib/action_controller/metal/conditional_get.rb +38 -1
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +5 -13
- data/lib/action_controller/metal/exceptions.rb +19 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- data/lib/action_controller/metal/helpers.rb +1 -1
- data/lib/action_controller/metal/http_authentication.rb +17 -16
- data/lib/action_controller/metal/instrumentation.rb +57 -52
- data/lib/action_controller/metal/live.rb +42 -2
- data/lib/action_controller/metal/mime_responds.rb +3 -3
- data/lib/action_controller/metal/params_wrapper.rb +20 -11
- data/lib/action_controller/metal/permissions_policy.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +86 -16
- data/lib/action_controller/metal/rendering.rb +7 -7
- data/lib/action_controller/metal/request_forgery_protection.rb +64 -24
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +1 -3
- data/lib/action_controller/metal/strong_parameters.rb +84 -47
- data/lib/action_controller/metal/testing.rb +0 -2
- data/lib/action_controller/metal.rb +7 -10
- data/lib/action_controller/railtie.rb +49 -6
- data/lib/action_controller/test_case.rb +19 -4
- data/lib/action_controller.rb +1 -5
- data/lib/action_dispatch/http/cache.rb +13 -6
- data/lib/action_dispatch/http/content_security_policy.rb +39 -35
- data/lib/action_dispatch/http/filter_parameters.rb +5 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +13 -3
- data/lib/action_dispatch/http/mime_type.rb +9 -11
- data/lib/action_dispatch/http/parameters.rb +4 -4
- data/lib/action_dispatch/http/permissions_policy.rb +1 -1
- data/lib/action_dispatch/http/request.rb +10 -19
- data/lib/action_dispatch/http/response.rb +1 -13
- data/lib/action_dispatch/http/url.rb +11 -19
- data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
- data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
- data/lib/action_dispatch/journey/nodes/node.rb +70 -5
- data/lib/action_dispatch/journey/path/pattern.rb +22 -13
- data/lib/action_dispatch/journey/route.rb +6 -13
- data/lib/action_dispatch/journey/router/utils.rb +2 -2
- data/lib/action_dispatch/journey/router.rb +1 -1
- data/lib/action_dispatch/journey/routes.rb +3 -3
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
- data/lib/action_dispatch/middleware/cookies.rb +8 -4
- data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
- data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
- data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
- data/lib/action_dispatch/middleware/executor.rb +3 -0
- data/lib/action_dispatch/middleware/flash.rb +9 -11
- data/lib/action_dispatch/middleware/host_authorization.rb +44 -30
- data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
- data/lib/action_dispatch/middleware/server_timing.rb +33 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +17 -9
- data/lib/action_dispatch/middleware/stack.rb +27 -9
- data/lib/action_dispatch/middleware/static.rb +2 -6
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +4 -3
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +3 -1
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +43 -13
- data/lib/action_dispatch/routing/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +54 -78
- data/lib/action_dispatch/routing/redirection.rb +0 -2
- data/lib/action_dispatch/routing/route_set.rb +14 -6
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +1 -2
- data/lib/action_dispatch/routing.rb +2 -2
- data/lib/action_dispatch/system_test_case.rb +12 -6
- data/lib/action_dispatch/system_testing/browser.rb +2 -12
- data/lib/action_dispatch/system_testing/driver.rb +35 -11
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +10 -6
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
- data/lib/action_dispatch/testing/assertions.rb +2 -5
- data/lib/action_dispatch/testing/integration.rb +6 -8
- data/lib/action_dispatch/testing/test_process.rb +3 -26
- data/lib/action_dispatch.rb +2 -1
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +18 -16
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
require "base64"
|
4
4
|
require "active_support/security_utils"
|
5
|
+
require "active_support/core_ext/array/access"
|
5
6
|
|
6
7
|
module ActionController
|
7
|
-
#
|
8
|
+
# HTTP Basic, Digest and Token authentication.
|
8
9
|
module HttpAuthentication
|
9
|
-
#
|
10
|
+
# HTTP \Basic authentication.
|
10
11
|
#
|
11
12
|
# === Simple \Basic example
|
12
13
|
#
|
@@ -24,8 +25,8 @@ module ActionController
|
|
24
25
|
#
|
25
26
|
# === Advanced \Basic example
|
26
27
|
#
|
27
|
-
# Here is a more advanced \Basic example where only Atom feeds and the XML API
|
28
|
-
#
|
28
|
+
# Here is a more advanced \Basic example where only Atom feeds and the XML API are protected by HTTP authentication.
|
29
|
+
# The regular HTML interface is protected by a session approach:
|
29
30
|
#
|
30
31
|
# class ApplicationController < ActionController::Base
|
31
32
|
# before_action :set_account, :authenticate
|
@@ -103,7 +104,7 @@ module ActionController
|
|
103
104
|
end
|
104
105
|
|
105
106
|
def has_basic_credentials?(request)
|
106
|
-
request.authorization.present? && (auth_scheme(request).downcase == "basic")
|
107
|
+
request.authorization.present? && (auth_scheme(request).downcase == "basic") && user_name_and_password(request).length == 2
|
107
108
|
end
|
108
109
|
|
109
110
|
def user_name_and_password(request)
|
@@ -134,15 +135,15 @@ module ActionController
|
|
134
135
|
end
|
135
136
|
end
|
136
137
|
|
137
|
-
#
|
138
|
+
# HTTP \Digest authentication.
|
138
139
|
#
|
139
140
|
# === Simple \Digest example
|
140
141
|
#
|
141
|
-
# require "
|
142
|
+
# require "openssl"
|
142
143
|
# class PostsController < ApplicationController
|
143
144
|
# REALM = "SuperSecret"
|
144
145
|
# USERS = {"dhh" => "secret", #plain text password
|
145
|
-
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
|
146
|
+
# "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
|
146
147
|
#
|
147
148
|
# before_action :authenticate, except: [:index]
|
148
149
|
#
|
@@ -230,12 +231,12 @@ module ActionController
|
|
230
231
|
# of a plain-text password.
|
231
232
|
def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
|
232
233
|
ha1 = password_is_ha1 ? password : ha1(credentials, password)
|
233
|
-
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
|
234
|
-
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
|
234
|
+
ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
|
235
|
+
OpenSSL::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
|
235
236
|
end
|
236
237
|
|
237
238
|
def ha1(credentials, password)
|
238
|
-
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
|
239
|
+
OpenSSL::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
|
239
240
|
end
|
240
241
|
|
241
242
|
def encode_credentials(http_method, credentials, password, password_is_ha1)
|
@@ -309,7 +310,7 @@ module ActionController
|
|
309
310
|
def nonce(secret_key, time = Time.now)
|
310
311
|
t = time.to_i
|
311
312
|
hashed = [t, secret_key]
|
312
|
-
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
|
313
|
+
digest = OpenSSL::Digest::MD5.hexdigest(hashed.join(":"))
|
313
314
|
::Base64.strict_encode64("#{t}:#{digest}")
|
314
315
|
end
|
315
316
|
|
@@ -326,11 +327,11 @@ module ActionController
|
|
326
327
|
|
327
328
|
# Opaque based on digest of secret key
|
328
329
|
def opaque(secret_key)
|
329
|
-
::Digest::MD5.hexdigest(secret_key)
|
330
|
+
OpenSSL::Digest::MD5.hexdigest(secret_key)
|
330
331
|
end
|
331
332
|
end
|
332
333
|
|
333
|
-
#
|
334
|
+
# HTTP Token authentication.
|
334
335
|
#
|
335
336
|
# Simple Token example:
|
336
337
|
#
|
@@ -358,8 +359,8 @@ module ActionController
|
|
358
359
|
# end
|
359
360
|
#
|
360
361
|
#
|
361
|
-
# Here is a more advanced Token example where only Atom feeds and the XML API
|
362
|
-
#
|
362
|
+
# Here is a more advanced Token example where only Atom feeds and the XML API are protected by HTTP token authentication.
|
363
|
+
# The regular HTML interface is protected by a session approach:
|
363
364
|
#
|
364
365
|
# class ApplicationController < ActionController::Base
|
365
366
|
# before_action :set_account, :authenticate
|
@@ -16,30 +16,6 @@ module ActionController
|
|
16
16
|
|
17
17
|
attr_internal :view_runtime
|
18
18
|
|
19
|
-
def process_action(*)
|
20
|
-
raw_payload = {
|
21
|
-
controller: self.class.name,
|
22
|
-
action: action_name,
|
23
|
-
request: request,
|
24
|
-
params: request.filtered_parameters,
|
25
|
-
headers: request.headers,
|
26
|
-
format: request.format.ref,
|
27
|
-
method: request.request_method,
|
28
|
-
path: request.fullpath
|
29
|
-
}
|
30
|
-
|
31
|
-
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
|
32
|
-
|
33
|
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
|
34
|
-
result = super
|
35
|
-
payload[:response] = response
|
36
|
-
payload[:status] = response.status
|
37
|
-
result
|
38
|
-
ensure
|
39
|
-
append_info_to_payload(payload)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
19
|
def render(*)
|
44
20
|
render_output = nil
|
45
21
|
self.view_runtime = cleanup_view_runtime do
|
@@ -70,37 +46,66 @@ module ActionController
|
|
70
46
|
end
|
71
47
|
end
|
72
48
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
|
77
|
-
end
|
49
|
+
private
|
50
|
+
def process_action(*)
|
51
|
+
ActiveSupport::ExecutionContext[:controller] = self
|
78
52
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
53
|
+
raw_payload = {
|
54
|
+
controller: self.class.name,
|
55
|
+
action: action_name,
|
56
|
+
request: request,
|
57
|
+
params: request.filtered_parameters,
|
58
|
+
headers: request.headers,
|
59
|
+
format: request.format.ref,
|
60
|
+
method: request.request_method,
|
61
|
+
path: request.fullpath
|
62
|
+
}
|
88
63
|
|
89
|
-
|
90
|
-
# with the payload, so you can add more information.
|
91
|
-
def append_info_to_payload(payload) # :doc:
|
92
|
-
payload[:view_runtime] = view_runtime
|
93
|
-
end
|
64
|
+
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
|
94
65
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
66
|
+
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
|
67
|
+
result = super
|
68
|
+
payload[:response] = response
|
69
|
+
payload[:status] = response.status
|
70
|
+
result
|
71
|
+
rescue => error
|
72
|
+
payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(error.class.name)
|
73
|
+
raise
|
74
|
+
ensure
|
75
|
+
append_info_to_payload(payload)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# A hook invoked every time a before callback is halted.
|
80
|
+
def halted_callback_hook(filter, _)
|
81
|
+
ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
|
82
|
+
end
|
83
|
+
|
84
|
+
# A hook which allows you to clean up any time, wrongly taken into account in
|
85
|
+
# views, like database querying time.
|
86
|
+
#
|
87
|
+
# def cleanup_view_runtime
|
88
|
+
# super - time_taken_in_something_expensive
|
89
|
+
# end
|
90
|
+
def cleanup_view_runtime # :doc:
|
91
|
+
yield
|
92
|
+
end
|
93
|
+
|
94
|
+
# Every time after an action is processed, this method is invoked
|
95
|
+
# with the payload, so you can add more information.
|
96
|
+
def append_info_to_payload(payload) # :doc:
|
97
|
+
payload[:view_runtime] = view_runtime
|
98
|
+
end
|
99
|
+
|
100
|
+
module ClassMethods
|
101
|
+
# A hook which allows other frameworks to log what happened during
|
102
|
+
# controller process action. This method should return an array
|
103
|
+
# with the messages to be added.
|
104
|
+
def log_process_action(payload) # :nodoc:
|
105
|
+
messages, view_runtime = [], payload[:view_runtime]
|
106
|
+
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
|
107
|
+
messages
|
108
|
+
end
|
103
109
|
end
|
104
|
-
end
|
105
110
|
end
|
106
111
|
end
|
@@ -124,7 +124,7 @@ module ActionController
|
|
124
124
|
class ClientDisconnected < RuntimeError
|
125
125
|
end
|
126
126
|
|
127
|
-
class Buffer < ActionDispatch::Response::Buffer
|
127
|
+
class Buffer < ActionDispatch::Response::Buffer # :nodoc:
|
128
128
|
include MonitorMixin
|
129
129
|
|
130
130
|
class << self
|
@@ -168,6 +168,11 @@ module ActionController
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
|
+
# Same as +write+ but automatically include a newline at the end of the string.
|
172
|
+
def writeln(string)
|
173
|
+
write string.end_with?("\n") ? string : "#{string}\n"
|
174
|
+
end
|
175
|
+
|
171
176
|
# Write a 'close' event to the buffer; the producer/writing thread
|
172
177
|
# uses this to notify us that it's finished supplying content.
|
173
178
|
#
|
@@ -225,7 +230,7 @@ module ActionController
|
|
225
230
|
end
|
226
231
|
end
|
227
232
|
|
228
|
-
class Response < ActionDispatch::Response
|
233
|
+
class Response < ActionDispatch::Response # :nodoc: all
|
229
234
|
private
|
230
235
|
def before_committed
|
231
236
|
super
|
@@ -291,6 +296,41 @@ module ActionController
|
|
291
296
|
response.close if response
|
292
297
|
end
|
293
298
|
|
299
|
+
# Sends a stream to the browser, which is helpful when you're generating exports or other running data where you
|
300
|
+
# don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live.
|
301
|
+
#
|
302
|
+
# Options:
|
303
|
+
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
304
|
+
# * <tt>:type</tt> - specifies an HTTP content type.
|
305
|
+
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
|
306
|
+
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
|
307
|
+
# If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
|
308
|
+
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
309
|
+
# Valid values are 'inline' and 'attachment' (default).
|
310
|
+
#
|
311
|
+
# Example of generating a csv export:
|
312
|
+
#
|
313
|
+
# send_stream(filename: "subscribers.csv") do |stream|
|
314
|
+
# stream.write "email_address,updated_at\n"
|
315
|
+
#
|
316
|
+
# @subscribers.find_each do |subscriber|
|
317
|
+
# stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
|
318
|
+
# end
|
319
|
+
# end
|
320
|
+
def send_stream(filename:, disposition: "attachment", type: nil)
|
321
|
+
response.headers["Content-Type"] =
|
322
|
+
(type.is_a?(Symbol) ? Mime[type].to_s : type) ||
|
323
|
+
Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete(".")) ||
|
324
|
+
"application/octet-stream"
|
325
|
+
|
326
|
+
response.headers["Content-Disposition"] =
|
327
|
+
ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
|
328
|
+
|
329
|
+
yield response.stream
|
330
|
+
ensure
|
331
|
+
response.stream.close
|
332
|
+
end
|
333
|
+
|
294
334
|
private
|
295
335
|
# Spawn a new thread to serve up the controller in. This is to get
|
296
336
|
# around the fact that Rack isn't based around IOs and we need to use
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "abstract_controller/collector"
|
4
4
|
|
5
|
-
module ActionController
|
5
|
+
module ActionController # :nodoc:
|
6
6
|
module MimeResponds
|
7
7
|
# Without web-service support, an action which collects the data for displaying a list of people
|
8
8
|
# might look something like this:
|
@@ -103,7 +103,7 @@ module ActionController #:nodoc:
|
|
103
103
|
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in
|
104
104
|
# +config/initializers/mime_types.rb+ as follows.
|
105
105
|
#
|
106
|
-
# Mime::Type.register "image/
|
106
|
+
# Mime::Type.register "image/jpeg", :jpg
|
107
107
|
#
|
108
108
|
# +respond_to+ also allows you to specify a common block for different formats by using +any+:
|
109
109
|
#
|
@@ -289,7 +289,7 @@ module ActionController #:nodoc:
|
|
289
289
|
@format = request.negotiate_mime(@responses.keys)
|
290
290
|
end
|
291
291
|
|
292
|
-
class VariantCollector
|
292
|
+
class VariantCollector # :nodoc:
|
293
293
|
def initialize(variant = nil)
|
294
294
|
@variant = variant
|
295
295
|
@variants = {}
|
@@ -9,11 +9,14 @@ module ActionController
|
|
9
9
|
# Wraps the parameters hash into a nested hash. This will allow clients to
|
10
10
|
# submit requests without having to specify any root elements.
|
11
11
|
#
|
12
|
-
# This functionality is enabled
|
13
|
-
#
|
12
|
+
# This functionality is enabled by default for JSON, and can be customized by
|
13
|
+
# setting the format array:
|
14
14
|
#
|
15
|
-
#
|
16
|
-
#
|
15
|
+
# class ApplicationController < ActionController::Base
|
16
|
+
# wrap_parameters format: [:json, :xml]
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# You could also turn it on per controller:
|
17
20
|
#
|
18
21
|
# class UsersController < ApplicationController
|
19
22
|
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
|
@@ -68,6 +71,12 @@ module ActionController
|
|
68
71
|
# will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
|
69
72
|
# determine the wrapper key respectively. If both models don't exist,
|
70
73
|
# it will then fallback to use +user+ as the key.
|
74
|
+
#
|
75
|
+
# To disable this functionality for a controller:
|
76
|
+
#
|
77
|
+
# class UsersController < ApplicationController
|
78
|
+
# wrap_parameters false
|
79
|
+
# end
|
71
80
|
module ParamsWrapper
|
72
81
|
extend ActiveSupport::Concern
|
73
82
|
|
@@ -242,14 +251,14 @@ module ActionController
|
|
242
251
|
end
|
243
252
|
end
|
244
253
|
|
245
|
-
# Performs parameters wrapping upon the request. Called automatically
|
246
|
-
# by the metal call stack.
|
247
|
-
def process_action(*)
|
248
|
-
_perform_parameter_wrapping if _wrapper_enabled?
|
249
|
-
super
|
250
|
-
end
|
251
|
-
|
252
254
|
private
|
255
|
+
# Performs parameters wrapping upon the request. Called automatically
|
256
|
+
# by the metal call stack.
|
257
|
+
def process_action(*)
|
258
|
+
_perform_parameter_wrapping if _wrapper_enabled?
|
259
|
+
super
|
260
|
+
end
|
261
|
+
|
253
262
|
# Returns the wrapper key which will be used to store wrapped parameters.
|
254
263
|
def _wrapper_key
|
255
264
|
_wrapper_options.name
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module ActionController
|
3
|
+
module ActionController # :nodoc:
|
4
4
|
# HTTP Permissions Policy is a web standard for defining a mechanism to
|
5
5
|
# allow and deny the use of browser permissions in its own context, and
|
6
6
|
# in content within any <iframe> elements in the document.
|
@@ -7,6 +7,12 @@ module ActionController
|
|
7
7
|
include AbstractController::Logger
|
8
8
|
include ActionController::UrlFor
|
9
9
|
|
10
|
+
class UnsafeRedirectError < StandardError; end
|
11
|
+
|
12
|
+
included do
|
13
|
+
mattr_accessor :raise_on_open_redirects, default: false
|
14
|
+
end
|
15
|
+
|
10
16
|
# Redirects the browser to the target specified in +options+. This parameter can be any one of:
|
11
17
|
#
|
12
18
|
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
@@ -54,16 +60,42 @@ module ActionController
|
|
54
60
|
#
|
55
61
|
# Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
|
56
62
|
# To terminate the execution of the function immediately after the +redirect_to+, use return.
|
63
|
+
#
|
57
64
|
# redirect_to post_url(@post) and return
|
65
|
+
#
|
66
|
+
# === Open Redirect protection
|
67
|
+
#
|
68
|
+
# By default, Rails protects against redirecting to external hosts for your app's safety, so called open redirects.
|
69
|
+
# Note: this was a new default in Rails 7.0, after upgrading opt-in by uncommenting the line with +raise_on_open_redirects+ in <tt>config/initializers/new_framework_defaults_7_0.rb</tt>
|
70
|
+
#
|
71
|
+
# Here #redirect_to automatically validates the potentially-unsafe URL:
|
72
|
+
#
|
73
|
+
# redirect_to params[:redirect_url]
|
74
|
+
#
|
75
|
+
# Raises UnsafeRedirectError in the case of an unsafe redirect.
|
76
|
+
#
|
77
|
+
# To allow any external redirects pass `allow_other_host: true`, though using a user-provided param in that case is unsafe.
|
78
|
+
#
|
79
|
+
# redirect_to "https://rubyonrails.org", allow_other_host: true
|
80
|
+
#
|
81
|
+
# See #url_from for more information on what an internal and safe URL is, or how to fall back to an alternate redirect URL in the unsafe case.
|
58
82
|
def redirect_to(options = {}, response_options = {})
|
59
83
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
60
84
|
raise AbstractController::DoubleRenderError if response_body
|
61
85
|
|
86
|
+
allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
|
87
|
+
|
62
88
|
self.status = _extract_redirect_to_status(options, response_options)
|
63
|
-
self.location = _compute_redirect_to_location(request, options)
|
89
|
+
self.location = _enforce_open_redirect_protection(_compute_redirect_to_location(request, options), allow_other_host: allow_other_host)
|
64
90
|
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
|
65
91
|
end
|
66
92
|
|
93
|
+
# Soft deprecated alias for #redirect_back_or_to where the +fallback_location+ location is supplied as a keyword argument instead
|
94
|
+
# of the first positional argument.
|
95
|
+
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
|
96
|
+
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
|
97
|
+
end
|
98
|
+
|
67
99
|
# Redirects the browser to the page that issued the request (the referrer)
|
68
100
|
# if possible, otherwise redirects to the provided default fallback
|
69
101
|
# location.
|
@@ -73,35 +105,37 @@ module ActionController
|
|
73
105
|
# subject to browser security settings and user preferences. If the request
|
74
106
|
# is missing this header, the <tt>fallback_location</tt> will be used.
|
75
107
|
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
108
|
+
# redirect_back_or_to({ action: "show", id: 5 })
|
109
|
+
# redirect_back_or_to @post
|
110
|
+
# redirect_back_or_to "http://www.rubyonrails.org"
|
111
|
+
# redirect_back_or_to "/images/screenshot.jpg"
|
112
|
+
# redirect_back_or_to posts_url
|
113
|
+
# redirect_back_or_to proc { edit_post_url(@post) }
|
114
|
+
# redirect_back_or_to '/', allow_other_host: false
|
83
115
|
#
|
84
116
|
# ==== Options
|
85
|
-
# * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
|
86
117
|
# * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
|
87
118
|
#
|
88
119
|
# All other options that can be passed to #redirect_to are accepted as
|
89
120
|
# options and the behavior is identical.
|
90
|
-
def
|
91
|
-
referer
|
92
|
-
|
93
|
-
|
121
|
+
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
|
122
|
+
if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
|
123
|
+
redirect_to request.referer, allow_other_host: allow_other_host, **options
|
124
|
+
else
|
125
|
+
# The method level `allow_other_host` doesn't apply in the fallback case, omit and let the `redirect_to` handling take over.
|
126
|
+
redirect_to fallback_location, **options
|
127
|
+
end
|
94
128
|
end
|
95
129
|
|
96
|
-
def _compute_redirect_to_location(request, options)
|
130
|
+
def _compute_redirect_to_location(request, options) # :nodoc:
|
97
131
|
case options
|
98
132
|
# The scheme name consist of a letter followed by any combination of
|
99
133
|
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
|
100
134
|
# characters; and is terminated by a colon (":").
|
101
135
|
# See https://tools.ietf.org/html/rfc3986#section-3.1
|
102
136
|
# The protocol relative scheme starts with a double slash "//".
|
103
|
-
when /\A([a-z][a-z\d
|
104
|
-
options
|
137
|
+
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
|
138
|
+
options.to_str
|
105
139
|
when String
|
106
140
|
request.protocol + request.host_with_port + options
|
107
141
|
when Proc
|
@@ -113,7 +147,35 @@ module ActionController
|
|
113
147
|
module_function :_compute_redirect_to_location
|
114
148
|
public :_compute_redirect_to_location
|
115
149
|
|
150
|
+
# Verifies the passed +location+ is an internal URL that's safe to redirect to and returns it, or nil if not.
|
151
|
+
# Useful to wrap a params provided redirect URL and fallback to an alternate URL to redirect to:
|
152
|
+
#
|
153
|
+
# redirect_to url_from(params[:redirect_url]) || root_url
|
154
|
+
#
|
155
|
+
# The +location+ is considered internal, and safe, if it's on the same host as <tt>request.host</tt>:
|
156
|
+
#
|
157
|
+
# # If request.host is example.com:
|
158
|
+
# url_from("https://example.com/profile") # => "https://example.com/profile"
|
159
|
+
# url_from("http://example.com/profile") # => "http://example.com/profile"
|
160
|
+
# url_from("http://evil.com/profile") # => nil
|
161
|
+
#
|
162
|
+
# Subdomains are considered part of the host:
|
163
|
+
#
|
164
|
+
# # If request.host is on https://example.com or https://app.example.com, you'd get:
|
165
|
+
# url_from("https://dev.example.com/profile") # => nil
|
166
|
+
#
|
167
|
+
# NOTE: there's a similarity with {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor#url_for], which generates an internal URL from various options from within the app, e.g. <tt>url_for(@post)</tt>.
|
168
|
+
# However, #url_from is meant to take an external parameter to verify as in <tt>url_from(params[:redirect_url])</tt>.
|
169
|
+
def url_from(location)
|
170
|
+
location = location.presence
|
171
|
+
location if location && _url_host_allowed?(location)
|
172
|
+
end
|
173
|
+
|
116
174
|
private
|
175
|
+
def _allow_other_host
|
176
|
+
!raise_on_open_redirects
|
177
|
+
end
|
178
|
+
|
117
179
|
def _extract_redirect_to_status(options, response_options)
|
118
180
|
if options.is_a?(Hash) && options.key?(:status)
|
119
181
|
Rack::Utils.status_code(options.delete(:status))
|
@@ -124,6 +186,14 @@ module ActionController
|
|
124
186
|
end
|
125
187
|
end
|
126
188
|
|
189
|
+
def _enforce_open_redirect_protection(location, allow_other_host:)
|
190
|
+
if allow_other_host || _url_host_allowed?(location)
|
191
|
+
location
|
192
|
+
else
|
193
|
+
raise UnsafeRedirectError, "Unsafe redirect to #{location.truncate(100).inspect}, pass allow_other_host: true to redirect anyway."
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
127
197
|
def _url_host_allowed?(url)
|
128
198
|
URI(url.to_s).host == request.host
|
129
199
|
rescue ArgumentError, URI::Error
|
@@ -24,14 +24,8 @@ module ActionController
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
# Before processing, set the request formats in current controller formats.
|
28
|
-
def process_action(*) #:nodoc:
|
29
|
-
self.formats = request.formats.map(&:ref).compact
|
30
|
-
super
|
31
|
-
end
|
32
|
-
|
33
27
|
# Check for double render errors and set the content_type after rendering.
|
34
|
-
def render(*args)
|
28
|
+
def render(*args) # :nodoc:
|
35
29
|
raise ::AbstractController::DoubleRenderError if response_body
|
36
30
|
super
|
37
31
|
end
|
@@ -53,6 +47,12 @@ module ActionController
|
|
53
47
|
end
|
54
48
|
|
55
49
|
private
|
50
|
+
# Before processing, set the request formats in current controller formats.
|
51
|
+
def process_action(*) # :nodoc:
|
52
|
+
self.formats = request.formats.filter_map(&:ref)
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
56
|
def _process_variant(options)
|
57
57
|
if defined?(request) && !request.nil? && request.variant.present?
|
58
58
|
options[:variant] = request.variant
|