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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +355 -435
- data/MIT-LICENSE +2 -1
- data/README.rdoc +6 -7
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +33 -37
- data/lib/abstract_controller/caching/fragments.rb +4 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +50 -11
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/deprecator.rb +7 -0
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +78 -30
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
- data/lib/abstract_controller/rendering.rb +12 -14
- data/lib/abstract_controller/translation.rb +26 -7
- data/lib/abstract_controller/url_for.rb +6 -6
- data/lib/abstract_controller.rb +6 -0
- data/lib/action_controller/api.rb +12 -10
- data/lib/action_controller/base.rb +8 -21
- data/lib/action_controller/caching.rb +2 -0
- data/lib/action_controller/deprecator.rb +7 -0
- data/lib/action_controller/form_builder.rb +4 -2
- data/lib/action_controller/log_subscriber.rb +20 -7
- data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +137 -102
- data/lib/action_controller/metal/content_security_policy.rb +37 -3
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +25 -31
- data/lib/action_controller/metal/default_headers.rb +2 -0
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
- data/lib/action_controller/metal/exceptions.rb +27 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- data/lib/action_controller/metal/head.rb +9 -7
- data/lib/action_controller/metal/helpers.rb +5 -16
- data/lib/action_controller/metal/http_authentication.rb +78 -42
- data/lib/action_controller/metal/implicit_render.rb +5 -3
- data/lib/action_controller/metal/instrumentation.rb +62 -50
- data/lib/action_controller/metal/live.rb +67 -2
- data/lib/action_controller/metal/mime_responds.rb +5 -5
- data/lib/action_controller/metal/params_wrapper.rb +24 -13
- data/lib/action_controller/metal/permissions_policy.rb +20 -29
- data/lib/action_controller/metal/redirecting.rb +96 -23
- data/lib/action_controller/metal/renderers.rb +14 -15
- data/lib/action_controller/metal/rendering.rb +121 -16
- data/lib/action_controller/metal/request_forgery_protection.rb +208 -68
- data/lib/action_controller/metal/rescue.rb +7 -4
- data/lib/action_controller/metal/streaming.rb +74 -36
- data/lib/action_controller/metal/strong_parameters.rb +254 -151
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +10 -5
- data/lib/action_controller/metal.rb +89 -34
- data/lib/action_controller/railtie.rb +66 -9
- data/lib/action_controller/renderer.rb +99 -85
- data/lib/action_controller/test_case.rb +42 -11
- data/lib/action_controller.rb +10 -6
- data/lib/action_dispatch/constants.rb +32 -0
- data/lib/action_dispatch/deprecator.rb +7 -0
- data/lib/action_dispatch/http/cache.rb +21 -16
- data/lib/action_dispatch/http/content_security_policy.rb +122 -44
- data/lib/action_dispatch/http/filter_parameters.rb +14 -23
- data/lib/action_dispatch/http/headers.rb +3 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +25 -15
- data/lib/action_dispatch/http/mime_type.rb +43 -22
- data/lib/action_dispatch/http/mime_types.rb +3 -1
- data/lib/action_dispatch/http/parameters.rb +6 -6
- data/lib/action_dispatch/http/permissions_policy.rb +57 -19
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +75 -51
- data/lib/action_dispatch/http/response.rb +81 -77
- data/lib/action_dispatch/http/upload.rb +15 -2
- data/lib/action_dispatch/http/url.rb +11 -19
- data/lib/action_dispatch/journey/formatter.rb +8 -2
- data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
- data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
- data/lib/action_dispatch/journey/nodes/node.rb +70 -5
- data/lib/action_dispatch/journey/path/pattern.rb +36 -27
- data/lib/action_dispatch/journey/route.rb +8 -14
- data/lib/action_dispatch/journey/router/utils.rb +2 -2
- data/lib/action_dispatch/journey/router.rb +10 -9
- data/lib/action_dispatch/journey/routes.rb +5 -5
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/log_subscriber.rb +23 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -7
- data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -0
- data/lib/action_dispatch/middleware/cookies.rb +97 -107
- data/lib/action_dispatch/middleware/debug_exceptions.rb +31 -28
- data/lib/action_dispatch/middleware/debug_locks.rb +7 -4
- data/lib/action_dispatch/middleware/debug_view.rb +7 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +190 -27
- data/lib/action_dispatch/middleware/executor.rb +3 -0
- data/lib/action_dispatch/middleware/flash.rb +24 -18
- data/lib/action_dispatch/middleware/host_authorization.rb +19 -20
- data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
- data/lib/action_dispatch/middleware/reloader.rb +7 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +32 -19
- data/lib/action_dispatch/middleware/request_id.rb +5 -3
- data/lib/action_dispatch/middleware/server_timing.rb +76 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +6 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -13
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +30 -25
- data/lib/action_dispatch/middleware/ssl.rb +18 -6
- data/lib/action_dispatch/middleware/stack.rb +34 -11
- data/lib/action_dispatch/middleware/static.rb +16 -16
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +45 -18
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -55
- data/lib/action_dispatch/railtie.rb +20 -4
- data/lib/action_dispatch/request/session.rb +59 -19
- data/lib/action_dispatch/request/utils.rb +8 -3
- data/lib/action_dispatch/routing/inspector.rb +55 -7
- data/lib/action_dispatch/routing/mapper.rb +117 -107
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
- data/lib/action_dispatch/routing/redirection.rb +20 -8
- data/lib/action_dispatch/routing/route_set.rb +67 -27
- data/lib/action_dispatch/routing/routes_proxy.rb +11 -16
- data/lib/action_dispatch/routing/url_for.rb +29 -26
- data/lib/action_dispatch/routing.rb +12 -13
- data/lib/action_dispatch/system_test_case.rb +8 -8
- data/lib/action_dispatch/system_testing/browser.rb +20 -29
- data/lib/action_dispatch/system_testing/driver.rb +34 -18
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +35 -20
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +14 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +70 -30
- data/lib/action_dispatch/testing/assertions.rb +3 -4
- data/lib/action_dispatch/testing/integration.rb +33 -25
- data/lib/action_dispatch/testing/request_encoder.rb +4 -1
- data/lib/action_dispatch/testing/test_process.rb +5 -30
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +34 -2
- data/lib/action_dispatch.rb +38 -4
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_pack.rb +1 -1
- 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
|
8
|
-
class InvalidAuthenticityToken < ActionControllerError
|
7
|
+
module ActionController # :nodoc:
|
8
|
+
class InvalidAuthenticityToken < ActionControllerError # :nodoc:
|
9
9
|
end
|
10
10
|
|
11
|
-
class InvalidCrossOriginRequest < ActionControllerError
|
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
|
37
|
+
# Subclasses of ActionController::Base are protected by default with the
|
36
38
|
# <tt>:exception</tt> strategy, which raises an
|
37
|
-
#
|
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
|
-
#
|
94
|
-
config_accessor :
|
95
|
-
self.
|
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
|
116
|
+
# You can disable forgery protection on a controller using skip_forgery_protection:
|
113
117
|
#
|
114
|
-
#
|
118
|
+
# class BarController < ApplicationController
|
119
|
+
# skip_forgery_protection
|
120
|
+
# end
|
115
121
|
#
|
116
122
|
# Valid Options:
|
117
123
|
#
|
118
|
-
# * <tt>:only
|
119
|
-
# * <tt>:if
|
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
|
-
#
|
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
|
-
|
152
|
-
|
153
|
-
|
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
|
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
|
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
|
-
|
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)
|
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
|
-
|
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
|
-
#
|
307
|
-
def form_authenticity_token(form_options: {})
|
308
|
-
masked_authenticity_token(
|
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(
|
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(
|
472
|
+
per_form_csrf_token(nil, action_path, method)
|
320
473
|
else
|
321
|
-
global_csrf_token
|
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
|
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
|
355
|
-
compare_with_real_token(csrf_token
|
356
|
-
valid_per_form_csrf_token?(csrf_token
|
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(
|
400
|
-
|
401
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
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
|