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.

Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +269 -406
  3. data/MIT-LICENSE +1 -0
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +13 -26
  7. data/lib/abstract_controller/caching/fragments.rb +2 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +21 -7
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +4 -3
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/translation.rb +3 -2
  16. data/lib/abstract_controller/url_for.rb +4 -6
  17. data/lib/action_controller/api.rb +6 -6
  18. data/lib/action_controller/base.rb +5 -4
  19. data/lib/action_controller/form_builder.rb +2 -2
  20. data/lib/action_controller/log_subscriber.rb +4 -3
  21. data/lib/action_controller/metal/conditional_get.rb +39 -2
  22. data/lib/action_controller/metal/content_security_policy.rb +36 -2
  23. data/lib/action_controller/metal/cookies.rb +1 -1
  24. data/lib/action_controller/metal/data_streaming.rb +5 -13
  25. data/lib/action_controller/metal/exceptions.rb +19 -30
  26. data/lib/action_controller/metal/flash.rb +6 -2
  27. data/lib/action_controller/metal/helpers.rb +2 -2
  28. data/lib/action_controller/metal/http_authentication.rb +66 -39
  29. data/lib/action_controller/metal/instrumentation.rb +57 -52
  30. data/lib/action_controller/metal/live.rb +43 -2
  31. data/lib/action_controller/metal/mime_responds.rb +3 -3
  32. data/lib/action_controller/metal/params_wrapper.rb +20 -11
  33. data/lib/action_controller/metal/permissions_policy.rb +19 -28
  34. data/lib/action_controller/metal/redirecting.rb +93 -18
  35. data/lib/action_controller/metal/renderers.rb +10 -11
  36. data/lib/action_controller/metal/rendering.rb +8 -8
  37. data/lib/action_controller/metal/request_forgery_protection.rb +78 -29
  38. data/lib/action_controller/metal/rescue.rb +1 -1
  39. data/lib/action_controller/metal/streaming.rb +6 -8
  40. data/lib/action_controller/metal/strong_parameters.rb +100 -54
  41. data/lib/action_controller/metal/testing.rb +9 -2
  42. data/lib/action_controller/metal/url_for.rb +3 -3
  43. data/lib/action_controller/metal.rb +10 -13
  44. data/lib/action_controller/railtie.rb +49 -6
  45. data/lib/action_controller/renderer.rb +1 -1
  46. data/lib/action_controller/test_case.rb +28 -7
  47. data/lib/action_controller.rb +2 -5
  48. data/lib/action_dispatch/http/cache.rb +14 -7
  49. data/lib/action_dispatch/http/content_security_policy.rb +108 -35
  50. data/lib/action_dispatch/http/filter_parameters.rb +5 -0
  51. data/lib/action_dispatch/http/mime_negotiation.rb +15 -5
  52. data/lib/action_dispatch/http/mime_type.rb +9 -11
  53. data/lib/action_dispatch/http/parameters.rb +5 -5
  54. data/lib/action_dispatch/http/permissions_policy.rb +17 -1
  55. data/lib/action_dispatch/http/request.rb +12 -21
  56. data/lib/action_dispatch/http/response.rb +3 -16
  57. data/lib/action_dispatch/http/url.rb +11 -19
  58. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  59. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  60. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  61. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  62. data/lib/action_dispatch/journey/path/pattern.rb +22 -13
  63. data/lib/action_dispatch/journey/route.rb +6 -13
  64. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  65. data/lib/action_dispatch/journey/router.rb +1 -1
  66. data/lib/action_dispatch/journey/routes.rb +3 -3
  67. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  68. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  69. data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
  70. data/lib/action_dispatch/middleware/cookies.rb +42 -27
  71. data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
  72. data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
  73. data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
  74. data/lib/action_dispatch/middleware/executor.rb +3 -0
  75. data/lib/action_dispatch/middleware/flash.rb +17 -18
  76. data/lib/action_dispatch/middleware/host_authorization.rb +1 -12
  77. data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
  78. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  79. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  80. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
  81. data/lib/action_dispatch/middleware/session/cookie_store.rb +9 -9
  82. data/lib/action_dispatch/middleware/show_exceptions.rb +7 -9
  83. data/lib/action_dispatch/middleware/stack.rb +27 -9
  84. data/lib/action_dispatch/middleware/static.rb +2 -6
  85. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
  86. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  87. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  88. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +3 -2
  89. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
  91. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  92. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
  93. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
  94. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
  95. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  96. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  97. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
  98. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
  99. data/lib/action_dispatch/railtie.rb +8 -2
  100. data/lib/action_dispatch/request/session.rb +43 -13
  101. data/lib/action_dispatch/routing/inspector.rb +1 -1
  102. data/lib/action_dispatch/routing/mapper.rb +59 -83
  103. data/lib/action_dispatch/routing/redirection.rb +5 -2
  104. data/lib/action_dispatch/routing/route_set.rb +17 -7
  105. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  106. data/lib/action_dispatch/routing/url_for.rb +4 -5
  107. data/lib/action_dispatch/routing.rb +5 -6
  108. data/lib/action_dispatch/system_test_case.rb +5 -5
  109. data/lib/action_dispatch/system_testing/browser.rb +2 -12
  110. data/lib/action_dispatch/system_testing/driver.rb +35 -11
  111. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +11 -7
  112. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  113. data/lib/action_dispatch/testing/assertions/routing.rb +3 -2
  114. data/lib/action_dispatch/testing/assertions.rb +2 -5
  115. data/lib/action_dispatch/testing/integration.rb +6 -8
  116. data/lib/action_dispatch/testing/test_process.rb +3 -29
  117. data/lib/action_dispatch/testing/test_response.rb +20 -2
  118. data/lib/action_dispatch.rb +1 -0
  119. data/lib/action_pack/gem_version.rb +5 -5
  120. data/lib/action_pack/version.rb +1 -1
  121. metadata +16 -15
