actionpack 5.2.1 → 7.0.2.4

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.

Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionController
4
- class ActionControllerError < StandardError #:nodoc:
4
+ class ActionControllerError < StandardError # :nodoc:
5
5
  end
6
6
 
7
- class BadRequest < ActionControllerError #:nodoc:
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 #:nodoc:
14
+ class RenderError < ActionControllerError # :nodoc:
15
15
  end
16
16
 
17
- class RoutingError < ActionControllerError #:nodoc:
17
+ class RoutingError < ActionControllerError # :nodoc:
18
18
  attr_reader :failures
19
19
  def initialize(message, failures = [])
20
20
  super(message)
@@ -22,22 +22,44 @@ module ActionController
22
22
  end
23
23
  end
24
24
 
25
- class ActionController::UrlGenerationError < ActionControllerError #:nodoc:
25
+ class UrlGenerationError < ActionControllerError # :nodoc:
26
+ attr_reader :routes, :route_name, :method_name
27
+
28
+ def initialize(message, routes = nil, route_name = nil, method_name = nil)
29
+ @routes = routes
30
+ @route_name = route_name
31
+ @method_name = method_name
32
+
33
+ super(message)
34
+ end
35
+
36
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
37
+ include DidYouMean::Correctable
38
+
39
+ def corrections
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)
45
+ end
46
+ end
47
+ end
26
48
  end
27
49
 
28
- class MethodNotAllowed < ActionControllerError #:nodoc:
50
+ class MethodNotAllowed < ActionControllerError # :nodoc:
29
51
  def initialize(*allowed_methods)
30
- super("Only #{allowed_methods.to_sentence(locale: :en)} requests are allowed.")
52
+ super("Only #{allowed_methods.to_sentence} requests are allowed.")
31
53
  end
32
54
  end
33
55
 
34
- class NotImplemented < MethodNotAllowed #:nodoc:
56
+ class NotImplemented < MethodNotAllowed # :nodoc:
35
57
  end
36
58
 
37
- class MissingFile < ActionControllerError #:nodoc:
59
+ class MissingFile < ActionControllerError # :nodoc:
38
60
  end
39
61
 
40
- class SessionOverflowError < ActionControllerError #:nodoc:
62
+ class SessionOverflowError < ActionControllerError # :nodoc:
41
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."
42
64
 
43
65
  def initialize(message = nil)
@@ -45,9 +67,30 @@ module ActionController
45
67
  end
46
68
  end
47
69
 
48
- class UnknownHttpMethod < ActionControllerError #:nodoc:
70
+ class UnknownHttpMethod < ActionControllerError # :nodoc:
71
+ end
72
+
73
+ class UnknownFormat < ActionControllerError # :nodoc:
74
+ end
75
+
76
+ # Raised when a nested respond_to is triggered and the content types of each
77
+ # are incompatible. For example:
78
+ #
79
+ # respond_to do |outer_type|
80
+ # outer_type.js do
81
+ # respond_to do |inner_type|
82
+ # inner_type.html { render body: "HTML" }
83
+ # end
84
+ # end
85
+ # end
86
+ class RespondToMismatchError < ActionControllerError
87
+ DEFAULT_MESSAGE = "respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action."
88
+
89
+ def initialize(message = nil)
90
+ super(message || DEFAULT_MESSAGE)
91
+ end
49
92
  end
50
93
 
51
- class UnknownFormat < ActionControllerError #:nodoc:
94
+ class MissingExactTemplate < UnknownFormat # :nodoc:
52
95
  end
53
96
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionController #:nodoc:
3
+ module ActionController # :nodoc:
4
4
  module Flash
5
5
  extend ActiveSupport::Concern
6
6
 
@@ -36,26 +36,30 @@ module ActionController #:nodoc:
36
36
  define_method(type) do
37
37
  request.flash[type]
38
38
  end
