actionpack 5.2.0 → 6.1.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +408 -190
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/lib/abstract_controller/base.rb +38 -4
- data/lib/abstract_controller/caching/fragments.rb +6 -22
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +14 -2
- data/lib/abstract_controller/collector.rb +1 -2
- data/lib/abstract_controller/helpers.rb +106 -90
- data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +11 -5
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api.rb +4 -3
- data/lib/action_controller/base.rb +6 -9
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/log_subscriber.rb +10 -7
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +19 -5
- data/lib/action_controller/metal/content_security_policy.rb +1 -2
- data/lib/action_controller/metal/cookies.rb +3 -1
- data/lib/action_controller/metal/data_streaming.rb +6 -7
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
- data/lib/action_controller/metal/exceptions.rb +56 -2
- data/lib/action_controller/metal/flash.rb +5 -5
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +14 -5
- data/lib/action_controller/metal/http_authentication.rb +25 -24
- data/lib/action_controller/metal/implicit_render.rb +5 -15
- data/lib/action_controller/metal/instrumentation.rb +13 -14
- data/lib/action_controller/metal/live.rb +39 -32
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +19 -4
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +33 -23
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +7 -7
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +8 -3
- data/lib/action_controller/metal/request_forgery_protection.rb +89 -36
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +0 -1
- data/lib/action_controller/metal/strong_parameters.rb +181 -69
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/metal.rb +12 -10
- data/lib/action_controller/railties/helpers.rb +1 -1
- data/lib/action_controller/renderer.rb +37 -13
- data/lib/action_controller/template_assertions.rb +1 -1
- data/lib/action_controller/test_case.rb +81 -70
- data/lib/action_controller.rb +7 -4
- data/lib/action_dispatch/http/cache.rb +34 -28
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +47 -24
- data/lib/action_dispatch/http/filter_parameters.rb +9 -8
- data/lib/action_dispatch/http/filter_redirect.rb +2 -3
- data/lib/action_dispatch/http/headers.rb +4 -4
- data/lib/action_dispatch/http/mime_negotiation.rb +31 -13
- data/lib/action_dispatch/http/mime_type.rb +43 -24
- data/lib/action_dispatch/http/parameters.rb +14 -23
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +45 -22
- data/lib/action_dispatch/http/response.rb +45 -25
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +82 -82
- data/lib/action_dispatch/journey/formatter.rb +55 -31
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +13 -11
- data/lib/action_dispatch/journey/parser.rb +13 -13
- data/lib/action_dispatch/journey/parser.y +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +21 -22
- data/lib/action_dispatch/journey/route.rb +10 -20
- data/lib/action_dispatch/journey/router/utils.rb +14 -12
- data/lib/action_dispatch/journey/router.rb +26 -34
- data/lib/action_dispatch/journey/routes.rb +1 -2
- data/lib/action_dispatch/journey/scanner.rb +10 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -4
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +128 -109
- data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
- data/lib/action_dispatch/middleware/flash.rb +2 -2
- data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
- data/lib/action_dispatch/middleware/request_id.rb +5 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +15 -2
- data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
- data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
- data/lib/action_dispatch/middleware/ssl.rb +20 -15
- data/lib/action_dispatch/middleware/stack.rb +57 -3
- data/lib/action_dispatch/middleware/static.rb +153 -93
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +17 -10
- data/lib/action_dispatch/request/utils.rb +28 -2
- data/lib/action_dispatch/routing/inspector.rb +101 -53
- data/lib/action_dispatch/routing/mapper.rb +156 -103
- data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
- data/lib/action_dispatch/routing/redirection.rb +4 -4
- data/lib/action_dispatch/routing/route_set.rb +71 -69
- data/lib/action_dispatch/routing/url_for.rb +3 -3
- data/lib/action_dispatch/routing.rb +21 -20
- data/lib/action_dispatch/system_test_case.rb +54 -11
- data/lib/action_dispatch/system_testing/browser.rb +53 -16
- data/lib/action_dispatch/system_testing/driver.rb +11 -3
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -6
- data/lib/action_dispatch/testing/assertion_response.rb +0 -1
- data/lib/action_dispatch/testing/assertions/response.rb +4 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +61 -28
- data/lib/action_dispatch/testing/request_encoder.rb +3 -3
- data/lib/action_dispatch/testing/test_process.rb +29 -4
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_dispatch/testing/test_response.rb +4 -32
- data/lib/action_dispatch.rb +14 -7
- data/lib/action_pack/gem_version.rb +3 -3
- data/lib/action_pack.rb +1 -1
- metadata +39 -22
- data/lib/action_controller/metal/force_ssl.rb +0 -99
- data/lib/action_dispatch/http/parameter_filter.rb +0 -86
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -3,7 +3,6 @@
|
|
3
3
|
require "rack/session/abstract/id"
|
4
4
|
require "action_controller/metal/exceptions"
|
5
5
|
require "active_support/security_utils"
|
6
|
-
require "active_support/core_ext/string/strip"
|
7
6
|
|
8
7
|
module ActionController #:nodoc:
|
9
8
|
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
|
@@ -18,7 +17,7 @@ module ActionController #:nodoc:
|
|
18
17
|
# access. When a request reaches your application, \Rails verifies the received
|
19
18
|
# token with the token in the session. All requests are checked except GET requests
|
20
19
|
# as these should be idempotent. Keep in mind that all session-oriented requests
|
21
|
-
#
|
20
|
+
# are CSRF protected by default, including JavaScript and HTML requests.
|
22
21
|
#
|
23
22
|
# Since HTML and JavaScript requests are typically made from the browser, we
|
24
23
|
# need to ensure to verify request authenticity for the web browser. We can
|
@@ -31,31 +30,30 @@ module ActionController #:nodoc:
|
|
31
30
|
# URL on your site. When your JavaScript response loads on their site, it executes.
|
32
31
|
# With carefully crafted JavaScript on their end, sensitive data in your JavaScript
|
33
32
|
# response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
|
34
|
-
# Ajax) requests are allowed to make
|
33
|
+
# Ajax) requests are allowed to make requests for JavaScript responses.
|
35
34
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# <tt>
|
35
|
+
# Subclasses of <tt>ActionController::Base</tt> are protected by default with the
|
36
|
+
# <tt>:exception</tt> strategy, which raises an
|
37
|
+
# <tt>ActionController::InvalidAuthenticityToken</tt> error on unverified requests.
|
38
|
+
#
|
39
|
+
# 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.
|
41
|
+
# One way to achieve this is to use the <tt>:null_session</tt> strategy instead,
|
42
|
+
# which allows unverified requests to be handled, but with an empty session:
|
39
43
|
#
|
40
44
|
# class ApplicationController < ActionController::Base
|
41
|
-
# protect_from_forgery
|
45
|
+
# protect_from_forgery with: :null_session
|
42
46
|
# end
|
43
47
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
# <tt>:null_session</tt> method, which provides an empty session
|
47
|
-
# during request.
|
48
|
-
#
|
49
|
-
# We may want to disable CSRF protection for APIs since they are typically
|
50
|
-
# designed to be state-less. That is, the request API client will handle
|
51
|
-
# the session for you instead of Rails.
|
48
|
+
# Note that API only applications don't include this module or a session middleware
|
49
|
+
# by default, and so don't require CSRF protection to be configured.
|
52
50
|
#
|
53
51
|
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
|
54
52
|
# value of this token must be added to every layout that renders forms by including
|
55
53
|
# <tt>csrf_meta_tags</tt> in the HTML +head+.
|
56
54
|
#
|
57
55
|
# Learn more about CSRF attacks and securing your application in the
|
58
|
-
# {Ruby on Rails Security Guide}[
|
56
|
+
# {Ruby on Rails Security Guide}[https://guides.rubyonrails.org/security.html].
|
59
57
|
module RequestForgeryProtection
|
60
58
|
extend ActiveSupport::Concern
|
61
59
|
|
@@ -92,6 +90,10 @@ module ActionController #:nodoc:
|
|
92
90
|
config_accessor :default_protect_from_forgery
|
93
91
|
self.default_protect_from_forgery = false
|
94
92
|
|
93
|
+
# Controls whether URL-safe CSRF tokens are generated.
|
94
|
+
config_accessor :urlsafe_csrf_tokens, instance_writer: false
|
95
|
+
self.urlsafe_csrf_tokens = false
|
96
|
+
|
95
97
|
helper_method :form_authenticity_token
|
96
98
|
helper_method :protect_against_forgery?
|
97
99
|
end
|
@@ -145,7 +147,6 @@ module ActionController #:nodoc:
|
|
145
147
|
end
|
146
148
|
|
147
149
|
private
|
148
|
-
|
149
150
|
def protection_method_class(name)
|
150
151
|
ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
|
151
152
|
rescue NameError
|
@@ -169,7 +170,6 @@ module ActionController #:nodoc:
|
|
169
170
|
end
|
170
171
|
|
171
172
|
private
|
172
|
-
|
173
173
|
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
|
174
174
|
def initialize(req)
|
175
175
|
super(nil, req)
|
@@ -276,7 +276,7 @@ module ActionController #:nodoc:
|
|
276
276
|
|
277
277
|
# Check for cross-origin JavaScript responses.
|
278
278
|
def non_xhr_javascript_response? # :doc:
|
279
|
-
|
279
|
+
%r(\A(?:text|application)/javascript).match?(media_type) && !request.xhr?
|
280
280
|
end
|
281
281
|
|
282
282
|
AUTHENTICITY_TOKEN_LENGTH = 32
|
@@ -318,13 +318,10 @@ module ActionController #:nodoc:
|
|
318
318
|
action_path = normalize_action_path(action)
|
319
319
|
per_form_csrf_token(session, action_path, method)
|
320
320
|
else
|
321
|
-
|
321
|
+
global_csrf_token(session)
|
322
322
|
end
|
323
323
|
|
324
|
-
|
325
|
-
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
|
326
|
-
masked_token = one_time_pad + encrypted_csrf_token
|
327
|
-
Base64.strict_encode64(masked_token)
|
324
|
+
mask_token(raw_token)
|
328
325
|
end
|
329
326
|
|
330
327
|
# Checks the client's masked token to see if it matches the
|
@@ -336,7 +333,7 @@ module ActionController #:nodoc:
|
|
336
333
|
end
|
337
334
|
|
338
335
|
begin
|
339
|
-
masked_token =
|
336
|
+
masked_token = decode_csrf_token(encoded_masked_token)
|
340
337
|
rescue ArgumentError # encoded_masked_token is invalid Base64
|
341
338
|
return false
|
342
339
|
end
|
@@ -354,7 +351,8 @@ module ActionController #:nodoc:
|
|
354
351
|
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
355
352
|
csrf_token = unmask_token(masked_token)
|
356
353
|
|
357
|
-
|
354
|
+
compare_with_global_token(csrf_token, session) ||
|
355
|
+
compare_with_real_token(csrf_token, session) ||
|
358
356
|
valid_per_form_csrf_token?(csrf_token, session)
|
359
357
|
else
|
360
358
|
false # Token is malformed.
|
@@ -369,15 +367,26 @@ module ActionController #:nodoc:
|
|
369
367
|
xor_byte_strings(one_time_pad, encrypted_csrf_token)
|
370
368
|
end
|
371
369
|
|
370
|
+
def mask_token(raw_token) # :doc:
|
371
|
+
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
|
372
|
+
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
|
373
|
+
masked_token = one_time_pad + encrypted_csrf_token
|
374
|
+
encode_csrf_token(masked_token)
|
375
|
+
end
|
376
|
+
|
372
377
|
def compare_with_real_token(token, session) # :doc:
|
373
378
|
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
|
374
379
|
end
|
375
380
|
|
381
|
+
def compare_with_global_token(token, session) # :doc:
|
382
|
+
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, global_csrf_token(session))
|
383
|
+
end
|
384
|
+
|
376
385
|
def valid_per_form_csrf_token?(token, session) # :doc:
|
377
386
|
if per_form_csrf_tokens
|
378
387
|
correct_token = per_form_csrf_token(
|
379
388
|
session,
|
380
|
-
|
389
|
+
request.path.chomp("/"),
|
381
390
|
request.request_method
|
382
391
|
)
|
383
392
|
|
@@ -388,22 +397,38 @@ module ActionController #:nodoc:
|
|
388
397
|
end
|
389
398
|
|
390
399
|
def real_csrf_token(session) # :doc:
|
391
|
-
session[:_csrf_token] ||=
|
392
|
-
|
400
|
+
session[:_csrf_token] ||= generate_csrf_token
|
401
|
+
decode_csrf_token(session[:_csrf_token])
|
393
402
|
end
|
394
403
|
|
395
404
|
def per_form_csrf_token(session, action_path, method) # :doc:
|
405
|
+
csrf_token_hmac(session, [action_path, method.downcase].join("#"))
|
406
|
+
end
|
407
|
+
|
408
|
+
GLOBAL_CSRF_TOKEN_IDENTIFIER = "!real_csrf_token"
|
409
|
+
private_constant :GLOBAL_CSRF_TOKEN_IDENTIFIER
|
410
|
+
|
411
|
+
def global_csrf_token(session) # :doc:
|
412
|
+
csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER)
|
413
|
+
end
|
414
|
+
|
415
|
+
def csrf_token_hmac(session, identifier) # :doc:
|
396
416
|
OpenSSL::HMAC.digest(
|
397
417
|
OpenSSL::Digest::SHA256.new,
|
398
418
|
real_csrf_token(session),
|
399
|
-
|
419
|
+
identifier
|
400
420
|
)
|
401
421
|
end
|
402
422
|
|
403
423
|
def xor_byte_strings(s1, s2) # :doc:
|
404
|
-
|
405
|
-
s1.
|
406
|
-
|
424
|
+
s2 = s2.dup
|
425
|
+
size = s1.bytesize
|
426
|
+
i = 0
|
427
|
+
while i < size
|
428
|
+
s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
|
429
|
+
i += 1
|
430
|
+
end
|
431
|
+
s2
|
407
432
|
end
|
408
433
|
|
409
434
|
# The form's authenticity parameter. Override to provide your own.
|
@@ -416,11 +441,11 @@ module ActionController #:nodoc:
|
|
416
441
|
allow_forgery_protection
|
417
442
|
end
|
418
443
|
|
419
|
-
NULL_ORIGIN_MESSAGE =
|
444
|
+
NULL_ORIGIN_MESSAGE = <<~MSG
|
420
445
|
The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually
|
421
|
-
means you have the 'no-referrer' Referrer-Policy header enabled, or that
|
446
|
+
means you have the 'no-referrer' Referrer-Policy header enabled, or that the request came from a site that
|
422
447
|
refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the
|
423
|
-
best solution is to change your referrer policy to something less strict like same-origin or strict-
|
448
|
+
best solution is to change your referrer policy to something less strict like same-origin or strict-origin.
|
424
449
|
If you cannot change the referrer policy, you can disable origin checking with the
|
425
450
|
Rails.application.config.action_controller.forgery_protection_origin_check setting.
|
426
451
|
MSG
|
@@ -441,5 +466,33 @@ module ActionController #:nodoc:
|
|
441
466
|
uri = URI.parse(action_path)
|
442
467
|
uri.path.chomp("/")
|
443
468
|
end
|
469
|
+
|
470
|
+
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
|
476
|
+
end
|
477
|
+
|
478
|
+
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
|
484
|
+
end
|
485
|
+
|
486
|
+
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
|
496
|
+
end
|
444
497
|
end
|
445
498
|
end
|