@@ -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 in +config/initializers/wrap_parameters.rb+
13
- # and can be customized.
12
+ # This functionality is enabled by default for JSON, and can be customized by
13
+ # setting the format array:
14
14
  #
15
- # You could also turn it on per controller by setting the format array to
16
- # a non-empty array:
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,37 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionController #:nodoc:
4
- # HTTP Permissions Policy is a web standard for defining a mechanism to
5
- # allow and deny the use of browser permissions in its own context, and
6
- # in content within any <iframe> elements in the document.
7
- #
8
- # Full details of HTTP Permissions Policy specification and guidelines can
9
- # be found at MDN:
10
- #
11
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy
12
- #
13
- # Examples of usage:
14
- #
15
- # # Global policy
16
- # Rails.application.config.permissions_policy do |f|
17
- # f.camera :none
18
- # f.gyroscope :none
19
- # f.microphone :none
20
- # f.usb :none
21
- # f.fullscreen :self
22
- # f.payment :self, "https://secure.example.com"
23
- # end
24
- #
25
- # # Controller level policy
26
- # class PagesController < ApplicationController
27
- # permissions_policy do |p|
28
- # p.geolocation "https://example.com"
29
- # end
30
- # end
3
+ module ActionController # :nodoc:
31
4
  module PermissionsPolicy
32
5
  extend ActiveSupport::Concern
33
6
 
34
7
  module ClassMethods
8
+ # Overrides parts of the globally configured Feature-Policy
9
+ # header:
10
+ #
11
+ # class PagesController < ApplicationController
12
+ # permissions_policy do |policy|
13
+ # policy.geolocation "https://example.com"
14
+ # end
15
+ # end
16
+ #
17
+ # Options can be passed similar to +before_action+. For example, pass
18
+ # <tt>only: :index</tt> to override the header on the index action only:
19
+ #
20
+ # class PagesController < ApplicationController
21
+ # permissions_policy(only: :index) do |policy|
22
+ # policy.camera :self
23
+ # end
24
+ # end
25
+ #
35
26
  def permissions_policy(**options, &block)
36
27
  before_action(options) do
37
28
  if block_given?
