actionpack 6.1.7.5 → 7.1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +355 -435
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +33 -37
  7. data/lib/abstract_controller/caching/fragments.rb +4 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +50 -11
  10. data/lib/abstract_controller/collector.rb +2 -2
  11. data/lib/abstract_controller/deprecator.rb +7 -0
  12. data/lib/abstract_controller/error.rb +1 -1
  13. data/lib/abstract_controller/helpers.rb +78 -30
  14. data/lib/abstract_controller/logger.rb +1 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +12 -14
  17. data/lib/abstract_controller/translation.rb +26 -7
  18. data/lib/abstract_controller/url_for.rb +6 -6
  19. data/lib/abstract_controller.rb +6 -0
  20. data/lib/action_controller/api.rb +12 -10
  21. data/lib/action_controller/base.rb +8 -21
  22. data/lib/action_controller/caching.rb +2 -0
  23. data/lib/action_controller/deprecator.rb +7 -0
  24. data/lib/action_controller/form_builder.rb +4 -2
  25. data/lib/action_controller/log_subscriber.rb +20 -7
  26. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  27. data/lib/action_controller/metal/conditional_get.rb +137 -102
  28. data/lib/action_controller/metal/content_security_policy.rb +37 -3
  29. data/lib/action_controller/metal/cookies.rb +1 -1
  30. data/lib/action_controller/metal/data_streaming.rb +25 -31
  31. data/lib/action_controller/metal/default_headers.rb +2 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  34. data/lib/action_controller/metal/exceptions.rb +27 -30
  35. data/lib/action_controller/metal/flash.rb +6 -2
  36. data/lib/action_controller/metal/head.rb +9 -7
  37. data/lib/action_controller/metal/helpers.rb +5 -16
  38. data/lib/action_controller/metal/http_authentication.rb +78 -42
  39. data/lib/action_controller/metal/implicit_render.rb +5 -3
  40. data/lib/action_controller/metal/instrumentation.rb +62 -50
  41. data/lib/action_controller/metal/live.rb +67 -2
  42. data/lib/action_controller/metal/mime_responds.rb +5 -5
  43. data/lib/action_controller/metal/params_wrapper.rb +24 -13
  44. data/lib/action_controller/metal/permissions_policy.rb +20 -29
  45. data/lib/action_controller/metal/redirecting.rb +96 -23
  46. data/lib/action_controller/metal/renderers.rb +14 -15
  47. data/lib/action_controller/metal/rendering.rb +121 -16
  48. data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
  49. data/lib/action_controller/metal/rescue.rb +7 -4
  50. data/lib/action_controller/metal/streaming.rb +74 -36
  51. data/lib/action_controller/metal/strong_parameters.rb +254 -151
  52. data/lib/action_controller/metal/testing.rb +9 -2
  53. data/lib/action_controller/metal/url_for.rb +10 -5
  54. data/lib/action_controller/metal.rb +89 -34
  55. data/lib/action_controller/railtie.rb +66 -9
  56. data/lib/action_controller/renderer.rb +99 -85
  57. data/lib/action_controller/test_case.rb +42 -11
  58. data/lib/action_controller.rb +10 -6
  59. data/lib/action_dispatch/constants.rb +32 -0
  60. data/lib/action_dispatch/deprecator.rb +7 -0
  61. data/lib/action_dispatch/http/cache.rb +21 -16
  62. data/lib/action_dispatch/http/content_security_policy.rb +122 -44
  63. data/lib/action_dispatch/http/filter_parameters.rb +14 -23
  64. data/lib/action_dispatch/http/headers.rb +3 -1
  65. data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
  66. data/lib/action_dispatch/http/mime_type.rb +43 -22
  67. data/lib/action_dispatch/http/mime_types.rb +3 -1
  68. data/lib/action_dispatch/http/parameters.rb +6 -6
  69. data/lib/action_dispatch/http/permissions_policy.rb +57 -19
  70. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  71. data/lib/action_dispatch/http/request.rb +75 -51
  72. data/lib/action_dispatch/http/response.rb +81 -77
  73. data/lib/action_dispatch/http/upload.rb +15 -2
  74. data/lib/action_dispatch/http/url.rb +11 -19
  75. data/lib/action_dispatch/journey/formatter.rb +8 -2
  76. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  79. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  80. data/lib/action_dispatch/journey/path/pattern.rb +36 -27
  81. data/lib/action_dispatch/journey/route.rb +8 -14
  82. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  83. data/lib/action_dispatch/journey/router.rb +10 -9
  84. data/lib/action_dispatch/journey/routes.rb +5 -5
  85. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  86. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  87. data/lib/action_dispatch/log_subscriber.rb +23 -0
  88. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
  89. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  90. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  91. data/lib/action_dispatch/middleware/cookies.rb +97 -107
  92. data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
  93. data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
  94. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  95. data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
  96. data/lib/action_dispatch/middleware/executor.rb +3 -0
  97. data/lib/action_dispatch/middleware/flash.rb +24 -18
  98. data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
  99. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  100. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  101. data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
  102. data/lib/action_dispatch/middleware/request_id.rb +5 -3
  103. data/lib/action_dispatch/middleware/server_timing.rb +76 -0
  104. data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
  105. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  106. data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
  107. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
  109. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  110. data/lib/action_dispatch/middleware/stack.rb +34 -11
  111. data/lib/action_dispatch/middleware/static.rb +16 -16
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  113. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
  114. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  115. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  116. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  117. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
  118. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
  119. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
  120. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  122. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
  123. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
  124. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
  125. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
  126. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
  127. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
  131. data/lib/action_dispatch/railtie.rb +20 -4
  132. data/lib/action_dispatch/request/session.rb +59 -19
  133. data/lib/action_dispatch/request/utils.rb +8 -3
  134. data/lib/action_dispatch/routing/inspector.rb +55 -7
  135. data/lib/action_dispatch/routing/mapper.rb +117 -107
  136. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  137. data/lib/action_dispatch/routing/redirection.rb +20 -8
  138. data/lib/action_dispatch/routing/route_set.rb +67 -27
  139. data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
  140. data/lib/action_dispatch/routing/url_for.rb +29 -26
  141. data/lib/action_dispatch/routing.rb +12 -13
  142. data/lib/action_dispatch/system_test_case.rb +8 -8
  143. data/lib/action_dispatch/system_testing/browser.rb +20 -29
  144. data/lib/action_dispatch/system_testing/driver.rb +34 -18
  145. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
  146. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  147. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  148. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  149. data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
  150. data/lib/action_dispatch/testing/assertions.rb +3 -4
  151. data/lib/action_dispatch/testing/integration.rb +33 -25
  152. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  153. data/lib/action_dispatch/testing/test_process.rb +5 -30
  154. data/lib/action_dispatch/testing/test_request.rb +1 -1
  155. data/lib/action_dispatch/testing/test_response.rb +34 -2
  156. data/lib/action_dispatch.rb +38 -4
  157. data/lib/action_pack/gem_version.rb +4 -4
  158. data/lib/action_pack/version.rb +1 -1
  159. data/lib/action_pack.rb +1 -1
  160. metadata +67 -30