39
- helper_method type
39
+ helper_method(type) if respond_to?(:helper_method)
40
40
 
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_status_and_flash = {}) #:doc:
51
+ def redirect_to(options = {}, response_options_and_flash = {}) # :doc:
48
52
  self.class._flash_types.each do |flash_type|
49
- if type = response_status_and_flash.delete(flash_type)
53
+ if type = response_options_and_flash.delete(flash_type)
50
54
  flash[flash_type] = type
51
55
  end
52
56
  end
53
57
 
54
- if other_flashes = response_status_and_flash.delete(:flash)
58
+ if other_flashes = response_options_and_flash.delete(:flash)
55
59
  flash.update(other_flashes)
56
60
  end
57
61
 
58
- super(options, response_status_and_flash)
62
+ super(options, response_options_and_flash)
59
63
  end
60
64
  end
61
65
  end
@@ -29,19 +29,22 @@ module ActionController
29
29
  content_type = options.delete(:content_type)
30
30
 
31
31
  options.each do |key, value|
32
- headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s
32
+ headers[key.to_s.split(/[-_]/).each { |v| v[0] = v[0].upcase }.join("-")] = value.to_s
33
33
  end
34
34
 
35
35
  self.status = status
36
36
  self.location = url_for(location) if location
37
37
 
38
- self.response_body = ""
39
-
40
38
  if include_content?(response_code)
41
- self.content_type = content_type || (Mime[formats.first] if formats)
39
+ unless self.media_type
40
+ self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
41
+ end
42
+
42
43
  response.charset = false
43
44
  end
44
45
 
46
+ self.response_body = ""
47
+
45
48
  true
46
49
  end
47
50
 
@@ -11,7 +11,12 @@ module ActionController
11
11
  #
12
12
  # In previous versions of \Rails the controller will include a helper which
13
13
  # matches the name of the controller, e.g., <tt>MyController</tt> will automatically
14
- # include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
14
+ # include <tt>MyHelper</tt>. You can revert to the old behavior with the following:
15
+ #
16
+ # # config/application.rb
17
+ # class Application < Rails::Application
18
+ # config.action_controller.include_all_helpers = false
19
+ # end
15
20
  #
16
21
  # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
17
22
  # controller which inherits from it.
@@ -21,7 +26,7 @@ module ActionController
21
26
  #
22
27
  # module FormattedTimeHelper
23
28
  # def format_time(time, format=:long, blank_message="&nbsp;")
24
- # time.blank? ? blank_message : time.to_s(format)
29
+ # time.blank? ? blank_message : time.to_fs(format)
25
30
  # end
26
31
  # end
27
32
  #
@@ -34,7 +39,7 @@ module ActionController
34
39
  # end
35
40
  # end
36
41
  #
37
- # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
42
+ # Then, in any view rendered by <tt>EventsController</tt>, the <tt>format_time</tt> method can be called:
38
43
  #
39
44
  # <% @events.each do |event| -%>
40
45
  # <p>
@@ -73,9 +78,14 @@ module ActionController
73
78
  end
74
79
 
75
80
  # Provides a proxy to access helper methods from outside the view.
81
+ #
82
+ # Note that the proxy is rendered under a different view context.
83
+ # This may cause incorrect behaviour with capture methods. Consider
84
+ # using {helper}[rdoc-ref:AbstractController::Helpers::ClassMethods#helper]
85
+ # instead when using +capture+.
76
86
  def helpers
77
87
  @helper_proxy ||= begin
78
- proxy = ActionView::Base.new
88
+ proxy = ActionView::Base.empty
79
89
  proxy.config = config.inheritable_copy
80
90
  proxy.extend(_helpers)
81
91
  end
@@ -100,8 +110,7 @@ module ActionController
100
110
  # # => ["application", "chart", "rubygems"]
101
111
  def all_helpers_from_path(path)
102
112
  helpers = Array(path).flat_map do |_path|
103
- extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
104
- names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) }
113
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file[_path.to_s.size + 1..-"_helper.rb".size - 1] }
105
114
  names.sort!
