omg-actionpack 8.0.0.alpha1
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 +7 -0
- data/CHANGELOG.md +129 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +57 -0
- data/lib/abstract_controller/asset_paths.rb +14 -0
- data/lib/abstract_controller/base.rb +299 -0
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +265 -0
- data/lib/abstract_controller/collector.rb +44 -0
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +243 -0
- data/lib/abstract_controller/logger.rb +16 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
- data/lib/abstract_controller/rendering.rb +126 -0
- data/lib/abstract_controller/translation.rb +42 -0
- data/lib/abstract_controller/url_for.rb +37 -0
- data/lib/abstract_controller.rb +36 -0
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +155 -0
- data/lib/action_controller/base.rb +332 -0
- data/lib/action_controller/caching.rb +49 -0
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +96 -0
- data/lib/action_controller/metal/allow_browser.rb +123 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +341 -0
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +20 -0
- data/lib/action_controller/metal/data_streaming.rb +154 -0
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
- data/lib/action_controller/metal/exceptions.rb +106 -0
- data/lib/action_controller/metal/flash.rb +67 -0
- data/lib/action_controller/metal/head.rb +67 -0
- data/lib/action_controller/metal/helpers.rb +129 -0
- data/lib/action_controller/metal/http_authentication.rb +565 -0
- data/lib/action_controller/metal/implicit_render.rb +67 -0
- data/lib/action_controller/metal/instrumentation.rb +120 -0
- data/lib/action_controller/metal/live.rb +398 -0
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +337 -0
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +312 -0
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +251 -0
- data/lib/action_controller/metal/renderers.rb +181 -0
- data/lib/action_controller/metal/rendering.rb +260 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
- data/lib/action_controller/metal/rescue.rb +33 -0
- data/lib/action_controller/metal/streaming.rb +183 -0
- data/lib/action_controller/metal/strong_parameters.rb +1546 -0
- data/lib/action_controller/metal/testing.rb +25 -0
- data/lib/action_controller/metal/url_for.rb +65 -0
- data/lib/action_controller/metal.rb +339 -0
- data/lib/action_controller/railtie.rb +149 -0
- data/lib/action_controller/railties/helpers.rb +26 -0
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +691 -0
- data/lib/action_controller.rb +80 -0
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +249 -0
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +365 -0
- data/lib/action_dispatch/http/filter_parameters.rb +80 -0
- data/lib/action_dispatch/http/filter_redirect.rb +50 -0
- data/lib/action_dispatch/http/headers.rb +134 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
- data/lib/action_dispatch/http/mime_type.rb +389 -0
- data/lib/action_dispatch/http/mime_types.rb +54 -0
- data/lib/action_dispatch/http/parameters.rb +119 -0
- data/lib/action_dispatch/http/permissions_policy.rb +189 -0
- data/lib/action_dispatch/http/rack_cache.rb +67 -0
- data/lib/action_dispatch/http/request.rb +498 -0
- data/lib/action_dispatch/http/response.rb +556 -0
- data/lib/action_dispatch/http/upload.rb +107 -0
- data/lib/action_dispatch/http/url.rb +344 -0
- data/lib/action_dispatch/journey/formatter.rb +226 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
- data/lib/action_dispatch/journey/nodes/node.rb +208 -0
- data/lib/action_dispatch/journey/parser.rb +103 -0
- data/lib/action_dispatch/journey/path/pattern.rb +209 -0
- data/lib/action_dispatch/journey/route.rb +189 -0
- data/lib/action_dispatch/journey/router/utils.rb +105 -0
- data/lib/action_dispatch/journey/router.rb +151 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +70 -0
- data/lib/action_dispatch/journey/visitors.rb +267 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +38 -0
- data/lib/action_dispatch/middleware/cookies.rb +719 -0
- data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +318 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
- data/lib/action_dispatch/middleware/reloader.rb +16 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
- data/lib/action_dispatch/middleware/request_id.rb +50 -0
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
- data/lib/action_dispatch/middleware/ssl.rb +180 -0
- data/lib/action_dispatch/middleware/stack.rb +194 -0
- data/lib/action_dispatch/middleware/static.rb +192 -0
- 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 +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -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 +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
- data/lib/action_dispatch/railtie.rb +77 -0
- data/lib/action_dispatch/request/session.rb +283 -0
- data/lib/action_dispatch/request/utils.rb +109 -0
- data/lib/action_dispatch/routing/endpoint.rb +19 -0
- data/lib/action_dispatch/routing/inspector.rb +323 -0
- data/lib/action_dispatch/routing/mapper.rb +2372 -0
- data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
- data/lib/action_dispatch/routing/redirection.rb +218 -0
- data/lib/action_dispatch/routing/route_set.rb +958 -0
- data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
- data/lib/action_dispatch/routing/url_for.rb +244 -0
- data/lib/action_dispatch/routing.rb +262 -0
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +75 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +114 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
- data/lib/action_dispatch/testing/assertions.rb +25 -0
- data/lib/action_dispatch/testing/integration.rb +694 -0
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +57 -0
- data/lib/action_dispatch/testing/test_request.rb +73 -0
- data/lib/action_dispatch/testing/test_response.rb +58 -0
- data/lib/action_dispatch.rb +147 -0
- data/lib/action_pack/gem_version.rb +19 -0
- data/lib/action_pack/version.rb +12 -0
- data/lib/action_pack.rb +27 -0
- metadata +375 -0
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
require "active_support/core_ext/hash/keys"
|
|
6
|
+
require "active_support/key_generator"
|
|
7
|
+
require "active_support/message_verifier"
|
|
8
|
+
require "active_support/json"
|
|
9
|
+
require "rack/utils"
|
|
10
|
+
|
|
11
|
+
module ActionDispatch
|
|
12
|
+
module RequestCookieMethods
|
|
13
|
+
def cookie_jar
|
|
14
|
+
fetch_header("action_dispatch.cookies") do
|
|
15
|
+
self.cookie_jar = Cookies::CookieJar.build(self, cookies)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# :stopdoc:
|
|
20
|
+
prepend Module.new {
|
|
21
|
+
def commit_cookie_jar!
|
|
22
|
+
cookie_jar.commit!
|
|
23
|
+
end
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def have_cookie_jar?
|
|
27
|
+
has_header? "action_dispatch.cookies"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def cookie_jar=(jar)
|
|
31
|
+
set_header "action_dispatch.cookies", jar
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def key_generator
|
|
35
|
+
get_header Cookies::GENERATOR_KEY
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def signed_cookie_salt
|
|
39
|
+
get_header Cookies::SIGNED_COOKIE_SALT
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def encrypted_cookie_salt
|
|
43
|
+
get_header Cookies::ENCRYPTED_COOKIE_SALT
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def encrypted_signed_cookie_salt
|
|
47
|
+
get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def authenticated_encrypted_cookie_salt
|
|
51
|
+
get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def use_authenticated_cookie_encryption
|
|
55
|
+
get_header Cookies::USE_AUTHENTICATED_COOKIE_ENCRYPTION
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def encrypted_cookie_cipher
|
|
59
|
+
get_header Cookies::ENCRYPTED_COOKIE_CIPHER
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def signed_cookie_digest
|
|
63
|
+
get_header Cookies::SIGNED_COOKIE_DIGEST
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def secret_key_base
|
|
67
|
+
get_header Cookies::SECRET_KEY_BASE
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def cookies_serializer
|
|
71
|
+
get_header Cookies::COOKIES_SERIALIZER
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def cookies_same_site_protection
|
|
75
|
+
get_header(Cookies::COOKIES_SAME_SITE_PROTECTION)&.call(self)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def cookies_digest
|
|
79
|
+
get_header Cookies::COOKIES_DIGEST
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def cookies_rotations
|
|
83
|
+
get_header Cookies::COOKIES_ROTATIONS
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def use_cookies_with_metadata
|
|
87
|
+
get_header Cookies::USE_COOKIES_WITH_METADATA
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# :startdoc:
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
ActiveSupport.on_load(:action_dispatch_request) do
|
|
94
|
+
include RequestCookieMethods
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Read and write data to cookies through ActionController::Cookies#cookies.
|
|
98
|
+
#
|
|
99
|
+
# When reading cookie data, the data is read from the HTTP request header,
|
|
100
|
+
# Cookie. When writing cookie data, the data is sent out in the HTTP response
|
|
101
|
+
# header, `Set-Cookie`.
|
|
102
|
+
#
|
|
103
|
+
# Examples of writing:
|
|
104
|
+
#
|
|
105
|
+
# # Sets a simple session cookie.
|
|
106
|
+
# # This cookie will be deleted when the user's browser is closed.
|
|
107
|
+
# cookies[:user_name] = "david"
|
|
108
|
+
#
|
|
109
|
+
# # Cookie values are String-based. Other data types need to be serialized.
|
|
110
|
+
# cookies[:lat_lon] = JSON.generate([47.68, -122.37])
|
|
111
|
+
#
|
|
112
|
+
# # Sets a cookie that expires in 1 hour.
|
|
113
|
+
# cookies[:login] = { value: "XJ-122", expires: 1.hour }
|
|
114
|
+
#
|
|
115
|
+
# # Sets a cookie that expires at a specific time.
|
|
116
|
+
# cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
|
|
117
|
+
#
|
|
118
|
+
# # Sets a signed cookie, which prevents users from tampering with its value.
|
|
119
|
+
# cookies.signed[:user_id] = current_user.id
|
|
120
|
+
# # It can be read using the signed method.
|
|
121
|
+
# cookies.signed[:user_id] # => 123
|
|
122
|
+
#
|
|
123
|
+
# # Sets an encrypted cookie value before sending it to the client which
|
|
124
|
+
# # prevent users from reading and tampering with its value.
|
|
125
|
+
# cookies.encrypted[:discount] = 45
|
|
126
|
+
# # It can be read using the encrypted method.
|
|
127
|
+
# cookies.encrypted[:discount] # => 45
|
|
128
|
+
#
|
|
129
|
+
# # Sets a "permanent" cookie (which expires in 20 years from now).
|
|
130
|
+
# cookies.permanent[:login] = "XJ-122"
|
|
131
|
+
#
|
|
132
|
+
# # You can also chain these methods:
|
|
133
|
+
# cookies.signed.permanent[:login] = "XJ-122"
|
|
134
|
+
#
|
|
135
|
+
# Examples of reading:
|
|
136
|
+
#
|
|
137
|
+
# cookies[:user_name] # => "david"
|
|
138
|
+
# cookies.size # => 2
|
|
139
|
+
# JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
|
|
140
|
+
# cookies.signed[:login] # => "XJ-122"
|
|
141
|
+
# cookies.encrypted[:discount] # => 45
|
|
142
|
+
#
|
|
143
|
+
# Example for deleting:
|
|
144
|
+
#
|
|
145
|
+
# cookies.delete :user_name
|
|
146
|
+
#
|
|
147
|
+
# Please note that if you specify a `:domain` when setting a cookie, you must
|
|
148
|
+
# also specify the domain when deleting the cookie:
|
|
149
|
+
#
|
|
150
|
+
# cookies[:name] = {
|
|
151
|
+
# value: 'a yummy cookie',
|
|
152
|
+
# expires: 1.year,
|
|
153
|
+
# domain: 'domain.com'
|
|
154
|
+
# }
|
|
155
|
+
#
|
|
156
|
+
# cookies.delete(:name, domain: 'domain.com')
|
|
157
|
+
#
|
|
158
|
+
# The option symbols for setting cookies are:
|
|
159
|
+
#
|
|
160
|
+
# * `:value` - The cookie's value.
|
|
161
|
+
# * `:path` - The path for which this cookie applies. Defaults to the root of
|
|
162
|
+
# the application.
|
|
163
|
+
# * `:domain` - The domain for which this cookie applies so you can restrict
|
|
164
|
+
# to the domain level. If you use a schema like www.example.com and want to
|
|
165
|
+
# share session with user.example.com set `:domain` to `:all`. To support
|
|
166
|
+
# multiple domains, provide an array, and the first domain matching
|
|
167
|
+
# `request.host` will be used. Make sure to specify the `:domain` option
|
|
168
|
+
# with `:all` or `Array` again when deleting cookies. For more flexibility
|
|
169
|
+
# you can set the domain on a per-request basis by specifying `:domain` with
|
|
170
|
+
# a proc.
|
|
171
|
+
#
|
|
172
|
+
# domain: nil # Does not set cookie domain. (default)
|
|
173
|
+
# domain: :all # Allow the cookie for the top most level
|
|
174
|
+
# # domain and subdomains.
|
|
175
|
+
# domain: %w(.example.com .example.org) # Allow the cookie
|
|
176
|
+
# # for concrete domain names.
|
|
177
|
+
# domain: proc { Tenant.current.cookie_domain } # Set cookie domain dynamically
|
|
178
|
+
# domain: proc { |req| ".sub.#{req.host}" } # Set cookie domain dynamically based on request
|
|
179
|
+
#
|
|
180
|
+
# * `:tld_length` - When using `:domain => :all`, this option can be used to
|
|
181
|
+
# explicitly set the TLD length when using a short (<= 3 character) domain
|
|
182
|
+
# that is being interpreted as part of a TLD. For example, to share cookies
|
|
183
|
+
# between user1.lvh.me and user2.lvh.me, set `:tld_length` to 2.
|
|
184
|
+
# * `:expires` - The time at which this cookie expires, as a Time or
|
|
185
|
+
# ActiveSupport::Duration object.
|
|
186
|
+
# * `:secure` - Whether this cookie is only transmitted to HTTPS servers.
|
|
187
|
+
# Default is `false`.
|
|
188
|
+
# * `:httponly` - Whether this cookie is accessible via scripting or only
|
|
189
|
+
# HTTP. Defaults to `false`.
|
|
190
|
+
# * `:same_site` - The value of the `SameSite` cookie attribute, which
|
|
191
|
+
# determines how this cookie should be restricted in cross-site contexts.
|
|
192
|
+
# Possible values are `nil`, `:none`, `:lax`, and `:strict`. Defaults to
|
|
193
|
+
# `:lax`.
|
|
194
|
+
#
|
|
195
|
+
class Cookies
|
|
196
|
+
HTTP_HEADER = "Set-Cookie"
|
|
197
|
+
GENERATOR_KEY = "action_dispatch.key_generator"
|
|
198
|
+
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt"
|
|
199
|
+
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt"
|
|
200
|
+
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt"
|
|
201
|
+
AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt"
|
|
202
|
+
USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
|
|
203
|
+
ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
|
|
204
|
+
SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
|
|
205
|
+
SECRET_KEY_BASE = "action_dispatch.secret_key_base"
|
|
206
|
+
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
|
|
207
|
+
COOKIES_DIGEST = "action_dispatch.cookies_digest"
|
|
208
|
+
COOKIES_ROTATIONS = "action_dispatch.cookies_rotations"
|
|
209
|
+
COOKIES_SAME_SITE_PROTECTION = "action_dispatch.cookies_same_site_protection"
|
|
210
|
+
USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata"
|
|
211
|
+
|
|
212
|
+
# Cookies can typically store 4096 bytes.
|
|
213
|
+
MAX_COOKIE_SIZE = 4096
|
|
214
|
+
|
|
215
|
+
# Raised when storing more than 4K of session data.
|
|
216
|
+
CookieOverflow = Class.new StandardError
|
|
217
|
+
|
|
218
|
+
# Include in a cookie jar to allow chaining, e.g. `cookies.permanent.signed`.
|
|
219
|
+
module ChainedCookieJars
|
|
220
|
+
# Returns a jar that'll automatically set the assigned cookies to have an
|
|
221
|
+
# expiration date 20 years from now. Example:
|
|
222
|
+
#
|
|
223
|
+
# cookies.permanent[:prefers_open_id] = true
|
|
224
|
+
# # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
|
225
|
+
#
|
|
226
|
+
# This jar is only meant for writing. You'll read permanent cookies through the
|
|
227
|
+
# regular accessor.
|
|
228
|
+
#
|
|
229
|
+
# This jar allows chaining with the signed jar as well, so you can set
|
|
230
|
+
# permanent, signed cookies. Examples:
|
|
231
|
+
#
|
|
232
|
+
# cookies.permanent.signed[:remember_me] = current_user.id
|
|
233
|
+
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
|
234
|
+
def permanent
|
|
235
|
+
@permanent ||= PermanentCookieJar.new(self)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Returns a jar that'll automatically generate a signed representation of cookie
|
|
239
|
+
# value and verify it when reading from the cookie again. This is useful for
|
|
240
|
+
# creating cookies with values that the user is not supposed to change. If a
|
|
241
|
+
# signed cookie was tampered with by the user (or a 3rd party), `nil` will be
|
|
242
|
+
# returned.
|
|
243
|
+
#
|
|
244
|
+
# This jar requires that you set a suitable secret for the verification on your
|
|
245
|
+
# app's `secret_key_base`.
|
|
246
|
+
#
|
|
247
|
+
# Example:
|
|
248
|
+
#
|
|
249
|
+
# cookies.signed[:discount] = 45
|
|
250
|
+
# # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
|
|
251
|
+
#
|
|
252
|
+
# cookies.signed[:discount] # => 45
|
|
253
|
+
def signed
|
|
254
|
+
@signed ||= SignedKeyRotatingCookieJar.new(self)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Returns a jar that'll automatically encrypt cookie values before sending them
|
|
258
|
+
# to the client and will decrypt them for read. If the cookie was tampered with
|
|
259
|
+
# by the user (or a 3rd party), `nil` will be returned.
|
|
260
|
+
#
|
|
261
|
+
# If `config.action_dispatch.encrypted_cookie_salt` and
|
|
262
|
+
# `config.action_dispatch.encrypted_signed_cookie_salt` are both set, legacy
|
|
263
|
+
# cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
|
|
264
|
+
#
|
|
265
|
+
# This jar requires that you set a suitable secret for the verification on your
|
|
266
|
+
# app's `secret_key_base`.
|
|
267
|
+
#
|
|
268
|
+
# Example:
|
|
269
|
+
#
|
|
270
|
+
# cookies.encrypted[:discount] = 45
|
|
271
|
+
# # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
|
|
272
|
+
#
|
|
273
|
+
# cookies.encrypted[:discount] # => 45
|
|
274
|
+
def encrypted
|
|
275
|
+
@encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Returns the `signed` or `encrypted` jar, preferring `encrypted` if
|
|
279
|
+
# `secret_key_base` is set. Used by ActionDispatch::Session::CookieStore to
|
|
280
|
+
# avoid the need to introduce new cookie stores.
|
|
281
|
+
def signed_or_encrypted
|
|
282
|
+
@signed_or_encrypted ||=
|
|
283
|
+
if request.secret_key_base.present?
|
|
284
|
+
encrypted
|
|
285
|
+
else
|
|
286
|
+
signed
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
private
|
|
291
|
+
def upgrade_legacy_hmac_aes_cbc_cookies?
|
|
292
|
+
request.secret_key_base.present? &&
|
|
293
|
+
request.encrypted_signed_cookie_salt.present? &&
|
|
294
|
+
request.encrypted_cookie_salt.present? &&
|
|
295
|
+
request.use_authenticated_cookie_encryption
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def prepare_upgrade_legacy_hmac_aes_cbc_cookies?
|
|
299
|
+
request.secret_key_base.present? &&
|
|
300
|
+
request.authenticated_encrypted_cookie_salt.present? &&
|
|
301
|
+
!request.use_authenticated_cookie_encryption
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def encrypted_cookie_cipher
|
|
305
|
+
request.encrypted_cookie_cipher || "aes-256-gcm"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def signed_cookie_digest
|
|
309
|
+
request.signed_cookie_digest || "SHA1"
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
class CookieJar # :nodoc:
|
|
314
|
+
include Enumerable, ChainedCookieJars
|
|
315
|
+
|
|
316
|
+
def self.build(req, cookies)
|
|
317
|
+
jar = new(req)
|
|
318
|
+
jar.update(cookies)
|
|
319
|
+
jar
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
attr_reader :request
|
|
323
|
+
|
|
324
|
+
def initialize(request)
|
|
325
|
+
@set_cookies = {}
|
|
326
|
+
@delete_cookies = {}
|
|
327
|
+
@request = request
|
|
328
|
+
@cookies = {}
|
|
329
|
+
@committed = false
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def committed?; @committed; end
|
|
333
|
+
|
|
334
|
+
def commit!
|
|
335
|
+
@committed = true
|
|
336
|
+
@set_cookies.freeze
|
|
337
|
+
@delete_cookies.freeze
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def each(&block)
|
|
341
|
+
@cookies.each(&block)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Returns the value of the cookie by `name`, or `nil` if no such cookie exists.
|
|
345
|
+
def [](name)
|
|
346
|
+
@cookies[name.to_s]
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def fetch(name, *args, &block)
|
|
350
|
+
@cookies.fetch(name.to_s, *args, &block)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def key?(name)
|
|
354
|
+
@cookies.key?(name.to_s)
|
|
355
|
+
end
|
|
356
|
+
alias :has_key? :key?
|
|
357
|
+
|
|
358
|
+
# Returns the cookies as Hash.
|
|
359
|
+
alias :to_hash :to_h
|
|
360
|
+
|
|
361
|
+
def update(other_hash)
|
|
362
|
+
@cookies.update other_hash.stringify_keys
|
|
363
|
+
self
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def update_cookies_from_jar
|
|
367
|
+
request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
|
|
368
|
+
set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) }
|
|
369
|
+
|
|
370
|
+
@cookies.update set_cookies if set_cookies
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def to_header
|
|
374
|
+
@cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Sets the cookie named `name`. The second argument may be the cookie's value or
|
|
378
|
+
# a hash of options as documented above.
|
|
379
|
+
def []=(name, options)
|
|
380
|
+
if options.is_a?(Hash)
|
|
381
|
+
options.symbolize_keys!
|
|
382
|
+
value = options[:value]
|
|
383
|
+
else
|
|
384
|
+
value = options
|
|
385
|
+
options = { value: value }
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
handle_options(options)
|
|
389
|
+
|
|
390
|
+
if @cookies[name.to_s] != value || options[:expires]
|
|
391
|
+
@cookies[name.to_s] = value
|
|
392
|
+
@set_cookies[name.to_s] = options
|
|
393
|
+
@delete_cookies.delete(name.to_s)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
value
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Removes the cookie on the client machine by setting the value to an empty
|
|
400
|
+
# string and the expiration date in the past. Like `[]=`, you can pass in an
|
|
401
|
+
# options hash to delete cookies with extra data such as a `:path`.
|
|
402
|
+
#
|
|
403
|
+
# Returns the value of the cookie, or `nil` if the cookie does not exist.
|
|
404
|
+
def delete(name, options = {})
|
|
405
|
+
return unless @cookies.has_key? name.to_s
|
|
406
|
+
|
|
407
|
+
options.symbolize_keys!
|
|
408
|
+
handle_options(options)
|
|
409
|
+
|
|
410
|
+
value = @cookies.delete(name.to_s)
|
|
411
|
+
@delete_cookies[name.to_s] = options
|
|
412
|
+
value
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Whether the given cookie is to be deleted by this CookieJar. Like `[]=`, you
|
|
416
|
+
# can pass in an options hash to test if a deletion applies to a specific
|
|
417
|
+
# `:path`, `:domain` etc.
|
|
418
|
+
def deleted?(name, options = {})
|
|
419
|
+
options.symbolize_keys!
|
|
420
|
+
handle_options(options)
|
|
421
|
+
@delete_cookies[name.to_s] == options
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Removes all cookies on the client machine by calling `delete` for each cookie.
|
|
425
|
+
def clear(options = {})
|
|
426
|
+
@cookies.each_key { |k| delete(k, options) }
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def write(response)
|
|
430
|
+
@set_cookies.each do |name, value|
|
|
431
|
+
if write_cookie?(value)
|
|
432
|
+
response.set_cookie(name, value)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
@delete_cookies.each do |name, value|
|
|
437
|
+
response.delete_cookie(name, value)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
mattr_accessor :always_write_cookie, default: false
|
|
442
|
+
|
|
443
|
+
private
|
|
444
|
+
def escape(string)
|
|
445
|
+
::Rack::Utils.escape(string)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def write_cookie?(cookie)
|
|
449
|
+
request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def handle_options(options)
|
|
453
|
+
if options[:expires].respond_to?(:from_now)
|
|
454
|
+
options[:expires] = options[:expires].from_now
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
options[:path] ||= "/"
|
|
458
|
+
|
|
459
|
+
unless options.key?(:same_site)
|
|
460
|
+
options[:same_site] = request.cookies_same_site_protection
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
if options[:domain] == :all || options[:domain] == "all"
|
|
464
|
+
cookie_domain = ""
|
|
465
|
+
dot_splitted_host = request.host.split(".", -1)
|
|
466
|
+
|
|
467
|
+
# Case where request.host is not an IP address or it's an invalid domain (ip
|
|
468
|
+
# confirms to the domain structure we expect so we explicitly check for ip)
|
|
469
|
+
if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1
|
|
470
|
+
options[:domain] = nil
|
|
471
|
+
return
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
# If there is a provided tld length then we use it otherwise default domain.
|
|
475
|
+
if options[:tld_length].present?
|
|
476
|
+
# Case where the tld_length provided is valid
|
|
477
|
+
if dot_splitted_host.length >= options[:tld_length]
|
|
478
|
+
cookie_domain = dot_splitted_host.last(options[:tld_length]).join(".")
|
|
479
|
+
end
|
|
480
|
+
# Case where tld_length is not provided
|
|
481
|
+
else
|
|
482
|
+
# Regular TLDs
|
|
483
|
+
if !(/\.[^.]{2,3}\.[^.]{2}\z/.match?(request.host))
|
|
484
|
+
cookie_domain = dot_splitted_host.last(2).join(".")
|
|
485
|
+
# **.**, ***.** style TLDs like co.uk and com.au
|
|
486
|
+
else
|
|
487
|
+
cookie_domain = dot_splitted_host.last(3).join(".")
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
options[:domain] = if cookie_domain.present?
|
|
492
|
+
cookie_domain
|
|
493
|
+
end
|
|
494
|
+
elsif options[:domain].is_a? Array
|
|
495
|
+
# If host matches one of the supplied domains.
|
|
496
|
+
options[:domain] = options[:domain].find do |domain|
|
|
497
|
+
domain = domain.delete_prefix(".")
|
|
498
|
+
request.host == domain || request.host.end_with?(".#{domain}")
|
|
499
|
+
end
|
|
500
|
+
elsif options[:domain].respond_to?(:call)
|
|
501
|
+
options[:domain] = options[:domain].call(request)
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
class AbstractCookieJar # :nodoc:
|
|
507
|
+
include ChainedCookieJars
|
|
508
|
+
|
|
509
|
+
def initialize(parent_jar)
|
|
510
|
+
@parent_jar = parent_jar
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def [](name)
|
|
514
|
+
if data = @parent_jar[name.to_s]
|
|
515
|
+
result = parse(name, data, purpose: "cookie.#{name}")
|
|
516
|
+
|
|
517
|
+
if result.nil?
|
|
518
|
+
parse(name, data)
|
|
519
|
+
else
|
|
520
|
+
result
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def []=(name, options)
|
|
526
|
+
if options.is_a?(Hash)
|
|
527
|
+
options.symbolize_keys!
|
|
528
|
+
else
|
|
529
|
+
options = { value: options }
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
commit(name, options)
|
|
533
|
+
@parent_jar[name] = options
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
protected
|
|
537
|
+
def request; @parent_jar.request; end
|
|
538
|
+
|
|
539
|
+
private
|
|
540
|
+
def expiry_options(options)
|
|
541
|
+
if options[:expires].respond_to?(:from_now)
|
|
542
|
+
{ expires_in: options[:expires] }
|
|
543
|
+
else
|
|
544
|
+
{ expires_at: options[:expires] }
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def cookie_metadata(name, options)
|
|
549
|
+
expiry_options(options).tap do |metadata|
|
|
550
|
+
metadata[:purpose] = "cookie.#{name}" if request.use_cookies_with_metadata
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def parse(name, data, purpose: nil); data; end
|
|
555
|
+
def commit(name, options); end
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
class PermanentCookieJar < AbstractCookieJar # :nodoc:
|
|
559
|
+
private
|
|
560
|
+
def commit(name, options)
|
|
561
|
+
options[:expires] = 20.years.from_now
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
module SerializedCookieJars # :nodoc:
|
|
566
|
+
SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
|
|
567
|
+
|
|
568
|
+
protected
|
|
569
|
+
def digest
|
|
570
|
+
request.cookies_digest || "SHA1"
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
private
|
|
574
|
+
def serializer
|
|
575
|
+
@serializer ||=
|
|
576
|
+
case request.cookies_serializer
|
|
577
|
+
when nil
|
|
578
|
+
ActiveSupport::Messages::SerializerWithFallback[:marshal]
|
|
579
|
+
when :hybrid
|
|
580
|
+
ActiveSupport::Messages::SerializerWithFallback[:json_allow_marshal]
|
|
581
|
+
when Symbol
|
|
582
|
+
ActiveSupport::Messages::SerializerWithFallback[request.cookies_serializer]
|
|
583
|
+
else
|
|
584
|
+
request.cookies_serializer
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def reserialize?(dumped)
|
|
589
|
+
serializer.is_a?(ActiveSupport::Messages::SerializerWithFallback) &&
|
|
590
|
+
serializer != ActiveSupport::Messages::SerializerWithFallback[:marshal] &&
|
|
591
|
+
!serializer.dumped?(dumped)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def parse(name, dumped, force_reserialize: false, **)
|
|
595
|
+
if dumped
|
|
596
|
+
begin
|
|
597
|
+
value = serializer.load(dumped)
|
|
598
|
+
rescue StandardError
|
|
599
|
+
return
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
self[name] = { value: value } if force_reserialize || reserialize?(dumped)
|
|
603
|
+
|
|
604
|
+
value
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def commit(name, options)
|
|
609
|
+
options[:value] = serializer.dump(options[:value])
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def check_for_overflow!(name, options)
|
|
613
|
+
if options[:value].bytesize > MAX_COOKIE_SIZE
|
|
614
|
+
raise CookieOverflow, "#{name} cookie overflowed with size #{options[:value].bytesize} bytes"
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
|
|
620
|
+
include SerializedCookieJars
|
|
621
|
+
|
|
622
|
+
def initialize(parent_jar)
|
|
623
|
+
super
|
|
624
|
+
|
|
625
|
+
secret = request.key_generator.generate_key(request.signed_cookie_salt)
|
|
626
|
+
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
|
|
627
|
+
|
|
628
|
+
request.cookies_rotations.signed.each do |(*secrets)|
|
|
629
|
+
options = secrets.extract_options!
|
|
630
|
+
@verifier.rotate(*secrets, serializer: SERIALIZER, **options)
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
private
|
|
635
|
+
def parse(name, signed_message, purpose: nil)
|
|
636
|
+
rotated = false
|
|
637
|
+
data = @verifier.verified(signed_message, purpose: purpose, on_rotation: -> { rotated = true })
|
|
638
|
+
super(name, data, force_reserialize: rotated)
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def commit(name, options)
|
|
642
|
+
super
|
|
643
|
+
options[:value] = @verifier.generate(options[:value], **cookie_metadata(name, options))
|
|
644
|
+
check_for_overflow!(name, options)
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
|
|
649
|
+
include SerializedCookieJars
|
|
650
|
+
|
|
651
|
+
def initialize(parent_jar)
|
|
652
|
+
super
|
|
653
|
+
|
|
654
|
+
if request.use_authenticated_cookie_encryption
|
|
655
|
+
key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
|
|
656
|
+
secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len)
|
|
657
|
+
@encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER)
|
|
658
|
+
else
|
|
659
|
+
key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")
|
|
660
|
+
secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len)
|
|
661
|
+
sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
|
|
662
|
+
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
request.cookies_rotations.encrypted.each do |(*secrets)|
|
|
666
|
+
options = secrets.extract_options!
|
|
667
|
+
@encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
if upgrade_legacy_hmac_aes_cbc_cookies?
|
|
671
|
+
legacy_cipher = "aes-256-cbc"
|
|
672
|
+
secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher))
|
|
673
|
+
sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
|
|
674
|
+
|
|
675
|
+
@encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
|
|
676
|
+
elsif prepare_upgrade_legacy_hmac_aes_cbc_cookies?
|
|
677
|
+
future_cipher = encrypted_cookie_cipher
|
|
678
|
+
secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(future_cipher))
|
|
679
|
+
|
|
680
|
+
@encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER)
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
private
|
|
685
|
+
def parse(name, encrypted_message, purpose: nil)
|
|
686
|
+
rotated = false
|
|
687
|
+
data = @encryptor.decrypt_and_verify(encrypted_message, purpose: purpose, on_rotation: -> { rotated = true })
|
|
688
|
+
super(name, data, force_reserialize: rotated)
|
|
689
|
+
rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
|
|
690
|
+
nil
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
def commit(name, options)
|
|
694
|
+
super
|
|
695
|
+
options[:value] = @encryptor.encrypt_and_sign(options[:value], **cookie_metadata(name, options))
|
|
696
|
+
check_for_overflow!(name, options)
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
def initialize(app)
|
|
701
|
+
@app = app
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def call(env)
|
|
705
|
+
request = ActionDispatch::Request.new(env)
|
|
706
|
+
response = @app.call(env)
|
|
707
|
+
|
|
708
|
+
if request.have_cookie_jar?
|
|
709
|
+
cookie_jar = request.cookie_jar
|
|
710
|
+
unless cookie_jar.committed?
|
|
711
|
+
response = Rack::Response[*response]
|
|
712
|
+
cookie_jar.write(response)
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
response.to_a
|
|
717
|
+
end
|
|
718
|
+
end
|
|
719
|
+
end
|