@@ -4,13 +4,15 @@ 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
+ # = Action Controller Request Forgery Protection
15
+ #
14
16
  # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
15
17
  # by including a token in the rendered HTML for your application. This token is
16
18
  # stored as a random string in the session, to which an attacker does not have
@@ -32,12 +34,12 @@ module ActionController #:nodoc:
32
34
  # response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
33
35
  # Ajax) requests are allowed to make requests for JavaScript responses.
34
36
  #
35
- # Subclasses of <tt>ActionController::Base</tt> are protected by default with the
37
+ # Subclasses of ActionController::Base are protected by default with the
36
38
  # <tt>:exception</tt> strategy, which raises an
37
- # <tt>ActionController::InvalidAuthenticityToken</tt> error on unverified requests.
39
+ # ActionController::InvalidAuthenticityToken error on unverified requests.
38
40
  #
39
41
  # APIs may want to disable this behavior since they are typically designed to be
40
- # state-less: that is, the request API client handles the session instead of Rails.
42
+ # state-less: that is, the request API client handles the session instead of \Rails.
41
43
  # One way to achieve this is to use the <tt>:null_session</tt> strategy instead,
42
44
  # which allows unverified requests to be handled, but with an empty session:
43
45
  #
@@ -55,6 +57,8 @@ module ActionController #:nodoc:
55
57
  # Learn more about CSRF attacks and securing your application in the