@@ -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 <tt>allow_other_host: true</tt>, 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
- # redirect_back fallback_location: { action: "show", id: 5 }
77
- # redirect_back fallback_location: @post
78
- # redirect_back fallback_location: "http://www.rubyonrails.org"
79
- # redirect_back fallback_location: "/images/screenshot.jpg"
80
- # redirect_back fallback_location: posts_url
81
- # redirect_back fallback_location: proc { edit_post_url(@post) }
82
- # redirect_back fallback_location: '/', allow_other_host: false
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
- # options and the behavior is identical.
90
- def redirect_back(fallback_location:, allow_other_host: true, **args)
91
- referer = request.headers["Referer"]
92
- redirect_to_referer = referer && (allow_other_host || _url_host_allowed?(referer))
93
- redirect_to redirect_to_referer ? referer : fallback_location, **args
120
+ # options, and the behavior is identical.
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) #:nodoc:
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\-+\.]*:|\/\/).*/i
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,8 +186,21 @@ 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
- URI(url.to_s).host == request.host
198
+ host = URI(url.to_s).host
199
+
200
+ return true if host == request.host
201
+ return false unless host.nil?
202
+ return false unless url.to_s.start_with?("/")
203
+ return !url.to_s.start_with?("//")
129
204
  rescue ArgumentError, URI::Error
130
205
  false
131
206
  end
@@ -31,8 +31,7 @@ module ActionController
31
31
  class_attribute :_renderers, default: Set.new.freeze
32
32
  end
33
33
 
34
- # Used in <tt>ActionController::Base</tt>
35
- # and <tt>ActionController::API</tt> to include all
34
+ # Used in ActionController::Base and ActionController::API to include all
36
35
  # renderers by default.
37
36
  module All
38
37
  extend ActiveSupport::Concern
@@ -45,7 +44,7 @@ module ActionController
45
44
 
46
45
  # Adds a new renderer to call within controller actions.
47
46
  # A renderer is invoked by passing its name as an option to
48
- # <tt>AbstractController::Rendering#render</tt>. To create a renderer
47
+ # AbstractController::Rendering#render. To create a renderer
49
48
  # pass it a name and a block. The block takes two arguments, the first
50
49
  # is the value paired with its key and the second is the remaining
51
50
  # hash of options passed to +render+.
@@ -96,18 +95,18 @@ module ActionController
96
95
  # Adds, by name, a renderer or renderers to the +_renderers+ available
97
96
  # to call within controller actions.
98
97
  #
99
- # It is useful when rendering from an <tt>ActionController::Metal</tt> controller or
98
+ # It is useful when rendering from an ActionController::Metal controller or
100
99
  # otherwise to add an available renderer proc to a specific controller.
101
100
  #
102
- # Both <tt>ActionController::Base</tt> and <tt>ActionController::API</tt>
103
- # include <tt>ActionController::Renderers::All</tt>, making all renderers
101
+ # Both ActionController::Base and ActionController::API
102
+ # include ActionController::Renderers::All, making all renderers
104
103
  # available in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
105
104
  #
106
- # Since <tt>ActionController::Metal</tt> controllers cannot render, the controller
107
- # must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>,
108
- # and <tt>ActionController::Renderers</tt>, and have at least one renderer.
105
+ # Since ActionController::Metal controllers cannot render, the controller
106
+ # must include AbstractController::Rendering, ActionController::Rendering,
107
+ # and ActionController::Renderers, and have at least one renderer.
109
108
  #
110
- # Rather than including <tt>ActionController::Renderers::All</tt> and including all renderers,
109
+ # Rather than including ActionController::Renderers::All and including all renderers,
111
110
  # you may specify which renderers to include by passing the renderer name or names to
112
111
  # +use_renderers+. For example, a controller that includes only the <tt>:json</tt> renderer
113
112
  # (+_render_with_renderer_json+) might look like:
@@ -133,7 +132,7 @@ module ActionController
133
132
  alias use_renderer use_renderers
134
133
  end
135
134
 
