actionpack 7.1.5.1 → 8.1.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +308 -523
- data/README.rdoc +1 -1
- data/lib/abstract_controller/asset_paths.rb +6 -2
- data/lib/abstract_controller/base.rb +104 -105
- data/lib/abstract_controller/caching/fragments.rb +50 -53
- data/lib/abstract_controller/caching.rb +8 -3
- data/lib/abstract_controller/callbacks.rb +70 -62
- data/lib/abstract_controller/collector.rb +7 -7
- data/lib/abstract_controller/deprecator.rb +2 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +71 -84
- data/lib/abstract_controller/logger.rb +4 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +13 -13
- data/lib/abstract_controller/translation.rb +12 -13
- data/lib/abstract_controller/url_for.rb +8 -6
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api/api_rendering.rb +2 -0
- data/lib/action_controller/api.rb +76 -72
- data/lib/action_controller/base.rb +199 -126
- data/lib/action_controller/caching.rb +16 -14
- data/lib/action_controller/deprecator.rb +2 -0
- data/lib/action_controller/form_builder.rb +21 -18
- data/lib/action_controller/log_subscriber.rb +23 -2
- data/lib/action_controller/metal/allow_browser.rb +133 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
- data/lib/action_controller/metal/conditional_get.rb +217 -175
- data/lib/action_controller/metal/content_security_policy.rb +25 -24
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +72 -63
- data/lib/action_controller/metal/default_headers.rb +5 -3
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
- data/lib/action_controller/metal/exceptions.rb +16 -9
- data/lib/action_controller/metal/flash.rb +13 -14
- data/lib/action_controller/metal/head.rb +15 -11
- data/lib/action_controller/metal/helpers.rb +63 -55
- data/lib/action_controller/metal/http_authentication.rb +209 -201
- data/lib/action_controller/metal/implicit_render.rb +17 -15
- data/lib/action_controller/metal/instrumentation.rb +16 -14
- data/lib/action_controller/metal/live.rb +177 -128
- data/lib/action_controller/metal/logging.rb +6 -4
- data/lib/action_controller/metal/mime_responds.rb +151 -142
- data/lib/action_controller/metal/parameter_encoding.rb +34 -32
- data/lib/action_controller/metal/params_wrapper.rb +57 -59
- data/lib/action_controller/metal/permissions_policy.rb +22 -12
- data/lib/action_controller/metal/rate_limiting.rb +92 -0
- data/lib/action_controller/metal/redirecting.rb +213 -94
- data/lib/action_controller/metal/renderers.rb +78 -57
- data/lib/action_controller/metal/rendering.rb +111 -77
- data/lib/action_controller/metal/request_forgery_protection.rb +182 -143
- data/lib/action_controller/metal/rescue.rb +20 -9
- data/lib/action_controller/metal/streaming.rb +118 -195
- data/lib/action_controller/metal/strong_parameters.rb +720 -530
- data/lib/action_controller/metal/testing.rb +2 -0
- data/lib/action_controller/metal/url_for.rb +17 -15
- data/lib/action_controller/metal.rb +86 -60
- data/lib/action_controller/railtie.rb +36 -15
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +41 -36
- data/lib/action_controller/structured_event_subscriber.rb +116 -0
- data/lib/action_controller/template_assertions.rb +4 -2
- data/lib/action_controller/test_case.rb +160 -131
- data/lib/action_controller.rb +5 -1
- data/lib/action_dispatch/constants.rb +8 -0
- data/lib/action_dispatch/deprecator.rb +2 -0
- data/lib/action_dispatch/http/cache.rb +163 -35
- data/lib/action_dispatch/http/content_disposition.rb +2 -0
- data/lib/action_dispatch/http/content_security_policy.rb +54 -39
- data/lib/action_dispatch/http/filter_parameters.rb +14 -8
- data/lib/action_dispatch/http/filter_redirect.rb +22 -1
- data/lib/action_dispatch/http/headers.rb +22 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +89 -41
- data/lib/action_dispatch/http/mime_type.rb +25 -21
- data/lib/action_dispatch/http/mime_types.rb +3 -0
- data/lib/action_dispatch/http/param_builder.rb +187 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/parameters.rb +14 -12
- data/lib/action_dispatch/http/permissions_policy.rb +25 -36
- data/lib/action_dispatch/http/query_parser.rb +55 -0
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +141 -92
- data/lib/action_dispatch/http/response.rb +137 -77
- data/lib/action_dispatch/http/upload.rb +18 -16
- data/lib/action_dispatch/http/url.rb +187 -89
- data/lib/action_dispatch/journey/formatter.rb +21 -9
- data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +34 -11
- data/lib/action_dispatch/journey/gtg/transition_table.rb +47 -53
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
- data/lib/action_dispatch/journey/nodes/node.rb +8 -6
- data/lib/action_dispatch/journey/parser.rb +99 -195
- data/lib/action_dispatch/journey/path/pattern.rb +4 -1
- data/lib/action_dispatch/journey/route.rb +54 -38
- data/lib/action_dispatch/journey/router/utils.rb +22 -27
- data/lib/action_dispatch/journey/router.rb +63 -83
- data/lib/action_dispatch/journey/routes.rb +11 -2
- data/lib/action_dispatch/journey/scanner.rb +46 -42
- data/lib/action_dispatch/journey/visitors.rb +57 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/journey.rb +2 -0
- data/lib/action_dispatch/log_subscriber.rb +7 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
- data/lib/action_dispatch/middleware/callbacks.rb +3 -1
- data/lib/action_dispatch/middleware/cookies.rb +125 -106
- data/lib/action_dispatch/middleware/debug_exceptions.rb +37 -8
- data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
- data/lib/action_dispatch/middleware/debug_view.rb +13 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +18 -23
- data/lib/action_dispatch/middleware/executor.rb +19 -4
- data/lib/action_dispatch/middleware/flash.rb +63 -51
- data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
- data/lib/action_dispatch/middleware/public_exceptions.rb +14 -12
- data/lib/action_dispatch/middleware/reloader.rb +5 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +87 -77
- data/lib/action_dispatch/middleware/request_id.rb +16 -10
- data/lib/action_dispatch/middleware/server_timing.rb +4 -2
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +30 -8
- data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +16 -16
- data/lib/action_dispatch/middleware/ssl.rb +53 -40
- data/lib/action_dispatch/middleware/stack.rb +11 -10
- data/lib/action_dispatch/middleware/static.rb +33 -31
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
- data/lib/action_dispatch/railtie.rb +23 -3
- data/lib/action_dispatch/request/session.rb +24 -21
- data/lib/action_dispatch/request/utils.rb +11 -3
- data/lib/action_dispatch/routing/endpoint.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +85 -60
- data/lib/action_dispatch/routing/mapper.rb +1031 -851
- data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
- data/lib/action_dispatch/routing/redirection.rb +47 -39
- data/lib/action_dispatch/routing/route_set.rb +79 -56
- data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
- data/lib/action_dispatch/routing/url_for.rb +130 -125
- data/lib/action_dispatch/routing.rb +150 -148
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_test_case.rb +91 -81
- data/lib/action_dispatch/system_testing/browser.rb +16 -23
- data/lib/action_dispatch/system_testing/driver.rb +2 -0
- data/lib/action_dispatch/system_testing/server.rb +2 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +34 -23
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
- data/lib/action_dispatch/testing/assertion_response.rb +9 -7
- data/lib/action_dispatch/testing/assertions/response.rb +52 -25
- data/lib/action_dispatch/testing/assertions/routing.rb +168 -87
- data/lib/action_dispatch/testing/assertions.rb +2 -0
- data/lib/action_dispatch/testing/integration.rb +233 -223
- data/lib/action_dispatch/testing/request_encoder.rb +11 -9
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +11 -8
- data/lib/action_dispatch/testing/test_request.rb +3 -1
- data/lib/action_dispatch/testing/test_response.rb +27 -26
- data/lib/action_dispatch.rb +36 -32
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +17 -16
- metadata +36 -32
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -31
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
require "rack/session/abstract/id"
|
|
4
6
|
require "action_controller/metal/exceptions"
|
|
5
7
|
require "active_support/security_utils"
|
|
@@ -11,51 +13,53 @@ module ActionController # :nodoc:
|
|
|
11
13
|
class InvalidCrossOriginRequest < ActionControllerError # :nodoc:
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
#
|
|
16
|
+
# # Action Controller Request Forgery Protection
|
|
15
17
|
#
|
|
16
|
-
# Controller actions are protected from Cross-Site Request Forgery (CSRF)
|
|
17
|
-
# by including a token in the rendered HTML for your application. This
|
|
18
|
-
# stored as a random string in the session, to which an attacker does
|
|
19
|
-
# access. When a request reaches your application,
|
|
20
|
-
# token with the token in the session. All requests are checked except
|
|
21
|
-
# as these should be idempotent. Keep in mind that all
|
|
22
|
-
# are CSRF protected by default, including JavaScript
|
|
18
|
+
# Controller actions are protected from Cross-Site Request Forgery (CSRF)
|
|
19
|
+
# attacks by including a token in the rendered HTML for your application. This
|
|
20
|
+
# token is stored as a random string in the session, to which an attacker does
|
|
21
|
+
# not have access. When a request reaches your application, Rails verifies the
|
|
22
|
+
# received token with the token in the session. All requests are checked except
|
|
23
|
+
# GET requests as these should be idempotent. Keep in mind that all
|
|
24
|
+
# session-oriented requests are CSRF protected by default, including JavaScript
|
|
25
|
+
# and HTML requests.
|
|
23
26
|
#
|
|
24
27
|
# Since HTML and JavaScript requests are typically made from the browser, we
|
|
25
|
-
# need to ensure to verify request authenticity for the web browser. We can
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
+
# need to ensure to verify request authenticity for the web browser. We can use
|
|
29
|
+
# session-oriented authentication for these types of requests, by using the
|
|
30
|
+
# `protect_from_forgery` method in our controllers.
|
|
28
31
|
#
|
|
29
32
|
# GET requests are not protected since they don't have side effects like writing
|
|
30
33
|
# to the database and don't leak sensitive information. JavaScript requests are
|
|
31
|
-
# an exception: a third-party site can use a <script> tag to reference a
|
|
32
|
-
# URL on your site. When your JavaScript response loads on their
|
|
33
|
-
# With carefully crafted JavaScript on their end, sensitive
|
|
34
|
-
# response may be extracted. To prevent this, only
|
|
35
|
-
# Ajax) requests are allowed to make requests
|
|
34
|
+
# an exception: a third-party site can use a <script> tag to reference a
|
|
35
|
+
# JavaScript URL on your site. When your JavaScript response loads on their
|
|
36
|
+
# site, it executes. With carefully crafted JavaScript on their end, sensitive
|
|
37
|
+
# data in your JavaScript response may be extracted. To prevent this, only
|
|
38
|
+
# XmlHttpRequest (known as XHR or Ajax) requests are allowed to make requests
|
|
39
|
+
# for JavaScript responses.
|
|
36
40
|
#
|
|
37
41
|
# Subclasses of ActionController::Base are protected by default with the
|
|
38
|
-
#
|
|
42
|
+
# `:exception` strategy, which raises an
|
|
39
43
|
# ActionController::InvalidAuthenticityToken error on unverified requests.
|
|
40
44
|
#
|
|
41
45
|
# APIs may want to disable this behavior since they are typically designed to be
|
|
42
|
-
# state-less: that is, the request API client handles the session instead of
|
|
43
|
-
# One way to achieve this is to use the
|
|
46
|
+
# state-less: that is, the request API client handles the session instead of
|
|
47
|
+
# Rails. One way to achieve this is to use the `:null_session` strategy instead,
|
|
44
48
|
# which allows unverified requests to be handled, but with an empty session:
|
|
45
49
|
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
50
|
+
# class ApplicationController < ActionController::Base
|
|
51
|
+
# protect_from_forgery with: :null_session
|
|
52
|
+
# end
|
|
49
53
|
#
|
|
50
|
-
# Note that API only applications don't include this module or a session
|
|
51
|
-
# by default, and so don't require CSRF protection to be configured.
|
|
54
|
+
# Note that API only applications don't include this module or a session
|
|
55
|
+
# middleware by default, and so don't require CSRF protection to be configured.
|
|
52
56
|
#
|
|
53
|
-
# The token parameter is named
|
|
54
|
-
# value of this token must be added to every layout that renders forms by
|
|
55
|
-
#
|
|
57
|
+
# The token parameter is named `authenticity_token` by default. The name and
|
|
58
|
+
# value of this token must be added to every layout that renders forms by
|
|
59
|
+
# including `csrf_meta_tags` in the HTML `head`.
|
|
56
60
|
#
|
|
57
|
-
# Learn more about CSRF attacks and securing your application in the
|
|
58
|
-
#
|
|
61
|
+
# Learn more about CSRF attacks and securing your application in the [Ruby on
|
|
62
|
+
# Rails Security Guide](https://guides.rubyonrails.org/security.html).
|
|
59
63
|
module RequestForgeryProtection
|
|
60
64
|
CSRF_TOKEN = "action_controller.csrf_token"
|
|
61
65
|
|
|
@@ -65,37 +69,41 @@ module ActionController # :nodoc:
|
|
|
65
69
|
include AbstractController::Callbacks
|
|
66
70
|
|
|
67
71
|
included do
|
|
68
|
-
# Sets the token parameter name for RequestForgery. Calling
|
|
69
|
-
# sets it to
|
|
70
|
-
|
|
72
|
+
# Sets the token parameter name for RequestForgery. Calling
|
|
73
|
+
# `protect_from_forgery` sets it to `:authenticity_token` by default.
|
|
74
|
+
singleton_class.delegate :request_forgery_protection_token, :request_forgery_protection_token=, to: :config
|
|
75
|
+
delegate :request_forgery_protection_token, :request_forgery_protection_token=, to: :config
|
|
71
76
|
self.request_forgery_protection_token ||= :authenticity_token
|
|
72
77
|
|
|
73
78
|
# Holds the class which implements the request forgery protection.
|
|
74
|
-
|
|
79
|
+
singleton_class.delegate :forgery_protection_strategy, :forgery_protection_strategy=, to: :config
|
|
80
|
+
delegate :forgery_protection_strategy, :forgery_protection_strategy=, to: :config
|
|
75
81
|
self.forgery_protection_strategy = nil
|
|
76
82
|
|
|
77
|
-
# Controls whether request forgery protection is turned on or not. Turned off by
|
|
78
|
-
|
|
83
|
+
# Controls whether request forgery protection is turned on or not. Turned off by
|
|
84
|
+
# default only in test mode.
|
|
85
|
+
singleton_class.delegate :allow_forgery_protection, :allow_forgery_protection=, to: :config
|
|
86
|
+
delegate :allow_forgery_protection, :allow_forgery_protection=, to: :config
|
|
79
87
|
self.allow_forgery_protection = true if allow_forgery_protection.nil?
|
|
80
88
|
|
|
81
89
|
# Controls whether a CSRF failure logs a warning. On by default.
|
|
82
|
-
|
|
90
|
+
singleton_class.delegate :log_warning_on_csrf_failure, :log_warning_on_csrf_failure=, to: :config
|
|
91
|
+
delegate :log_warning_on_csrf_failure, :log_warning_on_csrf_failure=, to: :config
|
|
83
92
|
self.log_warning_on_csrf_failure = true
|
|
84
93
|
|
|
85
94
|
# Controls whether the Origin header is checked in addition to the CSRF token.
|
|
86
|
-
|
|
95
|
+
singleton_class.delegate :forgery_protection_origin_check, :forgery_protection_origin_check=, to: :config
|
|
96
|
+
delegate :forgery_protection_origin_check, :forgery_protection_origin_check=, to: :config
|
|
87
97
|
self.forgery_protection_origin_check = false
|
|
88
98
|
|
|
89
99
|
# Controls whether form-action/method specific CSRF tokens are used.
|
|
90
|
-
|
|
100
|
+
singleton_class.delegate :per_form_csrf_tokens, :per_form_csrf_tokens=, to: :config
|
|
101
|
+
delegate :per_form_csrf_tokens, :per_form_csrf_tokens=, to: :config
|
|
91
102
|
self.per_form_csrf_tokens = false
|
|
92
103
|
|
|
93
|
-
# Controls whether forgery protection is enabled by default.
|
|
94
|
-
config_accessor :default_protect_from_forgery
|
|
95
|
-
self.default_protect_from_forgery = false
|
|
96
|
-
|
|
97
104
|
# The strategy to use for storing and retrieving CSRF tokens.
|
|
98
|
-
|
|
105
|
+
singleton_class.delegate :csrf_token_storage_strategy, :csrf_token_storage_strategy=, to: :config
|
|
106
|
+
delegate :csrf_token_storage_strategy, :csrf_token_storage_strategy=, to: :config
|
|
99
107
|
self.csrf_token_storage_strategy = SessionStore.new
|
|
100
108
|
|
|
101
109
|
helper_method :form_authenticity_token
|
|
@@ -103,79 +111,98 @@ module ActionController # :nodoc:
|
|
|
103
111
|
end
|
|
104
112
|
|
|
105
113
|
module ClassMethods
|
|
106
|
-
# Turn on request forgery protection. Bear in mind that GET and HEAD requests
|
|
114
|
+
# Turn on request forgery protection. Bear in mind that GET and HEAD requests
|
|
115
|
+
# are not checked.
|
|
107
116
|
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
117
|
+
# class ApplicationController < ActionController::Base
|
|
118
|
+
# protect_from_forgery
|
|
119
|
+
# end
|
|
111
120
|
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
121
|
+
# class FooController < ApplicationController
|
|
122
|
+
# protect_from_forgery except: :index
|
|
123
|
+
# end
|
|
115
124
|
#
|
|
116
|
-
# You can disable forgery protection on a controller using
|
|
125
|
+
# You can disable forgery protection on a controller using
|
|
126
|
+
# skip_forgery_protection:
|
|
117
127
|
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
128
|
+
# class BarController < ApplicationController
|
|
129
|
+
# skip_forgery_protection
|
|
130
|
+
# end
|
|
121
131
|
#
|
|
122
132
|
# Valid Options:
|
|
123
133
|
#
|
|
124
|
-
# *
|
|
125
|
-
#
|
|
126
|
-
# *
|
|
127
|
-
#
|
|
128
|
-
#
|
|
134
|
+
# * `:only` / `:except` - Only apply forgery protection to a subset of
|
|
135
|
+
# actions. For example `only: [ :create, :create_all ]`.
|
|
136
|
+
# * `:if` / `:unless` - Turn off the forgery protection entirely depending on
|
|
137
|
+
# the passed Proc or method reference.
|
|
138
|
+
# * `:prepend` - By default, the verification of the authentication token will
|
|
139
|
+
# be added at the position of the protect_from_forgery call in your
|
|
140
|
+
# application. This means any callbacks added before are run first. This is
|
|
141
|
+
# useful when you want your forgery protection to depend on other callbacks,
|
|
142
|
+
# like authentication methods (Oauth vs Cookie auth).
|
|
143
|
+
#
|
|
144
|
+
# If you need to add verification to the beginning of the callback chain,
|
|
145
|
+
# use `prepend: true`.
|
|
146
|
+
# * `:with` - Set the method to handle unverified request. Note if
|
|
147
|
+
# `default_protect_from_forgery` is true, Rails call protect_from_forgery
|
|
148
|
+
# with `with :exception`.
|
|
129
149
|
#
|
|
130
|
-
# If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
|
|
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>.
|
|
133
150
|
#
|
|
134
151
|
# Built-in unverified request handling methods are:
|
|
135
|
-
# * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
|
|
136
|
-
# * <tt>:reset_session</tt> - Resets the session.
|
|
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
152
|
#
|
|
139
|
-
#
|
|
153
|
+
# * `:exception` - Raises ActionController::InvalidAuthenticityToken
|
|
154
|
+
# exception.
|
|
155
|
+
# * `:reset_session` - Resets the session.
|
|
156
|
+
# * `:null_session` - Provides an empty session during request but doesn't
|
|
157
|
+
# reset it completely. Used as default if `:with` option is not specified.
|
|
140
158
|
#
|
|
141
|
-
# class CustomStrategy
|
|
142
|
-
# def initialize(controller)
|
|
143
|
-
# @controller = controller
|
|
144
|
-
# end
|
|
145
159
|
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
160
|
+
# You can also implement custom strategy classes for unverified request
|
|
161
|
+
# handling:
|
|
162
|
+
#
|
|
163
|
+
# class CustomStrategy
|
|
164
|
+
# def initialize(controller)
|
|
165
|
+
# @controller = controller
|
|
166
|
+
# end
|
|
167
|
+
#
|
|
168
|
+
# def handle_unverified_request
|
|
169
|
+
# # Custom behavior for unverfied request
|
|
170
|
+
# end
|
|
171
|
+
# end
|
|
172
|
+
#
|
|
173
|
+
# class ApplicationController < ActionController::Base
|
|
174
|
+
# protect_from_forgery with: CustomStrategy
|
|
175
|
+
# end
|
|
176
|
+
#
|
|
177
|
+
# * `:store` - Set the strategy to store and retrieve CSRF tokens.
|
|
150
178
|
#
|
|
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
179
|
#
|
|
156
180
|
# Built-in session token strategies are:
|
|
157
|
-
#
|
|
158
|
-
# *
|
|
181
|
+
#
|
|
182
|
+
# * `:session` - Store the CSRF token in the session. Used as default if
|
|
183
|
+
# `:store` option is not specified.
|
|
184
|
+
# * `:cookie` - Store the CSRF token in an encrypted cookie.
|
|
185
|
+
#
|
|
159
186
|
#
|
|
160
187
|
# You can also implement custom strategy classes for CSRF token storage:
|
|
161
188
|
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
189
|
+
# class CustomStore
|
|
190
|
+
# def fetch(request)
|
|
191
|
+
# # Return the token from a custom location
|
|
192
|
+
# end
|
|
166
193
|
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
194
|
+
# def store(request, csrf_token)
|
|
195
|
+
# # Store the token in a custom location
|
|
196
|
+
# end
|
|
170
197
|
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
198
|
+
# def reset(request)
|
|
199
|
+
# # Delete the stored session token
|
|
200
|
+
# end
|
|
173
201
|
# end
|
|
174
|
-
# end
|
|
175
202
|
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
203
|
+
# class ApplicationController < ActionController::Base
|
|
204
|
+
# protect_from_forgery store: CustomStore.new
|
|
205
|
+
# end
|
|
179
206
|
def protect_from_forgery(options = {})
|
|
180
207
|
options = options.reverse_merge(prepend: false)
|
|
181
208
|
|
|
@@ -190,9 +217,9 @@ module ActionController # :nodoc:
|
|
|
190
217
|
|
|
191
218
|
# Turn off request forgery protection. This is a wrapper for:
|
|
192
219
|
#
|
|
193
|
-
#
|
|
220
|
+
# skip_before_action :verify_authenticity_token
|
|
194
221
|
#
|
|
195
|
-
# See
|
|
222
|
+
# See `skip_before_action` for allowed options.
|
|
196
223
|
def skip_forgery_protection(options = {})
|
|
197
224
|
skip_before_action :verify_authenticity_token, options.reverse_merge(raise: false)
|
|
198
225
|
end
|
|
@@ -236,7 +263,8 @@ module ActionController # :nodoc:
|
|
|
236
263
|
@controller = controller
|
|
237
264
|
end
|
|
238
265
|
|
|
239
|
-
# This is the method that defines the application behavior when a request is
|
|
266
|
+
# This is the method that defines the application behavior when a request is
|
|
267
|
+
# found to be unverified.
|
|
240
268
|
def handle_unverified_request
|
|
241
269
|
request = @controller.request
|
|
242
270
|
request.session = NullSessionHash.new(request)
|
|
@@ -246,7 +274,7 @@ module ActionController # :nodoc:
|
|
|
246
274
|
end
|
|
247
275
|
|
|
248
276
|
private
|
|
249
|
-
class NullSessionHash < Rack::Session::Abstract::SessionHash
|
|
277
|
+
class NullSessionHash < Rack::Session::Abstract::SessionHash
|
|
250
278
|
def initialize(req)
|
|
251
279
|
super(nil, req)
|
|
252
280
|
@data = {}
|
|
@@ -265,7 +293,7 @@ module ActionController # :nodoc:
|
|
|
265
293
|
end
|
|
266
294
|
end
|
|
267
295
|
|
|
268
|
-
class NullCookieJar < ActionDispatch::Cookies::CookieJar
|
|
296
|
+
class NullCookieJar < ActionDispatch::Cookies::CookieJar
|
|
269
297
|
def write(*)
|
|
270
298
|
# nothing
|
|
271
299
|
end
|
|
@@ -344,7 +372,7 @@ module ActionController # :nodoc:
|
|
|
344
372
|
|
|
345
373
|
def initialize(...)
|
|
346
374
|
super
|
|
347
|
-
@
|
|
375
|
+
@_marked_for_same_origin_verification = nil
|
|
348
376
|
end
|
|
349
377
|
|
|
350
378
|
def reset_csrf_token(request) # :doc:
|
|
@@ -358,16 +386,15 @@ module ActionController # :nodoc:
|
|
|
358
386
|
end
|
|
359
387
|
|
|
360
388
|
private
|
|
361
|
-
# The actual before_action that is used to verify the CSRF token.
|
|
362
|
-
#
|
|
363
|
-
#
|
|
364
|
-
# <tt><script></tt> verification.
|
|
389
|
+
# The actual before_action that is used to verify the CSRF token. Don't override
|
|
390
|
+
# this directly. Provide your own forgery protection strategy instead. If you
|
|
391
|
+
# override, you'll disable same-origin `<script>` verification.
|
|
365
392
|
#
|
|
366
|
-
# Lean on the protect_from_forgery declaration to mark which actions are
|
|
367
|
-
#
|
|
368
|
-
#
|
|
369
|
-
#
|
|
370
|
-
#
|
|
393
|
+
# Lean on the protect_from_forgery declaration to mark which actions are due for
|
|
394
|
+
# same-origin request verification. If protect_from_forgery is enabled on an
|
|
395
|
+
# action, this before_action flags its after_action to verify that JavaScript
|
|
396
|
+
# responses are for XHR requests, ensuring they follow the browser's same-origin
|
|
397
|
+
# policy.
|
|
371
398
|
def verify_authenticity_token # :doc:
|
|
372
399
|
mark_for_same_origin_verification!
|
|
373
400
|
|
|
@@ -378,7 +405,7 @@ module ActionController # :nodoc:
|
|
|
378
405
|
end
|
|
379
406
|
end
|
|
380
407
|
|
|
381
|
-
def handle_unverified_request
|
|
408
|
+
def handle_unverified_request
|
|
382
409
|
protection_strategy = forgery_protection_strategy.new(self)
|
|
383
410
|
|
|
384
411
|
if protection_strategy.respond_to?(:warning_message)
|
|
@@ -388,7 +415,7 @@ module ActionController # :nodoc:
|
|
|
388
415
|
protection_strategy.handle_unverified_request
|
|
389
416
|
end
|
|
390
417
|
|
|
391
|
-
def unverified_request_warning_message
|
|
418
|
+
def unverified_request_warning_message
|
|
392
419
|
if valid_request_origin?
|
|
393
420
|
"Can't verify CSRF token authenticity."
|
|
394
421
|
else
|
|
@@ -396,7 +423,6 @@ module ActionController # :nodoc:
|
|
|
396
423
|
end
|
|
397
424
|
end
|
|
398
425
|
|
|
399
|
-
# :nodoc:
|
|
400
426
|
CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
|
|
401
427
|
"<script> tag on another site requested protected JavaScript. " \
|
|
402
428
|
"If you know what you're doing, go ahead and disable forgery " \
|
|
@@ -404,9 +430,9 @@ module ActionController # :nodoc:
|
|
|
404
430
|
private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
|
|
405
431
|
# :startdoc:
|
|
406
432
|
|
|
407
|
-
# If
|
|
408
|
-
# forgery protection enabled for this request) then also verify that
|
|
409
|
-
#
|
|
433
|
+
# If `verify_authenticity_token` was run (indicating that we have
|
|
434
|
+
# forgery protection enabled for this request) then also verify that we aren't
|
|
435
|
+
# serving an unauthorized cross-origin response.
|
|
410
436
|
def verify_same_origin_request # :doc:
|
|
411
437
|
if marked_for_same_origin_verification? && non_xhr_javascript_response?
|
|
412
438
|
if logger && log_warning_on_csrf_failure
|
|
@@ -418,13 +444,13 @@ module ActionController # :nodoc:
|
|
|
418
444
|
|
|
419
445
|
# GET requests are checked for cross-origin JavaScript after rendering.
|
|
420
446
|
def mark_for_same_origin_verification! # :doc:
|
|
421
|
-
@
|
|
447
|
+
@_marked_for_same_origin_verification = request.get?
|
|
422
448
|
end
|
|
423
449
|
|
|
424
|
-
# If the
|
|
425
|
-
#
|
|
450
|
+
# If the `verify_authenticity_token` before_action ran, verify that JavaScript
|
|
451
|
+
# responses are only served to same-origin GET requests.
|
|
426
452
|
def marked_for_same_origin_verification? # :doc:
|
|
427
|
-
@
|
|
453
|
+
@_marked_for_same_origin_verification ||= false
|
|
428
454
|
end
|
|
429
455
|
|
|
430
456
|
# Check for cross-origin JavaScript responses.
|
|
@@ -436,11 +462,13 @@ module ActionController # :nodoc:
|
|
|
436
462
|
|
|
437
463
|
# Returns true or false if a request is verified. Checks:
|
|
438
464
|
#
|
|
439
|
-
# *
|
|
440
|
-
# *
|
|
441
|
-
#
|
|
465
|
+
# * Is it a GET or HEAD request? GETs should be safe and idempotent
|
|
466
|
+
# * Does the form_authenticity_token match the given token value from the
|
|
467
|
+
# params?
|
|
468
|
+
# * Does the `X-CSRF-Token` header match the form_authenticity_token?
|
|
469
|
+
#
|
|
442
470
|
def verified_request? # :doc:
|
|
443
|
-
|
|
471
|
+
request.get? || request.head? || !protect_against_forgery? ||
|
|
444
472
|
(valid_request_origin? && any_authenticity_token_valid?)
|
|
445
473
|
end
|
|
446
474
|
|
|
@@ -461,9 +489,8 @@ module ActionController # :nodoc:
|
|
|
461
489
|
masked_authenticity_token(form_options: form_options)
|
|
462
490
|
end
|
|
463
491
|
|
|
464
|
-
# Creates a masked version of the authenticity token that varies
|
|
465
|
-
#
|
|
466
|
-
# like BREACH.
|
|
492
|
+
# Creates a masked version of the authenticity token that varies on each
|
|
493
|
+
# request. The masking is used to mitigate SSL attacks like BREACH.
|
|
467
494
|
def masked_authenticity_token(form_options: {})
|
|
468
495
|
action, method = form_options.values_at(:action, :method)
|
|
469
496
|
|
|
@@ -477,11 +504,10 @@ module ActionController # :nodoc:
|
|
|
477
504
|
mask_token(raw_token)
|
|
478
505
|
end
|
|
479
506
|
|
|
480
|
-
# Checks the client's masked token to see if it matches the
|
|
481
|
-
#
|
|
482
|
-
# +masked_authenticity_token+.
|
|
507
|
+
# Checks the client's masked token to see if it matches the session token.
|
|
508
|
+
# Essentially the inverse of `masked_authenticity_token`.
|
|
483
509
|
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
|
|
484
|
-
if encoded_masked_token.
|
|
510
|
+
if !encoded_masked_token.is_a?(String) || encoded_masked_token.empty?
|
|
485
511
|
return false
|
|
486
512
|
end
|
|
487
513
|
|
|
@@ -491,14 +517,12 @@ module ActionController # :nodoc:
|
|
|
491
517
|
return false
|
|
492
518
|
end
|
|
493
519
|
|
|
494
|
-
# See if it's actually a masked token or not. In order to
|
|
495
|
-
#
|
|
496
|
-
# tokens that we've issued without error.
|
|
520
|
+
# See if it's actually a masked token or not. In order to deploy this code, we
|
|
521
|
+
# should be able to handle any unmasked tokens that we've issued without error.
|
|
497
522
|
|
|
498
523
|
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
|
|
499
|
-
# This is actually an unmasked token. This is expected if
|
|
500
|
-
#
|
|
501
|
-
# happening shortly after installing this gem.
|
|
524
|
+
# This is actually an unmasked token. This is expected if you have just upgraded
|
|
525
|
+
# to masked tokens, but should stop happening shortly after installing this gem.
|
|
502
526
|
compare_with_real_token masked_token
|
|
503
527
|
|
|
504
528
|
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
|
@@ -513,8 +537,7 @@ module ActionController # :nodoc:
|
|
|
513
537
|
end
|
|
514
538
|
|
|
515
539
|
def unmask_token(masked_token) # :doc:
|
|
516
|
-
# Split the token into the one-time pad and the encrypted
|
|
517
|
-
# value and decrypt it.
|
|
540
|
+
# Split the token into the one-time pad and the encrypted value and decrypt it.
|
|
518
541
|
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
|
|
519
542
|
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
|
|
520
543
|
xor_byte_strings(one_time_pad, encrypted_csrf_token)
|
|
@@ -605,9 +628,10 @@ module ActionController # :nodoc:
|
|
|
605
628
|
If you cannot change the referrer policy, you can disable origin checking with the
|
|
606
629
|
Rails.application.config.action_controller.forgery_protection_origin_check setting.
|
|
607
630
|
MSG
|
|
631
|
+
private_constant :NULL_ORIGIN_MESSAGE
|
|
608
632
|
|
|
609
|
-
# Checks if the request originated from the same origin by looking at the
|
|
610
|
-
#
|
|
633
|
+
# Checks if the request originated from the same origin by looking at the Origin
|
|
634
|
+
# header.
|
|
611
635
|
def valid_request_origin? # :doc:
|
|
612
636
|
if forgery_protection_origin_check
|
|
613
637
|
# We accept blank origin headers because some user agents don't send it.
|
|
@@ -618,20 +642,35 @@ module ActionController # :nodoc:
|
|
|
618
642
|
end
|
|
619
643
|
end
|
|
620
644
|
|
|
621
|
-
def normalize_action_path(action_path)
|
|
645
|
+
def normalize_action_path(action_path)
|
|
622
646
|
uri = URI.parse(action_path)
|
|
647
|
+
|
|
648
|
+
if uri.relative? && (action_path.blank? || !action_path.start_with?("/"))
|
|
649
|
+
normalize_relative_action_path(uri.path)
|
|
650
|
+
else
|
|
651
|
+
uri.path.chomp("/")
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def normalize_relative_action_path(rel_action_path)
|
|
656
|
+
uri = URI.parse(request.path)
|
|
657
|
+
# add the action path to the request.path
|
|
658
|
+
uri.path += "/#{rel_action_path}"
|
|
659
|
+
# relative path with "./path"
|
|
660
|
+
uri.path.gsub!("/./", "/")
|
|
661
|
+
|
|
623
662
|
uri.path.chomp("/")
|
|
624
663
|
end
|
|
625
664
|
|
|
626
|
-
def generate_csrf_token
|
|
665
|
+
def generate_csrf_token
|
|
627
666
|
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
|
|
628
667
|
end
|
|
629
668
|
|
|
630
|
-
def encode_csrf_token(csrf_token)
|
|
669
|
+
def encode_csrf_token(csrf_token)
|
|
631
670
|
Base64.urlsafe_encode64(csrf_token, padding: false)
|
|
632
671
|
end
|
|
633
672
|
|
|
634
|
-
def decode_csrf_token(encoded_csrf_token)
|
|
673
|
+
def decode_csrf_token(encoded_csrf_token)
|
|
635
674
|
Base64.urlsafe_decode64(encoded_csrf_token)
|
|
636
675
|
end
|
|
637
676
|
end
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
module ActionController # :nodoc:
|
|
4
|
-
#
|
|
6
|
+
# # Action Controller Rescue
|
|
5
7
|
#
|
|
6
8
|
# This module is responsible for providing
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
9
|
+
# [rescue_from](rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from) to
|
|
10
|
+
# controllers, wrapping actions to handle configured errors, and configuring
|
|
11
|
+
# when detailed exceptions must be shown.
|
|
10
12
|
module Rescue
|
|
11
13
|
extend ActiveSupport::Concern
|
|
12
14
|
include ActiveSupport::Rescuable
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
module ClassMethods
|
|
17
|
+
def handler_for_rescue(exception, ...) # :nodoc:
|
|
18
|
+
if handler = super
|
|
19
|
+
ActiveSupport::Notifications.instrument("rescue_from_callback.action_controller", exception: exception)
|
|
20
|
+
handler
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Override this method if you want to customize when detailed exceptions must be
|
|
26
|
+
# shown. This method is only called when `consider_all_requests_local` is
|
|
27
|
+
# `false`. By default, it returns `false`, but someone may set it to
|
|
28
|
+
# `request.local?` so local requests in production still show the detailed
|
|
29
|
+
# exception pages.
|
|
19
30
|
def show_detailed_exceptions?
|
|
20
31
|
false
|
|
21
32
|
end
|