56
58
  # {Ruby on Rails Security Guide}[https://guides.rubyonrails.org/security.html].
57
59
  module RequestForgeryProtection
60
+ CSRF_TOKEN = "action_controller.csrf_token"
61
+
58
62
  extend ActiveSupport::Concern
59
63
 
60
64
  include AbstractController::Helpers
@@ -90,9 +94,9 @@ module ActionController #:nodoc:
90
94
  config_accessor :default_protect_from_forgery
91
95
  self.default_protect_from_forgery = false
92
96
 
93
- # Controls whether URL-safe CSRF tokens are generated.
94
- config_accessor :urlsafe_csrf_tokens, instance_writer: false
95
- self.urlsafe_csrf_tokens = false
97
+ # The strategy to use for storing and retrieving CSRF tokens.
98
+ config_accessor :csrf_token_storage_strategy
99
+ self.csrf_token_storage_strategy = SessionStore.new
96
100
 
97
101
  helper_method :form_authenticity_token
98
102
  helper_method :protect_against_forgery?
@@ -109,30 +113,77 @@ module ActionController #:nodoc:
109
113
  # protect_from_forgery except: :index
110
114
  # end
111
115
  #
112
- # You can disable forgery protection on controller by skipping the verification before_action:
116
+ # You can disable forgery protection on a controller using skip_forgery_protection:
113
117
  #
114
- # skip_before_action :verify_authenticity_token
118
+ # class BarController < ApplicationController
119
+ # skip_forgery_protection
120
+ # end
115
121
  #
116
122
  # Valid Options:
117
123
  #
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.
124
+ # * <tt>:only</tt> / <tt>:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
125
+ # * <tt>:if</tt> / <tt>:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
120
126
  # * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
121
127
  # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
122
128
  # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
123
129
  #
124
130
  # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
125
131
  # * <tt>:with</tt> - Set the method to handle unverified request.
132
+ # Note if <tt>default_protect_from_forgery</tt> is true, Rails call protect_from_forgery with <tt>with :exception</tt>.
126
133
  #
127
- # Valid unverified request handling methods are:
134
+ # Built-in unverified request handling methods are:
128
135
  # * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
129
136
  # * <tt>:reset_session</tt> - Resets the session.
130
137
  # * <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.
138
+ #
139
+ # You can also implement custom strategy classes for unverified request handling:
140
+ #
141
+ # class CustomStrategy
142
+ # def initialize(controller)
143
+ # @controller = controller
144
+ # end
145
+ #
146
+ # def handle_unverified_request
147
+ # # Custom behavior for unverfied request
148
+ # end
149
+ # end
150
+ #
151
+ # class ApplicationController < ActionController::Base
152
+ # protect_from_forgery with: CustomStrategy
153
+ # end
154
+ # * <tt>:store</tt> - Set the strategy to store and retrieve CSRF tokens.
155
+ #
156
+ # Built-in session token strategies are:
157
+ # * <tt>:session</tt> - Store the CSRF token in the session. Used as default if <tt>:store</tt> option is not specified.
158
+ # * <tt>:cookie</tt> - Store the CSRF token in an encrypted cookie.
159
+ #
160
+ # You can also implement custom strategy classes for CSRF token storage:
161
+ #
162
+ # class CustomStore
163
+ # def fetch(request)
164
+ # # Return the token from a custom location
165
+ # end
166
+ #
167
+ # def store(request, csrf_token)
168
+ # # Store the token in a custom location
169
+ # end
170
+ #
171
+ # def reset(request)
172
+ # # Delete the stored session token
173
+ # end
174
+ # end
175
+ #
176
+ # class ApplicationController < ActionController::Base
177
+ # protect_from_forgery store: CustomStore.new
178
+ # end
131
179
  def protect_from_forgery(options = {})
132
180
  options = options.reverse_merge(prepend: false)
133
181
 
134
182
  self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
135
183
  self.request_forgery_protection_token ||= :authenticity_token
184
+
185
+ self.csrf_token_storage_strategy = storage_strategy(options[:store] || SessionStore.new)
186
+
136
187
  before_action :verify_authenticity_token, options
137
188
  append_after_action :verify_same_origin_request
138
189
  end
@@ -143,14 +194,39 @@ module ActionController #:nodoc:
143
194
  #