136
- # Called by +render+ in <tt>AbstractController::Rendering</tt>
135
+ # Called by +render+ in AbstractController::Rendering
137
136
  # which sets the return value as the +response_body+.
138
137
  #
139
138
  # If no renderer is found, +super+ returns control to
@@ -24,19 +24,13 @@ 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) #:nodoc:
28
+ def render(*args) # :nodoc:
35
29
  raise ::AbstractController::DoubleRenderError if response_body
36
30
  super
37
31
  end
38
32
 
39
- # Overwrite render_to_string because body can now be set to a Rack body.
33
+ # Override render_to_string because body can now be set to a Rack body.
40
34
  def render_to_string(*)
41
35
  result = super
42
36
  if result.respond_to?(:each)
@@ -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
@@ -4,11 +4,11 @@ require "rack/session/abstract/id"
4
4
  require "action_controller/metal/exceptions"
5
5
  require "active_support/security_utils"
6
6
 
7
- module ActionController #:nodoc:
8
- class InvalidAuthenticityToken < ActionControllerError #:nodoc:
7
+ module ActionController # :nodoc:
8
+ class InvalidAuthenticityToken < ActionControllerError # :nodoc:
9
9
  end
10
10
 
11
- class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
11
+ class InvalidCrossOriginRequest < ActionControllerError # :nodoc:
12
12
  end
13
13
 
14
14
  # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
@@ -32,7 +32,7 @@ module ActionController #:nodoc:
32
32
  # response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
33
33
  # Ajax) requests are allowed to make requests for JavaScript responses.
34
34
  #
35
- # Subclasses of <tt>ActionController::Base</tt> are protected by default with the
35
+ # Subclasses of ActionController::Base are protected by default with the
36
36
  # <tt>:exception</tt> strategy, which raises an
37
37
  # <tt>ActionController::InvalidAuthenticityToken</tt> error on unverified requests.
38
38
  #
@@ -92,7 +92,16 @@ module ActionController #:nodoc:
92
92
 
93
93
  # Controls whether URL-safe CSRF tokens are generated.
94
94
  config_accessor :urlsafe_csrf_tokens, instance_writer: false
95
- self.urlsafe_csrf_tokens = false
95
+ self.urlsafe_csrf_tokens = true
96
+
97
+ singleton_class.redefine_method(:urlsafe_csrf_tokens=) do |urlsafe_csrf_tokens|
98
+ if urlsafe_csrf_tokens
99
+ ActiveSupport::Deprecation.warn("URL-safe CSRF tokens are now the default. Use 6.1 defaults or above.")
100
+ else
101
+ ActiveSupport::Deprecation.warn("Non-URL-safe CSRF tokens are deprecated. Use 6.1 defaults or above.")
102
+ end
103
+ config.urlsafe_csrf_tokens = urlsafe_csrf_tokens
104
+ end
96
105
 
97
106
  helper_method :form_authenticity_token
98
107
  helper_method :protect_against_forgery?
@@ -115,8 +124,8 @@ module ActionController #:nodoc:
115
124
  #
116
125
  # Valid Options:
117
126
  #
118
- # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
119
- # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
127
+ # * <tt>:only</tt> / <tt>:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
128
+ # * <tt>:if</tt> / <tt>:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
120
129
  # * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
121
130
  # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
122
131
  # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
@@ -124,10 +133,26 @@ module ActionController #:nodoc:
124
133
  # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
125
134
  # * <tt>:with</tt> - Set the method to handle unverified request.
126
135
  #
127
- # Valid unverified request handling methods are:
136
+ # Built-in unverified request handling methods are:
128
137
  # * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
129
138
  # * <tt>:reset_session</tt> - Resets the session.
130
139
  # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
140
+ #
141
+ # You can also implement custom strategy classes for unverified request handling:
142
+ #
143
+ # class CustomStrategy
144
+ # def initialize(controller)
145
+ # @controller = controller
146
+ # end
147
+ #
148
+ # def handle_unverified_request
149
+ # # Custom behaviour for unverfied request
150
+ # end
151
+ # end
152
+ #
153
+ # class ApplicationController < ActionController:x:Base
154
+ # protect_from_forgery with: CustomStrategy
155
+ # end
131
156
  def protect_from_forgery(options = {})