106
115
  end
107
116
  helpers.uniq!
@@ -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
- # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
8
+ # HTTP Basic, Digest and Token authentication.
8
9
  module HttpAuthentication
9
- # Makes it dead easy to do HTTP \Basic authentication.
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 is protected by HTTP authentication,
28
- # the regular HTML interface is protected by a session approach:
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
@@ -56,8 +57,9 @@ module ActionController
56
57
  # In your integration tests, you can do something like this:
57
58
  #
58
59
  # def test_access_granted_from_xml
59
- # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
60
- # get "/notes/1.xml"
60
+ # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
61
+ #
62
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
61
63
  #
62
64
  # assert_equal 200, status
63
65
  # end
@@ -68,21 +70,22 @@ module ActionController
68
70
  extend ActiveSupport::Concern
69
71
 
70
72
  module ClassMethods
71
- def http_basic_authenticate_with(options = {})
72
- before_action(options.except(:name, :password, :realm)) do
73
- authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
74
- # This comparison uses & so that it doesn't short circuit and
75
- # uses `secure_compare` so that length information
76
- # isn't leaked.
77
- ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
78
- ActiveSupport::SecurityUtils.secure_compare(password, options[:password])
79
- end
80
- end
73
+ def http_basic_authenticate_with(name:, password:, realm: nil, **options)
74
+ before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
75
+ end
76
+ end
77
+
78
+ def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
79
+ authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
80
+ # This comparison uses & so that it doesn't short circuit and
81
+ # 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)
81
84
  end
82
85
  end
83
86
 
84
- def authenticate_or_request_with_http_basic(realm = "Application", message = nil, &login_procedure)
85
- authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm, message)
87
+ def authenticate_or_request_with_http_basic(realm = nil, message = nil, &login_procedure)
88
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm || "Application", message)
86
89
  end
87
90
 
88
91
  def authenticate_with_http_basic(&login_procedure)
@@ -101,7 +104,7 @@ module ActionController
101
104
  end
102
105
 
103
106
  def has_basic_credentials?(request)
104
- request.authorization.present? && (auth_scheme(request).downcase == "basic")
107
+ request.authorization.present? && (auth_scheme(request).downcase == "basic") && user_name_and_password(request).length == 2
105
108
  end
106
109
 
107
110
  def user_name_and_password(request)
@@ -126,21 +129,21 @@ module ActionController
126
129
 
127
130
  def authentication_request(controller, realm, message)
128
131
  message ||= "HTTP Basic: Access denied.\n"
129
- controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"'.freeze, "".freeze)}")
132
+ controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"', "")}")
130
133
  controller.status = 401
131
134
  controller.response_body = message
132
135
  end
133
136
  end
134
137
 
135
- # Makes it dead easy to do HTTP \Digest authentication.
138
+ # HTTP \Digest authentication.
136
139
  #
137
140
  # === Simple \Digest example
138
141
  #
139
- # require 'digest/md5'
142
+ # require "openssl"
140
143
  # class PostsController < ApplicationController
141
144
  # REALM = "SuperSecret"
142
145
  # USERS = {"dhh" => "secret", #plain text password
143
- # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
146
+ # "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
144
147
  #
145
148
  # before_action :authenticate, except: [:index]
146
149
  #
@@ -228,12 +231,12 @@ module ActionController
228
231
  # of a plain-text password.
229
232
  def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
230
233
  ha1 = password_is_ha1 ? password : ha1(credentials, password)
231
- ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
232
- ::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(":"))
233
236
  end
234
237
 
235
238
  def ha1(credentials, password)
236
- ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
239
+ OpenSSL::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
237
240
  end
238
241
 
239
242
  def encode_credentials(http_method, credentials, password, password_is_ha1)
@@ -307,7 +310,7 @@ module ActionController
307
310
  def nonce(secret_key, time = Time.now)
308
311
  t = time.to_i
309
312
  hashed = [t, secret_key]