144
195
  # See +skip_before_action+ for allowed options.
145
196
  def skip_forgery_protection(options = {})
146
- skip_before_action :verify_authenticity_token, options
197
+ skip_before_action :verify_authenticity_token, options.reverse_merge(raise: false)
147
198
  end
148
199
 
149
200
  private
150
201
  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"
202
+ case name
203
+ when :null_session
204
+ ProtectionMethods::NullSession
205
+ when :reset_session
206
+ ProtectionMethods::ResetSession
207
+ when :exception
208
+ ProtectionMethods::Exception
209
+ when Class
210
+ name
211
+ else
212
+ raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, :reset_session, or a custom forgery protection class."
213
+ end
214
+ end
215
+
216
+ def storage_strategy(name)
217
+ case name
218
+ when :session
219
+ SessionStore.new
220
+ when :cookie
221
+ CookieStore.new(:csrf_token)
222
+ else
223
+ return name if is_storage_strategy?(name)
224
+ raise ArgumentError, "Invalid CSRF token storage strategy, use :session, :cookie, or a custom CSRF token storage class."
225
+ end
226
+ end
227
+
228
+ def is_storage_strategy?(object)
229
+ object.respond_to?(:fetch) && object.respond_to?(:store) && object.respond_to?(:reset)
154
230
  end
155
231
  end
156
232
 
@@ -170,7 +246,7 @@ module ActionController #:nodoc:
170
246
  end
171
247
 
172
248
  private
173
- class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
249
+ class NullSessionHash < Rack::Session::Abstract::SessionHash # :nodoc:
174
250
  def initialize(req)
175
251
  super(nil, req)
176
252
  @data = {}
@@ -183,9 +259,13 @@ module ActionController #:nodoc:
183
259
  def exists?
184
260
  true
185
261
  end
262
+
263
+ def enabled?
264
+ false
265
+ end
186
266
  end
187
267
 
188
- class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
268
+ class NullCookieJar < ActionDispatch::Cookies::CookieJar # :nodoc:
189
269
  def write(*)
190
270
  # nothing
191
271
  end
@@ -203,16 +283,80 @@ module ActionController #:nodoc:
203
283
  end
204
284
 
205
285
  class Exception
286
+ attr_accessor :warning_message
287
+
206
288
  def initialize(controller)
207
289
  @controller = controller
208
290
  end
209
291
 
210
292
  def handle_unverified_request
211
- raise ActionController::InvalidAuthenticityToken
293
+ raise ActionController::InvalidAuthenticityToken, warning_message
212
294
  end
213
295
  end
214
296
  end
215
297
 
298
+ class SessionStore
299
+ def fetch(request)
300
+ request.session[:_csrf_token]
301
+ end
302
+
303
+ def store(request, csrf_token)
304
+ request.session[:_csrf_token] = csrf_token
305
+ end
306
+
307
+ def reset(request)
308
+ request.session.delete(:_csrf_token)
309
+ end
310
+ end
311
+
312
+ class CookieStore
313
+ def initialize(cookie = :csrf_token)
314
+ @cookie_name = cookie
315
+ end
316
+
317
+ def fetch(request)
318
+ contents = request.cookie_jar.encrypted[@cookie_name]
319
+ return nil if contents.nil?
320
+
321
+ value = JSON.parse(contents)
322
+ return nil unless value.dig("session_id", "public_id") == request.session.id_was&.public_id
323
+
324
+ value["token"]
325
+ rescue JSON::ParserError
326
+ nil
327
+ end
328
+
329
+ def store(request, csrf_token)
330
+ request.cookie_jar.encrypted.permanent[@cookie_name] = {
331
+ value: {
332
+ token: csrf_token,
333
+ session_id: request.session.id,
334
+ }.to_json,
335
+ httponly: true,
336
+ same_site: :lax,
337
+ }
338
+ end
339
+
340
+ def reset(request)
341
+ request.cookie_jar.delete(@cookie_name)
342
+ end
343
+ end
344
+
345
+ def initialize(...)
346
+ super
347
+ @marked_for_same_origin_verification = nil
348
+ end
349
+
350
+ def reset_csrf_token(request) # :doc:
351
+ request.env.delete(CSRF_TOKEN)
352
+ csrf_token_storage_strategy.reset(request)
353
+ end
354
+
355
+ def commit_csrf_token(request) # :doc:
356
+ csrf_token = request.env[CSRF_TOKEN]
357
+ csrf_token_storage_strategy.store(request, csrf_token) unless csrf_token.nil?
358
+ end
359
+
216
360
  private