132
157
  options = options.reverse_merge(prepend: false)
133
158
 
@@ -143,14 +168,23 @@ module ActionController #:nodoc:
143
168
  #
144
169
  # See +skip_before_action+ for allowed options.
145
170
  def skip_forgery_protection(options = {})
146
- skip_before_action :verify_authenticity_token, options
171
+ skip_before_action :verify_authenticity_token, options.reverse_merge(raise: false)
147
172
  end
148
173
 
149
174
  private
150
175
  def protection_method_class(name)
151
- ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
152
- rescue NameError
153
- raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, or :reset_session"
176
+ case name
177
+ when :null_session
178
+ ProtectionMethods::NullSession
179
+ when :reset_session
180
+ ProtectionMethods::ResetSession
181
+ when :exception
182
+ ProtectionMethods::Exception
183
+ when Class
184
+ name
185
+ else
186
+ raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, :reset_session, or a custom forgery protection class."
187
+ end
154
188
  end
155
189
  end
156
190
 
@@ -170,7 +204,7 @@ module ActionController #:nodoc:
170
204
  end
171
205
 
172
206
  private
173
- class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
207
+ class NullSessionHash < Rack::Session::Abstract::SessionHash # :nodoc:
174
208
  def initialize(req)
175
209
  super(nil, req)
176
210
  @data = {}
@@ -183,9 +217,13 @@ module ActionController #:nodoc:
183
217
  def exists?
184
218
  true
185
219
  end
220
+
221
+ def enabled?
222
+ false
223
+ end
186
224
  end
187
225
 
188
- class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
226
+ class NullCookieJar < ActionDispatch::Cookies::CookieJar # :nodoc:
189
227
  def write(*)
190
228
  # nothing
191
229
  end
@@ -203,12 +241,14 @@ module ActionController #:nodoc:
203
241
  end
204
242
 
205
243
  class Exception
244
+ attr_accessor :warning_message
245
+
206
246
  def initialize(controller)
207
247
  @controller = controller
208
248
  end
209
249
 
210
250
  def handle_unverified_request
211
- raise ActionController::InvalidAuthenticityToken
251
+ raise ActionController::InvalidAuthenticityToken, warning_message
212
252
  end
213
253
  end
214
254
  end
@@ -228,22 +268,31 @@ module ActionController #:nodoc:
228
268
  mark_for_same_origin_verification!
229
269
 
230
270
  if !verified_request?
231
- if logger && log_warning_on_csrf_failure
232
- if valid_request_origin?
233
- logger.warn "Can't verify CSRF token authenticity."
234
- else
235
- logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
236
- end
237
- end
271
+ logger.warn unverified_request_warning_message if logger && log_warning_on_csrf_failure
272
+
238
273
  handle_unverified_request
239
274
  end
240
275
  end
241
276
 
242
277
  def handle_unverified_request # :doc:
243
- forgery_protection_strategy.new(self).handle_unverified_request
278
+ protection_strategy = forgery_protection_strategy.new(self)
279
+
280
+ if protection_strategy.respond_to?(:warning_message)
281
+ protection_strategy.warning_message = unverified_request_warning_message
282
+ end
283
+
284
+ protection_strategy.handle_unverified_request
285
+ end
286
+
287
+ def unverified_request_warning_message # :nodoc:
288
+ if valid_request_origin?
289
+ "Can't verify CSRF token authenticity."
290
+ else
291
+ "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
292
+ end
244
293
  end
245
294
 
246
- #:nodoc:
295
+ # :nodoc:
247
296
  CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
248
297
  "<script> tag on another site requested protected JavaScript. " \
249
298
  "If you know what you're doing, go ahead and disable forgery " \
@@ -303,15 +352,15 @@ module ActionController #:nodoc:
303
352
  [form_authenticity_param, request.x_csrf_token]
304
353
  end
305
354
 