310
- digest = ::Digest::MD5.hexdigest(hashed.join(":"))
313
+ digest = OpenSSL::Digest::MD5.hexdigest(hashed.join(":"))
311
314
  ::Base64.strict_encode64("#{t}:#{digest}")
312
315
  end
313
316
 
@@ -324,11 +327,11 @@ module ActionController
324
327
 
325
328
  # Opaque based on digest of secret key
326
329
  def opaque(secret_key)
327
- ::Digest::MD5.hexdigest(secret_key)
330
+ OpenSSL::Digest::MD5.hexdigest(secret_key)
328
331
  end
329
332
  end
330
333
 
331
- # Makes it dead easy to do HTTP Token authentication.
334
+ # HTTP Token authentication.
332
335
  #
333
336
  # Simple Token example:
334
337
  #
@@ -356,8 +359,8 @@ module ActionController
356
359
  # end
357
360
  #
358
361
  #
359
- # Here is a more advanced Token example where only Atom feeds and the XML API is protected by HTTP token authentication,
360
- # the regular HTML interface is protected by a session approach:
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:
361
364
  #
362
365
  # class ApplicationController < ActionController::Base
363
366
  # before_action :set_account, :authenticate
@@ -389,10 +392,9 @@ module ActionController
389
392
  # In your integration tests, you can do something like this:
390
393
  #
391
394
  # def test_access_granted_from_xml
392
- # get(
393
- # "/notes/1.xml", nil,
394
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
395
- # )
395
+ # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
396
+ #
397
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
396
398
  #
397
399
  # assert_equal 200, status
398
400
  # end
@@ -406,7 +408,7 @@ module ActionController
406
408
  module Token
407
409
  TOKEN_KEY = "token="
408
410
  TOKEN_REGEX = /^(Token|Bearer)\s+/
409
- AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
411
+ AUTHN_PAIR_DELIMITERS = /(?:,|;|\t)/
410
412
  extend self
411
413
 
412
414
  module ControllerMethods
@@ -474,7 +476,7 @@ module ActionController
474
476
 
475
477
  # This removes the <tt>"</tt> characters wrapping the value.
476
478
  def rewrite_param_values(array_params)
477
- array_params.each { |param| (param[1] || "".dup).gsub! %r/^"|"$/, "" }
479
+ array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
478
480
  end
479
481
 
480
482
  # This method takes an authorization body and splits up the key-value
@@ -483,7 +485,7 @@ module ActionController
483
485
  def raw_params(auth)
484
486
  _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
485
487
 
486
- if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
488
+ if !_raw_params.first&.start_with?(TOKEN_KEY)
487
489
  _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
488
490
  end
489
491
 
@@ -511,7 +513,7 @@ module ActionController
511
513
  # Returns nothing.
512
514
  def authentication_request(controller, realm, message = nil)
513
515
  message ||= "HTTP Token: Access denied.\n"
514
- controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
516
+ controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")
515
517
  controller.__send__ :render, plain: message, status: :unauthorized
516
518
  end
517
519
  end
@@ -22,7 +22,7 @@ module ActionController
22
22
  # Third, if we DON'T find a template AND the request is a page load in a web
23
23
  # browser (technically, a non-XHR GET request for an HTML response) where
24
24
  # you reasonably expect to have rendered a template, then we raise
25
- # <tt>ActionView::UnknownFormat</tt> with an explanation.
25
+ # <tt>ActionController::MissingExactTemplate</tt> with an explanation.
26
26
  #
27
27
  # Finally, if we DON'T find a template AND the request isn't a browser page
28
28
  # load, then we implicitly respond with <tt>204 No Content</tt>.
@@ -30,9 +30,9 @@ module ActionController
30
30
  # :stopdoc:
31
31
  include BasicImplicitRender
32
32
 
33
- def default_render(*args)
33
+ def default_render
34
34
  if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
35
- render(*args)
35
+ render
36
36
  elsif any_templates?(action_name.to_s, _prefixes)