217
361
  # The actual before_action that is used to verify the CSRF token.
218
362
  # Don't override this directly. Provide your own forgery protection
@@ -228,22 +372,31 @@ module ActionController #:nodoc:
228
372
  mark_for_same_origin_verification!
229
373
 
230
374
  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
375
+ logger.warn unverified_request_warning_message if logger && log_warning_on_csrf_failure
376
+
238
377
  handle_unverified_request
239
378
  end
240
379
  end
241
380
 
242
381
  def handle_unverified_request # :doc:
243
- forgery_protection_strategy.new(self).handle_unverified_request
382
+ protection_strategy = forgery_protection_strategy.new(self)
383
+
384
+ if protection_strategy.respond_to?(:warning_message)
385
+ protection_strategy.warning_message = unverified_request_warning_message
386
+ end
387
+
388
+ protection_strategy.handle_unverified_request
389
+ end
390
+
391
+ def unverified_request_warning_message # :nodoc:
392
+ if valid_request_origin?
393
+ "Can't verify CSRF token authenticity."
394
+ else
395
+ "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
396
+ end
244
397
  end
245
398
 
246
- #:nodoc:
399
+ # :nodoc:
247
400
  CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
248
401
  "<script> tag on another site requested protected JavaScript. " \
249
402
  "If you know what you're doing, go ahead and disable forgery " \
@@ -285,7 +438,7 @@ module ActionController #:nodoc:
285
438
  #
286
439
  # * Is it a GET or HEAD request? GETs should be safe and idempotent
287
440
  # * Does the form_authenticity_token match the given token value from the params?
288
- # * Does the X-CSRF-Token header match the form_authenticity_token?
441
+ # * Does the +X-CSRF-Token+ header match the form_authenticity_token?
289
442
  def verified_request? # :doc:
290
443
  !protect_against_forgery? || request.get? || request.head? ||
291
444
  (valid_request_origin? && any_authenticity_token_valid?)
@@ -303,22 +456,22 @@ module ActionController #:nodoc:
303
456
  [form_authenticity_param, request.x_csrf_token]
304
457
  end
305
458
 
306
- # Sets the token value for the current session.
307
- def form_authenticity_token(form_options: {})
308
- masked_authenticity_token(session, form_options: form_options)
459
+ # Creates the authenticity token for the current request.
460
+ def form_authenticity_token(form_options: {}) # :doc:
461
+ masked_authenticity_token(form_options: form_options)
309
462
  end
310
463
 
311
464
  # Creates a masked version of the authenticity token that varies
312
465
  # on each request. The masking is used to mitigate SSL attacks
313
466
  # like BREACH.
314
- def masked_authenticity_token(session, form_options: {}) # :doc:
467
+ def masked_authenticity_token(form_options: {})
315
468
  action, method = form_options.values_at(:action, :method)
316
469
 
317
470
  raw_token = if per_form_csrf_tokens && action && method
318
471
  action_path = normalize_action_path(action)
319
- per_form_csrf_token(session, action_path, method)
472
+ per_form_csrf_token(nil, action_path, method)
320
473
  else
321
- global_csrf_token(session)
474
+ global_csrf_token
322
475
  end
323
476
 
324
477
  mask_token(raw_token)
@@ -346,14 +499,14 @@ module ActionController #:nodoc:
346
499
  # This is actually an unmasked token. This is expected if
347
500
  # you have just upgraded to masked tokens, but should stop
348
501
  # happening shortly after installing this gem.
349
- compare_with_real_token masked_token, session
502
+ compare_with_real_token masked_token
350
503
 
351
504
  elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
352
505
  csrf_token = unmask_token(masked_token)
353
506
 
354
- compare_with_global_token(csrf_token, session) ||
355
- compare_with_real_token(csrf_token, session) ||
356
- valid_per_form_csrf_token?(csrf_token, session)
507
+ compare_with_global_token(csrf_token) ||
508
+ compare_with_real_token(csrf_token) ||
509
+ valid_per_form_csrf_token?(csrf_token)
357
510
  else
358
511
  false # Token is malformed.
