actionpack 4.2.11.1 → 6.1.3.2

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.

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