37
37
  message = "#{self.class.name}\##{action_name} is missing a template " \
38
38
  "for this request format and variant.\n" \
@@ -41,18 +41,8 @@ module ActionController
41
41
 
42
42
  raise ActionController::UnknownFormat, message
43
43
  elsif interactive_browser_request?
44
- message = "#{self.class.name}\##{action_name} is missing a template " \
45
- "for this request format and variant.\n\n" \
46
- "request.formats: #{request.formats.map(&:to_s).inspect}\n" \
47
- "request.variant: #{request.variant.inspect}\n\n" \
48
- "NOTE! For XHR/Ajax or API requests, this action would normally " \
49
- "respond with 204 No Content: an empty white screen. Since you're " \
50
- "loading it in a web browser, we assume that you expected to " \
51
- "actually render a template, not nothing, so we're showing an " \
52
- "error to be extra-clear. If you expect 204 No Content, carry on. " \
53
- "That's what you'll get from an XHR or API request. Give it a shot."
54
-
55
- raise ActionController::UnknownFormat, message
44
+ message = "#{self.class.name}\##{action_name} is missing a template for request formats: #{request.formats.map(&:to_s).join(',')}"
45
+ raise ActionController::MissingExactTemplate, message
56
46
  else
57
47
  logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
58
48
  super
@@ -16,31 +16,7 @@ module ActionController
16
16
 
17
17
  attr_internal :view_runtime
18
18
 
19
- def process_action(*args)
20
- raw_payload = {
21
- controller: self.class.name,
22
- action: action_name,
23
- params: request.filtered_parameters,
24
- headers: request.headers,
25
- format: request.format.ref,
26
- method: request.request_method,
27
- path: request.fullpath
28
- }
29
-
30
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
31
-
32
- ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
33
- begin
34
- result = super
35
- payload[:status] = response.status
36
- result
37
- ensure
38
- append_info_to_payload(payload)
39
- end
40
- end
41
- end
42
-
43
- def render(*args)
19
+ def render(*)
44
20
  render_output = nil
45
21
  self.view_runtime = cleanup_view_runtime do
46
22
  Benchmark.ms { render_output = super }
@@ -61,8 +37,8 @@ module ActionController
61
37
  end
62
38
  end
63
39
 
64
- def redirect_to(*args)
65
- ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
40
+ def redirect_to(*)
41
+ ActiveSupport::Notifications.instrument("redirect_to.action_controller", request: request) do |payload|
66
42
  result = super
67
43
  payload[:status] = response.status
68
44
  payload[:location] = response.filtered_location
@@ -70,38 +46,66 @@ module ActionController
70
46
  end
71
47
  end
72
48
 
73
- private
49
+ private
50
+ def process_action(*)
51
+ ActiveSupport::ExecutionContext[:controller] = self
74
52
 
75
- # A hook invoked every time a before callback is halted.
76
- def halted_callback_hook(filter)
77
- ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
78
- end
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
+ }
79
63
 
80
- # A hook which allows you to clean up any time, wrongly taken into account in
81
- # views, like database querying time.
82
- #
83
- # def cleanup_view_runtime
84
- # super - time_taken_in_something_expensive
85
- # end
86
- def cleanup_view_runtime # :doc:
87
- yield
88
- end
64
+ ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
89
65
 
90
- # Every time after an action is processed, this method is invoked
91
- # with the payload, so you can add more information.
92
- def append_info_to_payload(payload) # :doc:
93
- payload[:view_runtime] = view_runtime
94
- end
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
95
78
 
96
- module ClassMethods
97
- # A hook which allows other frameworks to log what happened during
98
- # controller process action. This method should return an array
99
- # with the messages to be added.
100
- def log_process_action(payload) #:nodoc:
101
- messages, view_runtime = [], payload[:view_runtime]
102
- messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
103
- messages
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
104
109
  end
105
- end
106
110
  end
107
111
  end