actionpack 6.1.7 → 7.0.4.1
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 +269 -406
- data/MIT-LICENSE +1 -0
- data/README.rdoc +2 -3
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +13 -26
- 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 +2 -2
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +4 -3
- 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 +6 -6
- data/lib/action_controller/base.rb +5 -4
- data/lib/action_controller/form_builder.rb +2 -2
- data/lib/action_controller/log_subscriber.rb +4 -3
- data/lib/action_controller/metal/conditional_get.rb +39 -2
- data/lib/action_controller/metal/content_security_policy.rb +36 -2
- 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 +2 -2
- data/lib/action_controller/metal/http_authentication.rb +66 -39
- data/lib/action_controller/metal/instrumentation.rb +57 -52
- data/lib/action_controller/metal/live.rb +43 -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 +19 -28
- data/lib/action_controller/metal/redirecting.rb +93 -18
- data/lib/action_controller/metal/renderers.rb +10 -11
- data/lib/action_controller/metal/rendering.rb +8 -8
- data/lib/action_controller/metal/request_forgery_protection.rb +78 -29
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +6 -8
- data/lib/action_controller/metal/strong_parameters.rb +100 -54
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +3 -3
- data/lib/action_controller/metal.rb +10 -13
- data/lib/action_controller/railtie.rb +49 -6
- data/lib/action_controller/renderer.rb +1 -1
- data/lib/action_controller/test_case.rb +28 -7
- data/lib/action_controller.rb +2 -5
- data/lib/action_dispatch/http/cache.rb +14 -7
- data/lib/action_dispatch/http/content_security_policy.rb +108 -35
- data/lib/action_dispatch/http/filter_parameters.rb +5 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +15 -5
- data/lib/action_dispatch/http/mime_type.rb +9 -11
- data/lib/action_dispatch/http/parameters.rb +5 -5
- data/lib/action_dispatch/http/permissions_policy.rb +17 -1
- data/lib/action_dispatch/http/request.rb +12 -21
- data/lib/action_dispatch/http/response.rb +3 -16
- 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 +42 -27
- 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 +17 -18
- data/lib/action_dispatch/middleware/host_authorization.rb +1 -12
- data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- data/lib/action_dispatch/middleware/server_timing.rb +76 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
- data/lib/action_dispatch/middleware/session/cookie_store.rb +9 -9
- data/lib/action_dispatch/middleware/show_exceptions.rb +7 -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 +3 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +2 -0
- 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 +59 -83
- data/lib/action_dispatch/routing/redirection.rb +5 -2
- data/lib/action_dispatch/routing/route_set.rb +17 -7
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +4 -5
- data/lib/action_dispatch/routing.rb +5 -6
- data/lib/action_dispatch/system_test_case.rb +5 -5
- 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 +11 -7
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
- data/lib/action_dispatch/testing/assertions/routing.rb +3 -2
- 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 -29
- data/lib/action_dispatch/testing/test_response.rb +20 -2
- data/lib/action_dispatch.rb +1 -0
- data/lib/action_pack/gem_version.rb +5 -5
- data/lib/action_pack/version.rb +1 -1
- metadata +16 -15
@@ -1,20 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionController
|
4
|
-
class ActionControllerError < StandardError
|
4
|
+
class ActionControllerError < StandardError # :nodoc:
|
5
5
|
end
|
6
6
|
|
7
|
-
class BadRequest < ActionControllerError
|
7
|
+
class BadRequest < ActionControllerError # :nodoc:
|
8
8
|
def initialize(msg = nil)
|
9
9
|
super(msg)
|
10
10
|
set_backtrace $!.backtrace if $!
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
class RenderError < ActionControllerError
|
14
|
+
class RenderError < ActionControllerError # :nodoc:
|
15
15
|
end
|
16
16
|
|
17
|
-
class RoutingError < ActionControllerError
|
17
|
+
class RoutingError < ActionControllerError # :nodoc:
|
18
18
|
attr_reader :failures
|
19
19
|
def initialize(message, failures = [])
|
20
20
|
super(message)
|
@@ -22,7 +22,7 @@ module ActionController
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
class UrlGenerationError < ActionControllerError
|
25
|
+
class UrlGenerationError < ActionControllerError # :nodoc:
|
26
26
|
attr_reader :routes, :route_name, :method_name
|
27
27
|
|
28
28
|
def initialize(message, routes = nil, route_name = nil, method_name = nil)
|
@@ -33,44 +33,33 @@ module ActionController
|
|
33
33
|
super(message)
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
@error = error
|
39
|
-
end
|
36
|
+
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
|
37
|
+
include DidYouMean::Correctable
|
40
38
|
|
41
39
|
def corrections
|
42
|
-
|
43
|
-
maybe_these =
|
44
|
-
maybe_these -= [
|
45
|
-
|
46
|
-
maybe_these.
|
47
|
-
DidYouMean::Jaro.distance(@error.route_name, n)
|
48
|
-
}.reverse.first(4)
|
49
|
-
else
|
50
|
-
[]
|
40
|
+
@corrections ||= begin
|
41
|
+
maybe_these = routes&.named_routes&.helper_names&.grep(/#{route_name}/) || []
|
42
|
+
maybe_these -= [method_name.to_s] # remove exact match
|
43
|
+
|
44
|
+
DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(route_name)
|
51
45
|
end
|
52
46
|
end
|
53
47
|
end
|
54
|
-
|
55
|
-
# We may not have DYM, and DYM might not let us register error handlers
|
56
|
-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
|
57
|
-
DidYouMean.correct_error(self, Correction)
|
58
|
-
end
|
59
48
|
end
|
60
49
|
|
61
|
-
class MethodNotAllowed < ActionControllerError
|
50
|
+
class MethodNotAllowed < ActionControllerError # :nodoc:
|
62
51
|
def initialize(*allowed_methods)
|
63
52
|
super("Only #{allowed_methods.to_sentence} requests are allowed.")
|
64
53
|
end
|
65
54
|
end
|
66
55
|
|
67
|
-
class NotImplemented < MethodNotAllowed
|
56
|
+
class NotImplemented < MethodNotAllowed # :nodoc:
|
68
57
|
end
|
69
58
|
|
70
|
-
class MissingFile < ActionControllerError
|
59
|
+
class MissingFile < ActionControllerError # :nodoc:
|
71
60
|
end
|
72
61
|
|
73
|
-
class SessionOverflowError < ActionControllerError
|
62
|
+
class SessionOverflowError < ActionControllerError # :nodoc:
|
74
63
|
DEFAULT_MESSAGE = "Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data."
|
75
64
|
|
76
65
|
def initialize(message = nil)
|
@@ -78,10 +67,10 @@ module ActionController
|
|
78
67
|
end
|
79
68
|
end
|
80
69
|
|
81
|
-
class UnknownHttpMethod < ActionControllerError
|
70
|
+
class UnknownHttpMethod < ActionControllerError # :nodoc:
|
82
71
|
end
|
83
72
|
|
84
|
-
class UnknownFormat < ActionControllerError
|
73
|
+
class UnknownFormat < ActionControllerError # :nodoc:
|
85
74
|
end
|
86
75
|
|
87
76
|
# Raised when a nested respond_to is triggered and the content types of each
|
@@ -102,6 +91,6 @@ module ActionController
|
|
102
91
|
end
|
103
92
|
end
|
104
93
|
|
105
|
-
class MissingExactTemplate < UnknownFormat
|
94
|
+
class MissingExactTemplate < UnknownFormat # :nodoc:
|
106
95
|
end
|
107
96
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module ActionController
|
3
|
+
module ActionController # :nodoc:
|
4
4
|
module Flash
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
@@ -41,10 +41,14 @@ module ActionController #:nodoc:
|
|
41
41
|
self._flash_types += [type]
|
42
42
|
end
|
43
43
|
end
|
44
|
+
|
45
|
+
def action_methods # :nodoc:
|
46
|
+
@action_methods ||= super - _flash_types.map(&:to_s).to_set
|
47
|
+
end
|
44
48
|
end
|
45
49
|
|
46
50
|
private
|
47
|
-
def redirect_to(options = {}, response_options_and_flash = {})
|
51
|
+
def redirect_to(options = {}, response_options_and_flash = {}) # :doc:
|
48
52
|
self.class._flash_types.each do |flash_type|
|
49
53
|
if type = response_options_and_flash.delete(flash_type)
|
50
54
|
flash[flash_type] = type
|
@@ -26,7 +26,7 @@ module ActionController
|
|
26
26
|
#
|
27
27
|
# module FormattedTimeHelper
|
28
28
|
# def format_time(time, format=:long, blank_message=" ")
|
29
|
-
# time.blank? ? blank_message : time.
|
29
|
+
# time.blank? ? blank_message : time.to_fs(format)
|
30
30
|
# end
|
31
31
|
# end
|
32
32
|
#
|
@@ -91,7 +91,7 @@ module ActionController
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
-
#
|
94
|
+
# Override modules_for_helpers to accept +:all+ as argument, which loads
|
95
95
|
# all helpers in helpers_path.
|
96
96
|
#
|
97
97
|
# ==== Parameters
|
@@ -5,9 +5,9 @@ require "active_support/security_utils"
|
|
5
5
|
require "active_support/core_ext/array/access"
|
6
6
|
|
7
7
|
module ActionController
|
8
|
-
#
|
8
|
+
# HTTP Basic, Digest, and Token authentication.
|
9
9
|
module HttpAuthentication
|
10
|
-
#
|
10
|
+
# = HTTP \Basic authentication
|
11
11
|
#
|
12
12
|
# === Simple \Basic example
|
13
13
|
#
|
@@ -25,8 +25,8 @@ module ActionController
|
|
25
25
|
#
|
26
26
|
# === Advanced \Basic example
|
27
27
|
#
|
28
|
-
# Here is a more advanced \Basic example where only Atom feeds and the XML API
|
29
|
-
#
|
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:
|
30
30
|
#
|
31
31
|
# class ApplicationController < ActionController::Base
|
32
32
|
# before_action :set_account, :authenticate
|
@@ -70,7 +70,12 @@ module ActionController
|
|
70
70
|
extend ActiveSupport::Concern
|
71
71
|
|
72
72
|
module ClassMethods
|
73
|
+
# Enables HTTP \Basic authentication.
|
74
|
+
#
|
75
|
+
# See ActionController::HttpAuthentication::Basic for example usage.
|
73
76
|
def http_basic_authenticate_with(name:, password:, realm: nil, **options)
|
77
|
+
raise ArgumentError, "Expected name: to be a String, got #{name.class}" unless name.is_a?(String)
|
78
|
+
raise ArgumentError, "Expected password: to be a String, got #{password.class}" unless password.is_a?(String)
|
74
79
|
before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
|
75
80
|
end
|
76
81
|
end
|
@@ -79,8 +84,8 @@ module ActionController
|
|
79
84
|
authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
|
80
85
|
# This comparison uses & so that it doesn't short circuit and
|
81
86
|
# uses `secure_compare` so that length information isn't leaked.
|
82
|
-
ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
|
83
|
-
ActiveSupport::SecurityUtils.secure_compare(given_password, password)
|
87
|
+
ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) &
|
88
|
+
ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password)
|
84
89
|
end
|
85
90
|
end
|
86
91
|
|
@@ -135,15 +140,15 @@ module ActionController
|
|
135
140
|
end
|
136
141
|
end
|
137
142
|
|
138
|
-
#
|
143
|
+
# = HTTP \Digest authentication
|
139
144
|
#
|
140
145
|
# === Simple \Digest example
|
141
146
|
#
|
142
|
-
# require "
|
147
|
+
# require "openssl"
|
143
148
|
# class PostsController < ApplicationController
|
144
149
|
# REALM = "SuperSecret"
|
145
150
|
# USERS = {"dhh" => "secret", #plain text password
|
146
|
-
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
|
151
|
+
# "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
|
147
152
|
#
|
148
153
|
# before_action :authenticate, except: [:index]
|
149
154
|
#
|
@@ -181,22 +186,28 @@ module ActionController
|
|
181
186
|
extend self
|
182
187
|
|
183
188
|
module ControllerMethods
|
189
|
+
# Authenticate using an HTTP \Digest, or otherwise render an HTTP header
|
190
|
+
# requesting the client to send a \Digest.
|
191
|
+
#
|
192
|
+
# See ActionController::HttpAuthentication::Digest for example usage.
|
184
193
|
def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
|
185
194
|
authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
|
186
195
|
end
|
187
196
|
|
188
|
-
# Authenticate
|
197
|
+
# Authenticate using an HTTP \Digest. Returns true if authentication is
|
198
|
+
# successful, false otherwise.
|
189
199
|
def authenticate_with_http_digest(realm = "Application", &password_procedure)
|
190
200
|
HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
|
191
201
|
end
|
192
202
|
|
193
|
-
# Render
|
203
|
+
# Render an HTTP header requesting the client to send a \Digest for
|
204
|
+
# authentication.
|
194
205
|
def request_http_digest_authentication(realm = "Application", message = nil)
|
195
206
|
HttpAuthentication::Digest.authentication_request(self, realm, message)
|
196
207
|
end
|
197
208
|
end
|
198
209
|
|
199
|
-
# Returns false on a valid response, true otherwise
|
210
|
+
# Returns false on a valid response, true otherwise.
|
200
211
|
def authenticate(request, realm, &password_procedure)
|
201
212
|
request.authorization && validate_digest_response(request, realm, &password_procedure)
|
202
213
|
end
|
@@ -231,12 +242,12 @@ module ActionController
|
|
231
242
|
# of a plain-text password.
|
232
243
|
def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
|
233
244
|
ha1 = password_is_ha1 ? password : ha1(credentials, password)
|
234
|
-
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
|
235
|
-
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
|
245
|
+
ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
|
246
|
+
OpenSSL::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
|
236
247
|
end
|
237
248
|
|
238
249
|
def ha1(credentials, password)
|
239
|
-
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
|
250
|
+
OpenSSL::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
|
240
251
|
end
|
241
252
|
|
242
253
|
def encode_credentials(http_method, credentials, password, password_is_ha1)
|
@@ -301,7 +312,7 @@ module ActionController
|
|
301
312
|
#
|
302
313
|
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
|
303
314
|
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
|
304
|
-
# POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
|
315
|
+
# POST, PUT, or PATCH requests, and a time-stamp for GET requests. For more details on the issues involved see Section 4
|
305
316
|
# of this document.
|
306
317
|
#
|
307
318
|
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
|
@@ -310,7 +321,7 @@ module ActionController
|
|
310
321
|
def nonce(secret_key, time = Time.now)
|
311
322
|
t = time.to_i
|
312
323
|
hashed = [t, secret_key]
|
313
|
-
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
|
324
|
+
digest = OpenSSL::Digest::MD5.hexdigest(hashed.join(":"))
|
314
325
|
::Base64.strict_encode64("#{t}:#{digest}")
|
315
326
|
end
|
316
327
|
|
@@ -327,13 +338,13 @@ module ActionController
|
|
327
338
|
|
328
339
|
# Opaque based on digest of secret key
|
329
340
|
def opaque(secret_key)
|
330
|
-
::Digest::MD5.hexdigest(secret_key)
|
341
|
+
OpenSSL::Digest::MD5.hexdigest(secret_key)
|
331
342
|
end
|
332
343
|
end
|
333
344
|
|
334
|
-
#
|
345
|
+
# = HTTP \Token authentication
|
335
346
|
#
|
336
|
-
# Simple Token example
|
347
|
+
# === Simple \Token example
|
337
348
|
#
|
338
349
|
# class PostsController < ApplicationController
|
339
350
|
# TOKEN = "secret"
|
@@ -359,8 +370,8 @@ module ActionController
|
|
359
370
|
# end
|
360
371
|
#
|
361
372
|
#
|
362
|
-
# Here is a more advanced Token example where only Atom feeds and the XML API
|
363
|
-
#
|
373
|
+
# Here is a more advanced Token example where only Atom feeds and the XML API are protected by HTTP token authentication.
|
374
|
+
# The regular HTML interface is protected by a session approach:
|
364
375
|
#
|
365
376
|
# class ApplicationController < ActionController::Base
|
366
377
|
# before_action :set_account, :authenticate
|
@@ -412,14 +423,22 @@ module ActionController
|
|
412
423
|
extend self
|
413
424
|
|
414
425
|
module ControllerMethods
|
426
|
+
# Authenticate using an HTTP Bearer token, or otherwise render an HTTP
|
427
|
+
# header requesting the client to send a Bearer token.
|
428
|
+
#
|
429
|
+
# See ActionController::HttpAuthentication::Token for example usage.
|
415
430
|
def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
|
416
431
|
authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
|
417
432
|
end
|
418
433
|
|
434
|
+
# Authenticate using an HTTP Bearer token. Returns true if
|
435
|
+
# authentication is successful, false otherwise.
|
419
436
|
def authenticate_with_http_token(&login_procedure)
|
420
437
|
Token.authenticate(self, &login_procedure)
|
421
438
|
end
|
422
439
|
|
440
|
+
# Render an HTTP header requesting the client to send a Bearer token for
|
441
|
+
# authentication.
|
423
442
|
def request_http_token_authentication(realm = "Application", message = nil)
|
424
443
|
Token.authentication_request(self, realm, message)
|
425
444
|
end
|
@@ -428,17 +447,17 @@ module ActionController
|
|
428
447
|
# If token Authorization header is present, call the login
|
429
448
|
# procedure with the present token and options.
|
430
449
|
#
|
431
|
-
#
|
432
|
-
#
|
450
|
+
# Returns the return value of <tt>login_procedure</tt> if a
|
451
|
+
# token is found. Returns <tt>nil</tt> if no token is found.
|
452
|
+
#
|
453
|
+
# ==== Parameters
|
433
454
|
#
|
434
|
-
#
|
435
|
-
#
|
455
|
+
# * +controller+ - ActionController::Base instance for the current request.
|
456
|
+
# * +login_procedure+ - Proc to call if a token is present. The Proc
|
457
|
+
# should take two arguments:
|
436
458
|
#
|
437
459
|
# authenticate(controller) { |token, options| ... }
|
438
460
|
#
|
439
|
-
# Returns the return value of <tt>login_procedure</tt> if a
|
440
|
-
# token is found. Returns <tt>nil</tt> if no token is found.
|
441
|
-
|
442
461
|
def authenticate(controller, &login_procedure)
|
443
462
|
token, options = token_and_options(controller.request)
|
444
463
|
unless token.blank?
|
@@ -449,14 +468,18 @@ module ActionController
|
|
449
468
|
# Parses the token and options out of the token Authorization header.
|
450
469
|
# The value for the Authorization header is expected to have the prefix
|
451
470
|
# <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
|
471
|
+
#
|
452
472
|
# Authorization: Token token="abc", nonce="def"
|
453
|
-
# Then the returned token is <tt>"abc"</tt>, and the options are
|
454
|
-
# <tt>{nonce: "def"}</tt>
|
455
473
|
#
|
456
|
-
#
|
474
|
+
# Then the returned token is <tt>"abc"</tt>, and the options are
|
475
|
+
# <tt>{nonce: "def"}</tt>.
|
457
476
|
#
|
458
477
|
# Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
|
459
478
|
# Returns +nil+ if no token is found.
|
479
|
+
#
|
480
|
+
# ==== Parameters
|
481
|
+
#
|
482
|
+
# * +request+ - ActionDispatch::Request instance with the current headers.
|
460
483
|
def token_and_options(request)
|
461
484
|
authorization_request = request.authorization.to_s
|
462
485
|
if authorization_request[TOKEN_REGEX]
|
@@ -469,7 +492,7 @@ module ActionController
|
|
469
492
|
rewrite_param_values params_array_from raw_params auth
|
470
493
|
end
|
471
494
|
|
472
|
-
# Takes raw_params and turns it into an array of parameters
|
495
|
+
# Takes +raw_params+ and turns it into an array of parameters.
|
473
496
|
def params_array_from(raw_params)
|
474
497
|
raw_params.map { |param| param.split %r/=(.+)?/ }
|
475
498
|
end
|
@@ -494,10 +517,12 @@ module ActionController
|
|
494
517
|
|
495
518
|
# Encodes the given token and options into an Authorization header value.
|
496
519
|
#
|
497
|
-
# token - String token.
|
498
|
-
# options - optional Hash of the options.
|
499
|
-
#
|
500
520
|
# Returns String.
|
521
|
+
#
|
522
|
+
# ==== Parameters
|
523
|
+
#
|
524
|
+
# * +token+ - String token.
|
525
|
+
# * +options+ - Optional Hash of the options.
|
501
526
|
def encode_credentials(token, options = {})
|
502
527
|
values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
|
503
528
|
"#{key}=#{value.to_s.inspect}"
|
@@ -507,10 +532,12 @@ module ActionController
|
|
507
532
|
|
508
533
|
# Sets a WWW-Authenticate header to let the client know a token is desired.
|
509
534
|
#
|
510
|
-
# controller - ActionController::Base instance for the outgoing response.
|
511
|
-
# realm - String realm to use in the header.
|
512
|
-
#
|
513
535
|
# Returns nothing.
|
536
|
+
#
|
537
|
+
# ==== Parameters
|
538
|
+
#
|
539
|
+
# * +controller+ - ActionController::Base instance for the outgoing response.
|
540
|
+
# * +realm+ - String realm to use in the header.
|
514
541
|
def authentication_request(controller, realm, message = nil)
|
515
542
|
message ||= "HTTP Token: Access denied.\n"
|
516
543
|
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")
|
@@ -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
|
@@ -256,6 +261,7 @@ module ActionController
|
|
256
261
|
# Since we're processing the view in a different thread, copy the
|
257
262
|
# thread locals from the main thread to the child thread. :'(
|
258
263
|
locals.each { |k, v| t2[k] = v }
|
264
|
+
ActiveSupport::IsolatedExecutionState.share_with(t1)
|
259
265
|
|
260
266
|
begin
|
261
267
|
super(name)
|
@@ -291,6 +297,41 @@ module ActionController
|
|
291
297
|
response.close if response
|
292
298
|
end
|
293
299
|
|
300
|
+
# Sends a stream to the browser, which is helpful when you're generating exports or other running data where you
|
301
|
+
# don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live.
|
302
|
+
#
|
303
|
+
# Options:
|
304
|
+
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
305
|
+
# * <tt>:type</tt> - specifies an HTTP content type.
|
306
|
+
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
|
307
|
+
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
|
308
|
+
# If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
|
309
|
+
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
310
|
+
# Valid values are 'inline' and 'attachment' (default).
|
311
|
+
#
|
312
|
+
# Example of generating a csv export:
|
313
|
+
#
|
314
|
+
# send_stream(filename: "subscribers.csv") do |stream|
|
315
|
+
# stream.write "email_address,updated_at\n"
|
316
|
+
#
|
317
|
+
# @subscribers.find_each do |subscriber|
|
318
|
+
# stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
|
319
|
+
# end
|
320
|
+
# end
|
321
|
+
def send_stream(filename:, disposition: "attachment", type: nil)
|
322
|
+
response.headers["Content-Type"] =
|
323
|
+
(type.is_a?(Symbol) ? Mime[type].to_s : type) ||
|
324
|
+
Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete(".")) ||
|
325
|
+
"application/octet-stream"
|
326
|
+
|
327
|
+
response.headers["Content-Disposition"] =
|
328
|
+
ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
|
329
|
+
|
330
|
+
yield response.stream
|
331
|
+
ensure
|
332
|
+
response.stream.close
|
333
|
+
end
|
334
|
+
|
294
335
|
private
|
295
336
|
# Spawn a new thread to serve up the controller in. This is to get
|
296
337
|
# 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 = {}
|