306
- # Sets the token value for the current session.
307
- def form_authenticity_token(form_options: {})
355
+ # Creates the authenticity token for the current request.
356
+ def form_authenticity_token(form_options: {}) # :doc:
308
357
  masked_authenticity_token(session, form_options: form_options)
309
358
  end
310
359
 
311
360
  # Creates a masked version of the authenticity token that varies
312
361
  # on each request. The masking is used to mitigate SSL attacks
313
362
  # like BREACH.
314
- def masked_authenticity_token(session, form_options: {}) # :doc:
363
+ def masked_authenticity_token(session, form_options: {})
315
364
  action, method = form_options.values_at(:action, :method)
316
365
 
317
366
  raw_token = if per_form_csrf_tokens && action && method
@@ -438,7 +487,7 @@ module ActionController #:nodoc:
438
487
 
439
488
  # Checks if the controller allows forgery protection.
440
489
  def protect_against_forgery? # :doc:
441
- allow_forgery_protection
490
+ allow_forgery_protection && (!session.respond_to?(:enabled?) || session.enabled?)
442
491
  end
443
492
 
444
493
  NULL_ORIGIN_MESSAGE = <<~MSG
@@ -469,7 +518,7 @@ module ActionController #:nodoc:
469
518
 
470
519
  def generate_csrf_token # :nodoc:
471
520
  if urlsafe_csrf_tokens
472
- SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH, padding: false)
521
+ SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
473
522
  else
474
523
  SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
475
524
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionController #:nodoc:
3
+ module ActionController # :nodoc:
4
4
  # This module is responsible for providing +rescue_from+ helpers
5
5
  # to controllers and configuring when detailed exceptions must be
6
6
  # shown.
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "rack/chunked"
4
4
 
5
- module ActionController #:nodoc:
5
+ module ActionController # :nodoc:
6
6
  # Allows views to be streamed back to the client as they are rendered.
7
7
  #
8
8
  # By default, Rails renders views by first rendering the template
@@ -24,7 +24,7 @@ module ActionController #:nodoc:
24
24
  # Ruby implementation).
25
25
  #
26
26
  # Streaming can be added to a given template easily, all you need to do is
27
- # to pass the :stream option.
27
+ # to pass the +:stream+ option.
28
28
  #
29
29
  # class PostsController
30
30
  # def index
@@ -59,8 +59,8 @@ module ActionController #:nodoc:
59
59
  # render stream: true
60
60
  # end
61
61
  #
62
- # Notice that :stream only works with templates. Rendering :json
63
- # or :xml with :stream won't work.
62
+ # Notice that +:stream+ only works with templates. Rendering +:json+
63
+ # or +:xml+ with +:stream+ won't work.
64
64
  #
65
65
  # == Communication between layout and template
66
66
  #
@@ -72,7 +72,7 @@ module ActionController #:nodoc:
72
72
  # variables set in the template to be used in the layout, they won't
73
73
  # work once you move to streaming. The proper way to communicate
74
74
  # between layout and template, regardless of whether you use streaming
75
- # or not, is by using +content_for+, +provide+ and +yield+.
75
+ # or not, is by using +content_for+, +provide+, and +yield+.
76
76
  #
77
77
  # Take a simple example where the layout expects the template to tell
78
78
  # which title to use:
@@ -132,7 +132,7 @@ module ActionController #:nodoc:
132
132
  # That said, when streaming, you need to properly check your templates
133
133
  # and choose when to use +provide+ and +content_for+.
134
134
  #
135
- # == Headers, cookies, session and flash
135
+ # == Headers, cookies, session, and flash
136
136
  #
137
137
  # When streaming, the HTTP headers are sent to the client right before
138
138
  # it renders the first line. This means that, modifying headers, cookies,
@@ -193,8 +193,6 @@ module ActionController #:nodoc:
193
193
  # To be described.
194
194
  #
195
195
  module Streaming
196
- extend ActiveSupport::Concern
197
-
198
196
  private
199
197
  # Set proper cache control and transfer encoding when streaming
200
198
  def _process_options(options)