actionpack 5.2.1 → 7.0.2.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 +264 -220
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +24 -4
- data/lib/abstract_controller/caching/fragments.rb +8 -24
- data/lib/abstract_controller/caching.rb +2 -2
- data/lib/abstract_controller/callbacks.rb +34 -8
- data/lib/abstract_controller/collector.rb +5 -4
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +107 -90
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +12 -5
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api.rb +5 -4
- data/lib/action_controller/base.rb +6 -9
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/log_subscriber.rb +13 -9
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +57 -6
- data/lib/action_controller/metal/content_security_policy.rb +2 -3
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +9 -18
- 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 +55 -12
- data/lib/action_controller/metal/flash.rb +10 -6
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +15 -6
- data/lib/action_controller/metal/http_authentication.rb +41 -39
- data/lib/action_controller/metal/implicit_render.rb +5 -15
- data/lib/action_controller/metal/instrumentation.rb +59 -55
- data/lib/action_controller/metal/live.rb +80 -33
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +22 -7
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +50 -31
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +93 -23
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +14 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
- data/lib/action_controller/metal/rescue.rb +2 -2
- data/lib/action_controller/metal/streaming.rb +1 -4
- data/lib/action_controller/metal/strong_parameters.rb +236 -88
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/metal.rb +16 -17
- data/lib/action_controller/railtie.rb +49 -6
- 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 +98 -68
- data/lib/action_controller.rb +4 -5
- data/lib/action_dispatch/http/cache.rb +45 -32
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +69 -56
- data/lib/action_dispatch/http/filter_parameters.rb +14 -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 +44 -16
- data/lib/action_dispatch/http/mime_type.rb +47 -30
- data/lib/action_dispatch/http/parameters.rb +18 -27
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +49 -35
- data/lib/action_dispatch/http/response.rb +34 -26
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +86 -94
- data/lib/action_dispatch/journey/formatter.rb +55 -31
- data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
- data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
- data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +83 -16
- 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 +42 -34
- data/lib/action_dispatch/journey/route.rb +14 -31
- data/lib/action_dispatch/journey/router/utils.rb +16 -14
- data/lib/action_dispatch/journey/router.rb +27 -35
- data/lib/action_dispatch/journey/routes.rb +3 -5
- data/lib/action_dispatch/journey/scanner.rb +10 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -4
- 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/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +136 -113
- data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
- data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
- data/lib/action_dispatch/middleware/executor.rb +4 -1
- data/lib/action_dispatch/middleware/flash.rb +10 -12
- data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
- data/lib/action_dispatch/middleware/request_id.rb +5 -6
- data/lib/action_dispatch/middleware/server_timing.rb +33 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
- 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 +20 -11
- data/lib/action_dispatch/middleware/ssl.rb +20 -15
- data/lib/action_dispatch/middleware/stack.rb +79 -7
- data/lib/action_dispatch/middleware/static.rb +150 -94
- 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 +6 -11
- 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 +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
- 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 +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
- data/lib/action_dispatch/railtie.rb +16 -4
- data/lib/action_dispatch/request/session.rb +59 -22
- data/lib/action_dispatch/request/utils.rb +28 -2
- data/lib/action_dispatch/routing/inspector.rb +102 -54
- data/lib/action_dispatch/routing/mapper.rb +184 -156
- data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
- data/lib/action_dispatch/routing/redirection.rb +4 -6
- data/lib/action_dispatch/routing/route_set.rb +83 -73
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +2 -3
- data/lib/action_dispatch/routing.rb +23 -22
- data/lib/action_dispatch/system_test_case.rb +65 -16
- data/lib/action_dispatch/system_testing/browser.rb +43 -16
- data/lib/action_dispatch/system_testing/driver.rb +42 -10
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
- 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 +3 -6
- data/lib/action_dispatch/testing/integration.rb +61 -30
- data/lib/action_dispatch/testing/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_process.rb +8 -6
- 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 +15 -7
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +44 -25
- 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
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
require "uri"
|
5
|
+
require "active_support/actionable_error"
|
6
|
+
|
7
|
+
module ActionDispatch
|
8
|
+
class ActionableExceptions # :nodoc:
|
9
|
+
cattr_accessor :endpoint, default: "/rails/actions"
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
request = ActionDispatch::Request.new(env)
|
17
|
+
return @app.call(env) unless actionable_request?(request)
|
18
|
+
|
19
|
+
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
|
20
|
+
|
21
|
+
redirect_to request.params[:location]
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def actionable_request?(request)
|
26
|
+
request.get_header("action_dispatch.show_detailed_exceptions") && request.post? && request.path == endpoint
|
27
|
+
end
|
28
|
+
|
29
|
+
def redirect_to(location)
|
30
|
+
uri = URI.parse location
|
31
|
+
|
32
|
+
if uri.relative? || uri.scheme == "http" || uri.scheme == "https"
|
33
|
+
body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
|
34
|
+
else
|
35
|
+
return [400, { "Content-Type" => "text/plain" }, ["Invalid redirection URI"]]
|
36
|
+
end
|
37
|
+
|
38
|
+
[302, {
|
39
|
+
"Content-Type" => "text/html; charset=#{Response.default_charset}",
|
40
|
+
"Content-Length" => body.bytesize.to_s,
|
41
|
+
"Location" => location,
|
42
|
+
}, [body]]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -7,9 +7,9 @@ require "active_support/json"
|
|
7
7
|
require "rack/utils"
|
8
8
|
|
9
9
|
module ActionDispatch
|
10
|
-
|
10
|
+
module RequestCookieMethods
|
11
11
|
def cookie_jar
|
12
|
-
fetch_header("action_dispatch.cookies"
|
12
|
+
fetch_header("action_dispatch.cookies") do
|
13
13
|
self.cookie_jar = Cookies::CookieJar.build(self, cookies)
|
14
14
|
end
|
15
15
|
end
|
@@ -22,11 +22,11 @@ module ActionDispatch
|
|
22
22
|
}
|
23
23
|
|
24
24
|
def have_cookie_jar?
|
25
|
-
has_header? "action_dispatch.cookies"
|
25
|
+
has_header? "action_dispatch.cookies"
|
26
26
|
end
|
27
27
|
|
28
28
|
def cookie_jar=(jar)
|
29
|
-
set_header "action_dispatch.cookies"
|
29
|
+
set_header "action_dispatch.cookies", jar
|
30
30
|
end
|
31
31
|
|
32
32
|
def key_generator
|
@@ -61,10 +61,6 @@ module ActionDispatch
|
|
61
61
|
get_header Cookies::SIGNED_COOKIE_DIGEST
|
62
62
|
end
|
63
63
|
|
64
|
-
def secret_token
|
65
|
-
get_header Cookies::SECRET_TOKEN
|
66
|
-
end
|
67
|
-
|
68
64
|
def secret_key_base
|
69
65
|
get_header Cookies::SECRET_KEY_BASE
|
70
66
|
end
|
@@ -73,6 +69,10 @@ module ActionDispatch
|
|
73
69
|
get_header Cookies::COOKIES_SERIALIZER
|
74
70
|
end
|
75
71
|
|
72
|
+
def cookies_same_site_protection
|
73
|
+
get_header(Cookies::COOKIES_SAME_SITE_PROTECTION) || Proc.new { }
|
74
|
+
end
|
75
|
+
|
76
76
|
def cookies_digest
|
77
77
|
get_header Cookies::COOKIES_DIGEST
|
78
78
|
end
|
@@ -81,14 +81,21 @@ module ActionDispatch
|
|
81
81
|
get_header Cookies::COOKIES_ROTATIONS
|
82
82
|
end
|
83
83
|
|
84
|
+
def use_cookies_with_metadata
|
85
|
+
get_header Cookies::USE_COOKIES_WITH_METADATA
|
86
|
+
end
|
87
|
+
|
84
88
|
# :startdoc:
|
85
89
|
end
|
86
90
|
|
87
|
-
|
91
|
+
ActiveSupport.on_load(:action_dispatch_request) do
|
92
|
+
include RequestCookieMethods
|
93
|
+
end
|
94
|
+
|
95
|
+
# Read and write data to cookies through ActionController#cookies.
|
88
96
|
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
# the cookie object itself back, just the value it holds.
|
97
|
+
# When reading cookie data, the data is read from the HTTP request header, Cookie.
|
98
|
+
# When writing cookie data, the data is sent out in the HTTP response header, Set-Cookie.
|
92
99
|
#
|
93
100
|
# Examples of writing:
|
94
101
|
#
|
@@ -96,7 +103,7 @@ module ActionDispatch
|
|
96
103
|
# # This cookie will be deleted when the user's browser is closed.
|
97
104
|
# cookies[:user_name] = "david"
|
98
105
|
#
|
99
|
-
# # Cookie values are String
|
106
|
+
# # Cookie values are String-based. Other data types need to be serialized.
|
100
107
|
# cookies[:lat_lon] = JSON.generate([47.68, -122.37])
|
101
108
|
#
|
102
109
|
# # Sets a cookie that expires in 1 hour.
|
@@ -150,8 +157,10 @@ module ActionDispatch
|
|
150
157
|
# * <tt>:domain</tt> - The domain for which this cookie applies so you can
|
151
158
|
# restrict to the domain level. If you use a schema like www.example.com
|
152
159
|
# and want to share session with user.example.com set <tt>:domain</tt>
|
153
|
-
# to <tt>:all</tt>.
|
154
|
-
#
|
160
|
+
# to <tt>:all</tt>. To support multiple domains, provide an array, and
|
161
|
+
# the first domain matching <tt>request.host</tt> will be used. Make
|
162
|
+
# sure to specify the <tt>:domain</tt> option with <tt>:all</tt> or
|
163
|
+
# <tt>Array</tt> again when deleting cookies.
|
155
164
|
#
|
156
165
|
# domain: nil # Does not set cookie domain. (default)
|
157
166
|
# domain: :all # Allow the cookie for the top most level
|
@@ -168,20 +177,21 @@ module ActionDispatch
|
|
168
177
|
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
|
169
178
|
# only HTTP. Defaults to +false+.
|
170
179
|
class Cookies
|
171
|
-
HTTP_HEADER = "Set-Cookie"
|
172
|
-
GENERATOR_KEY = "action_dispatch.key_generator"
|
173
|
-
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt"
|
174
|
-
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt"
|
175
|
-
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt"
|
176
|
-
AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt"
|
177
|
-
USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
|
178
|
-
ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
|
179
|
-
SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
180
|
+
HTTP_HEADER = "Set-Cookie"
|
181
|
+
GENERATOR_KEY = "action_dispatch.key_generator"
|
182
|
+
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt"
|
183
|
+
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt"
|
184
|
+
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt"
|
185
|
+
AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt"
|
186
|
+
USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
|
187
|
+
ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
|
188
|
+
SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
|
189
|
+
SECRET_KEY_BASE = "action_dispatch.secret_key_base"
|
190
|
+
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
|
191
|
+
COOKIES_DIGEST = "action_dispatch.cookies_digest"
|
192
|
+
COOKIES_ROTATIONS = "action_dispatch.cookies_rotations"
|
193
|
+
COOKIES_SAME_SITE_PROTECTION = "action_dispatch.cookies_same_site_protection"
|
194
|
+
USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata"
|
185
195
|
|
186
196
|
# Cookies can typically store 4096 bytes.
|
187
197
|
MAX_COOKIE_SIZE = 4096
|
@@ -210,9 +220,6 @@ module ActionDispatch
|
|
210
220
|
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
211
221
|
# cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
|
212
222
|
#
|
213
|
-
# If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
|
214
|
-
# legacy cookies signed with the old key generator will be transparently upgraded.
|
215
|
-
#
|
216
223
|
# This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
|
217
224
|
#
|
218
225
|
# Example:
|
@@ -228,9 +235,6 @@ module ActionDispatch
|
|
228
235
|
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
|
229
236
|
# If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
|
230
237
|
#
|
231
|
-
# If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
|
232
|
-
# legacy cookies signed with the old key generator will be transparently upgraded.
|
233
|
-
#
|
234
238
|
# If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
|
235
239
|
# are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
|
236
240
|
#
|
@@ -258,11 +262,6 @@ module ActionDispatch
|
|
258
262
|
end
|
259
263
|
|
260
264
|
private
|
261
|
-
|
262
|
-
def upgrade_legacy_signed_cookies?
|
263
|
-
request.secret_token.present? && request.secret_key_base.present?
|
264
|
-
end
|
265
|
-
|
266
265
|
def upgrade_legacy_hmac_aes_cbc_cookies?
|
267
266
|
request.secret_key_base.present? &&
|
268
267
|
request.encrypted_signed_cookie_salt.present? &&
|
@@ -270,6 +269,12 @@ module ActionDispatch
|
|
270
269
|
request.use_authenticated_cookie_encryption
|
271
270
|
end
|
272
271
|
|
272
|
+
def prepare_upgrade_legacy_hmac_aes_cbc_cookies?
|
273
|
+
request.secret_key_base.present? &&
|
274
|
+
request.authenticated_encrypted_cookie_salt.present? &&
|
275
|
+
!request.use_authenticated_cookie_encryption
|
276
|
+
end
|
277
|
+
|
273
278
|
def encrypted_cookie_cipher
|
274
279
|
request.encrypted_cookie_cipher || "aes-256-gcm"
|
275
280
|
end
|
@@ -279,7 +284,7 @@ module ActionDispatch
|
|
279
284
|
end
|
280
285
|
end
|
281
286
|
|
282
|
-
class CookieJar
|
287
|
+
class CookieJar # :nodoc:
|
283
288
|
include Enumerable, ChainedCookieJars
|
284
289
|
|
285
290
|
# This regular expression is used to split the levels of a domain.
|
@@ -297,9 +302,9 @@ module ActionDispatch
|
|
297
302
|
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
|
298
303
|
|
299
304
|
def self.build(req, cookies)
|
300
|
-
new(req)
|
301
|
-
|
302
|
-
|
305
|
+
jar = new(req)
|
306
|
+
jar.update(cookies)
|
307
|
+
jar
|
303
308
|
end
|
304
309
|
|
305
310
|
attr_reader :request
|
@@ -348,7 +353,7 @@ module ActionDispatch
|
|
348
353
|
|
349
354
|
def update_cookies_from_jar
|
350
355
|
request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
|
351
|
-
set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
|
356
|
+
set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) }
|
352
357
|
|
353
358
|
@cookies.update set_cookies if set_cookies
|
354
359
|
end
|
@@ -357,28 +362,6 @@ module ActionDispatch
|
|
357
362
|
@cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
|
358
363
|
end
|
359
364
|
|
360
|
-
def handle_options(options) # :nodoc:
|
361
|
-
if options[:expires].respond_to?(:from_now)
|
362
|
-
options[:expires] = options[:expires].from_now
|
363
|
-
end
|
364
|
-
|
365
|
-
options[:path] ||= "/"
|
366
|
-
|
367
|
-
if options[:domain] == :all || options[:domain] == "all"
|
368
|
-
# If there is a provided tld length then we use it otherwise default domain regexp.
|
369
|
-
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
|
370
|
-
|
371
|
-
# If host is not ip and matches domain regexp.
|
372
|
-
# (ip confirms to domain regexp so we explicitly check for ip)
|
373
|
-
options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
|
374
|
-
".#{$&}"
|
375
|
-
end
|
376
|
-
elsif options[:domain].is_a? Array
|
377
|
-
# If host matches one of the supplied domains without a dot in front of it.
|
378
|
-
options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
365
|
# Sets the cookie named +name+. The second argument may be the cookie's
|
383
366
|
# value or a hash of options as documented above.
|
384
367
|
def []=(name, options)
|
@@ -438,7 +421,6 @@ module ActionDispatch
|
|
438
421
|
mattr_accessor :always_write_cookie, default: false
|
439
422
|
|
440
423
|
private
|
441
|
-
|
442
424
|
def escape(string)
|
443
425
|
::Rack::Utils.escape(string)
|
444
426
|
end
|
@@ -457,7 +439,35 @@ module ActionDispatch
|
|
457
439
|
end
|
458
440
|
|
459
441
|
def write_cookie?(cookie)
|
460
|
-
request.ssl? || !cookie[:secure] || always_write_cookie
|
442
|
+
request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
|
443
|
+
end
|
444
|
+
|
445
|
+
def handle_options(options)
|
446
|
+
if options[:expires].respond_to?(:from_now)
|
447
|
+
options[:expires] = options[:expires].from_now
|
448
|
+
end
|
449
|
+
|
450
|
+
options[:path] ||= "/"
|
451
|
+
|
452
|
+
cookies_same_site_protection = request.cookies_same_site_protection
|
453
|
+
options[:same_site] ||= cookies_same_site_protection.call(request)
|
454
|
+
|
455
|
+
if options[:domain] == :all || options[:domain] == "all"
|
456
|
+
# If there is a provided tld length then we use it otherwise default domain regexp.
|
457
|
+
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
|
458
|
+
|
459
|
+
# If host is not ip and matches domain regexp.
|
460
|
+
# (ip confirms to domain regexp so we explicitly check for ip)
|
461
|
+
options[:domain] = if !request.host.match?(/^[\d.]+$/) && (request.host =~ domain_regexp)
|
462
|
+
".#{$&}"
|
463
|
+
end
|
464
|
+
elsif options[:domain].is_a? Array
|
465
|
+
# If host matches one of the supplied domains.
|
466
|
+
options[:domain] = options[:domain].find do |domain|
|
467
|
+
domain = domain.delete_prefix(".")
|
468
|
+
request.host == domain || request.host.end_with?(".#{domain}")
|
469
|
+
end
|
470
|
+
end
|
461
471
|
end
|
462
472
|
end
|
463
473
|
|
@@ -470,7 +480,13 @@ module ActionDispatch
|
|
470
480
|
|
471
481
|
def [](name)
|
472
482
|
if data = @parent_jar[name.to_s]
|
473
|
-
parse
|
483
|
+
result = parse(name, data, purpose: "cookie.#{name}")
|
484
|
+
|
485
|
+
if result.nil?
|
486
|
+
parse(name, data)
|
487
|
+
else
|
488
|
+
result
|
489
|
+
end
|
474
490
|
end
|
475
491
|
end
|
476
492
|
|
@@ -481,7 +497,7 @@ module ActionDispatch
|
|
481
497
|
options = { value: options }
|
482
498
|
end
|
483
499
|
|
484
|
-
commit(options)
|
500
|
+
commit(name, options)
|
485
501
|
@parent_jar[name] = options
|
486
502
|
end
|
487
503
|
|
@@ -490,28 +506,42 @@ module ActionDispatch
|
|
490
506
|
|
491
507
|
private
|
492
508
|
def expiry_options(options)
|
493
|
-
if
|
494
|
-
|
495
|
-
{ expires_in: options[:expires] }
|
496
|
-
else
|
497
|
-
{ expires_at: options[:expires] }
|
498
|
-
end
|
509
|
+
if options[:expires].respond_to?(:from_now)
|
510
|
+
{ expires_in: options[:expires] }
|
499
511
|
else
|
500
|
-
{}
|
512
|
+
{ expires_at: options[:expires] }
|
501
513
|
end
|
502
514
|
end
|
503
515
|
|
504
|
-
def
|
505
|
-
|
516
|
+
def cookie_metadata(name, options)
|
517
|
+
expiry_options(options).tap do |metadata|
|
518
|
+
metadata[:purpose] = "cookie.#{name}" if request.use_cookies_with_metadata
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
def parse(name, data, purpose: nil); data; end
|
523
|
+
def commit(name, options); end
|
506
524
|
end
|
507
525
|
|
508
526
|
class PermanentCookieJar < AbstractCookieJar # :nodoc:
|
509
527
|
private
|
510
|
-
def commit(options)
|
528
|
+
def commit(name, options)
|
511
529
|
options[:expires] = 20.years.from_now
|
512
530
|
end
|
513
531
|
end
|
514
532
|
|
533
|
+
class MarshalWithJsonFallback # :nodoc:
|
534
|
+
def self.load(value)
|
535
|
+
Marshal.load(value)
|
536
|
+
rescue TypeError => e
|
537
|
+
ActiveSupport::JSON.decode(value) rescue raise e
|
538
|
+
end
|
539
|
+
|
540
|
+
def self.dump(value)
|
541
|
+
Marshal.dump(value)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
515
545
|
class JsonSerializer # :nodoc:
|
516
546
|
def self.load(value)
|
517
547
|
ActiveSupport::JSON.decode(value)
|
@@ -523,7 +553,7 @@ module ActionDispatch
|
|
523
553
|
end
|
524
554
|
|
525
555
|
module SerializedCookieJars # :nodoc:
|
526
|
-
MARSHAL_SIGNATURE = "\x04\x08"
|
556
|
+
MARSHAL_SIGNATURE = "\x04\x08"
|
527
557
|
SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
|
528
558
|
|
529
559
|
protected
|
@@ -542,9 +572,13 @@ module ActionDispatch
|
|
542
572
|
if value
|
543
573
|
case
|
544
574
|
when needs_migration?(value)
|
545
|
-
|
575
|
+
Marshal.load(value).tap do |v|
|
576
|
+
self[name] = { value: v }
|
577
|
+
end
|
546
578
|
when rotate
|
547
|
-
|
579
|
+
serializer.load(value).tap do |v|
|
580
|
+
self[name] = { value: v }
|
581
|
+
end
|
548
582
|
else
|
549
583
|
serializer.load(value)
|
550
584
|
end
|
@@ -555,7 +589,7 @@ module ActionDispatch
|
|
555
589
|
serializer = request.cookies_serializer || :marshal
|
556
590
|
case serializer
|
557
591
|
when :marshal
|
558
|
-
|
592
|
+
MarshalWithJsonFallback
|
559
593
|
when :json, :hybrid
|
560
594
|
JsonSerializer
|
561
595
|
else
|
@@ -577,24 +611,21 @@ module ActionDispatch
|
|
577
611
|
secret = request.key_generator.generate_key(request.signed_cookie_salt)
|
578
612
|
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
|
579
613
|
|
580
|
-
request.cookies_rotations.signed.each do
|
614
|
+
request.cookies_rotations.signed.each do |(*secrets)|
|
615
|
+
options = secrets.extract_options!
|
581
616
|
@verifier.rotate(*secrets, serializer: SERIALIZER, **options)
|
582
617
|
end
|
583
|
-
|
584
|
-
if upgrade_legacy_signed_cookies?
|
585
|
-
@verifier.rotate request.secret_token, serializer: SERIALIZER
|
586
|
-
end
|
587
618
|
end
|
588
619
|
|
589
620
|
private
|
590
|
-
def parse(name, signed_message)
|
621
|
+
def parse(name, signed_message, purpose: nil)
|
591
622
|
deserialize(name) do |rotate|
|
592
|
-
@verifier.verified(signed_message, on_rotation: rotate)
|
623
|
+
@verifier.verified(signed_message, on_rotation: rotate, purpose: purpose)
|
593
624
|
end
|
594
625
|
end
|
595
626
|
|
596
|
-
def commit(options)
|
597
|
-
options[:value] = @verifier.generate(serialize(options[:value]),
|
627
|
+
def commit(name, options)
|
628
|
+
options[:value] = @verifier.generate(serialize(options[:value]), **cookie_metadata(name, options))
|
598
629
|
|
599
630
|
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
600
631
|
end
|
@@ -617,7 +648,8 @@ module ActionDispatch
|
|
617
648
|
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
|
618
649
|
end
|
619
650
|
|
620
|
-
request.cookies_rotations.encrypted.each do
|
651
|
+
request.cookies_rotations.encrypted.each do |(*secrets)|
|
652
|
+
options = secrets.extract_options!
|
621
653
|
@encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
|
622
654
|
end
|
623
655
|
|
@@ -627,37 +659,28 @@ module ActionDispatch
|
|
627
659
|
sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
|
628
660
|
|
629
661
|
@encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
|
630
|
-
|
662
|
+
elsif prepare_upgrade_legacy_hmac_aes_cbc_cookies?
|
663
|
+
future_cipher = encrypted_cookie_cipher
|
664
|
+
secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(future_cipher))
|
631
665
|
|
632
|
-
|
633
|
-
@legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
|
666
|
+
@encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER)
|
634
667
|
end
|
635
668
|
end
|
636
669
|
|
637
670
|
private
|
638
|
-
def parse(name, encrypted_message)
|
671
|
+
def parse(name, encrypted_message, purpose: nil)
|
639
672
|
deserialize(name) do |rotate|
|
640
|
-
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
|
673
|
+
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
|
641
674
|
end
|
642
675
|
rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
|
643
|
-
|
676
|
+
nil
|
644
677
|
end
|
645
678
|
|
646
|
-
def commit(options)
|
647
|
-
options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]),
|
679
|
+
def commit(name, options)
|
680
|
+
options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), **cookie_metadata(name, options))
|
648
681
|
|
649
682
|
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
650
683
|
end
|
651
|
-
|
652
|
-
def parse_legacy_signed_message(name, legacy_signed_message)
|
653
|
-
if defined?(@legacy_verifier)
|
654
|
-
deserialize(name) do |rotate|
|
655
|
-
rotate.call
|
656
|
-
|
657
|
-
@legacy_verifier.verified(legacy_signed_message)
|
658
|
-
end
|
659
|
-
end
|
660
|
-
end
|
661
684
|
end
|
662
685
|
|
663
686
|
def initialize(app)
|