359
512
  end
@@ -374,15 +527,15 @@ module ActionController #:nodoc:
374
527
  encode_csrf_token(masked_token)
375
528
  end
376
529
 
377
- def compare_with_real_token(token, session) # :doc:
530
+ def compare_with_real_token(token, session = nil) # :doc:
378
531
  ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
379
532
  end
380
533
 
381
- def compare_with_global_token(token, session) # :doc:
534
+ def compare_with_global_token(token, session = nil) # :doc:
382
535
  ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, global_csrf_token(session))
383
536
  end
384
537
 
385
- def valid_per_form_csrf_token?(token, session) # :doc:
538
+ def valid_per_form_csrf_token?(token, session = nil) # :doc:
386
539
  if per_form_csrf_tokens
387
540
  correct_token = per_form_csrf_token(
388
541
  session,
@@ -396,9 +549,12 @@ module ActionController #:nodoc:
396
549
  end
397
550
  end
398
551
 
399
- def real_csrf_token(session) # :doc:
400
- session[:_csrf_token] ||= generate_csrf_token
401
- decode_csrf_token(session[:_csrf_token])
552
+ def real_csrf_token(_session = nil) # :doc:
553
+ csrf_token = request.env.fetch(CSRF_TOKEN) do
554
+ request.env[CSRF_TOKEN] = csrf_token_storage_strategy.fetch(request) || generate_csrf_token
555
+ end
556
+
557
+ decode_csrf_token(csrf_token)
402
558
  end
403
559
 
404
560
  def per_form_csrf_token(session, action_path, method) # :doc:
@@ -408,7 +564,7 @@ module ActionController #:nodoc:
408
564
  GLOBAL_CSRF_TOKEN_IDENTIFIER = "!real_csrf_token"
409
565
  private_constant :GLOBAL_CSRF_TOKEN_IDENTIFIER
410
566
 
411
- def global_csrf_token(session) # :doc:
567
+ def global_csrf_token(session = nil) # :doc:
412
568
  csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER)
413
569
  end
414
570
 
@@ -438,7 +594,7 @@ module ActionController #:nodoc:
438
594
 
439
595
  # Checks if the controller allows forgery protection.
440
596
  def protect_against_forgery? # :doc:
441
- allow_forgery_protection
597
+ allow_forgery_protection && (!session.respond_to?(:enabled?) || session.enabled?)
442
598
  end
443
599
 
444
600
  NULL_ORIGIN_MESSAGE = <<~MSG
@@ -468,31 +624,15 @@ module ActionController #:nodoc:
468
624
  end
469
625
 
470
626
  def generate_csrf_token # :nodoc:
471
- if urlsafe_csrf_tokens
472
- SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH, padding: false)
473
- else
474
- SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
475
- end
627
+ SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
476
628
  end
477
629
 
478
630
  def encode_csrf_token(csrf_token) # :nodoc:
479
- if urlsafe_csrf_tokens
480
- Base64.urlsafe_encode64(csrf_token, padding: false)
481
- else
482
- Base64.strict_encode64(csrf_token)
483
- end
631
+ Base64.urlsafe_encode64(csrf_token, padding: false)
484
632
  end
485
633
 
486
634
  def decode_csrf_token(encoded_csrf_token) # :nodoc:
487
- if urlsafe_csrf_tokens
488
- Base64.urlsafe_decode64(encoded_csrf_token)
489
- else
490
- begin
491
- Base64.strict_decode64(encoded_csrf_token)
492
- rescue ArgumentError
493
- Base64.urlsafe_decode64(encoded_csrf_token)
494
- end
495
- end
635
+ Base64.urlsafe_decode64(encoded_csrf_token)
496
636
  end
497
637
  end
498
638
  end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionController #:nodoc:
4
- # This module is responsible for providing +rescue_from+ helpers
5
- # to controllers and configuring when detailed exceptions must be
6
- # shown.
3
+ module ActionController # :nodoc:
4
+ # = Action Controller \Rescue
5
+ #
6
+ # This module is responsible for providing
7
+ # {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
8
+ # to controllers, wrapping actions to handle configured errors, and
9
+ # configuring when detailed exceptions must be shown.
7
10
  module Rescue
8
11
  extend ActiveSupport::Concern
9
12
  include ActiveSupport::Rescuable