actionpack 3.2.22.5 → 5.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.

Files changed (271) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +279 -603
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -297
  5. data/lib/abstract_controller/asset_paths.rb +4 -2
  6. data/lib/abstract_controller/base.rb +82 -52
  7. data/lib/abstract_controller/caching/fragments.rb +166 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +117 -103
  10. data/lib/abstract_controller/collector.rb +18 -7
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +65 -38
  13. data/lib/abstract_controller/logger.rb +3 -2
  14. data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
  15. data/lib/abstract_controller/rendering.rb +77 -129
  16. data/lib/abstract_controller/translation.rb +21 -3
  17. data/lib/abstract_controller/url_for.rb +9 -7
  18. data/lib/abstract_controller.rb +12 -13
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +81 -40
  22. data/lib/action_controller/caching.rb +22 -62
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +30 -18
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +190 -47
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +40 -65
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  32. data/lib/action_controller/metal/exceptions.rb +19 -12
  33. data/lib/action_controller/metal/flash.rb +42 -9
  34. data/lib/action_controller/metal/force_ssl.rb +79 -19
  35. data/lib/action_controller/metal/head.rb +35 -10
  36. data/lib/action_controller/metal/helpers.rb +31 -21
  37. data/lib/action_controller/metal/http_authentication.rb +182 -134
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +28 -26
  40. data/lib/action_controller/metal/live.rb +312 -0
  41. data/lib/action_controller/metal/mime_responds.rb +159 -163
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +146 -93
  44. data/lib/action_controller/metal/redirecting.rb +80 -56
  45. data/lib/action_controller/metal/renderers.rb +119 -47
  46. data/lib/action_controller/metal/rendering.rb +89 -32
  47. data/lib/action_controller/metal/request_forgery_protection.rb +373 -41
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +39 -45
  50. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  51. data/lib/action_controller/metal/testing.rb +8 -29
  52. data/lib/action_controller/metal/url_for.rb +43 -32
  53. data/lib/action_controller/metal.rb +112 -106
  54. data/lib/action_controller/railtie.rb +56 -18
  55. data/lib/action_controller/railties/helpers.rb +24 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +402 -347
  59. data/lib/action_controller.rb +31 -30
  60. data/lib/action_dispatch/http/cache.rb +133 -34
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +40 -24
  63. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  64. data/lib/action_dispatch/http/headers.rb +117 -16
  65. data/lib/action_dispatch/http/mime_negotiation.rb +98 -33
  66. data/lib/action_dispatch/http/mime_type.rb +198 -146
  67. data/lib/action_dispatch/http/mime_types.rb +22 -7
  68. data/lib/action_dispatch/http/parameter_filter.rb +61 -49
  69. data/lib/action_dispatch/http/parameters.rb +94 -51
  70. data/lib/action_dispatch/http/rack_cache.rb +4 -3
  71. data/lib/action_dispatch/http/request.rb +262 -117
  72. data/lib/action_dispatch/http/response.rb +400 -86
  73. data/lib/action_dispatch/http/upload.rb +66 -29
  74. data/lib/action_dispatch/http/url.rb +232 -60
  75. data/lib/action_dispatch/journey/formatter.rb +189 -0
  76. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  79. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  80. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  83. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  84. data/lib/action_dispatch/journey/parser.rb +199 -0
  85. data/lib/action_dispatch/journey/parser.y +50 -0
  86. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  87. data/lib/action_dispatch/journey/path/pattern.rb +199 -0
  88. data/lib/action_dispatch/journey/route.rb +203 -0
  89. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  90. data/lib/action_dispatch/journey/router.rb +156 -0
  91. data/lib/action_dispatch/journey/routes.rb +82 -0
  92. data/lib/action_dispatch/journey/scanner.rb +64 -0
  93. data/lib/action_dispatch/journey/visitors.rb +268 -0
  94. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  95. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  96. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  97. data/lib/action_dispatch/journey.rb +7 -0
  98. data/lib/action_dispatch/middleware/callbacks.rb +17 -13
  99. data/lib/action_dispatch/middleware/cookies.rb +494 -162
  100. data/lib/action_dispatch/middleware/debug_exceptions.rb +176 -53
  101. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  102. data/lib/action_dispatch/middleware/exception_wrapper.rb +103 -38
  103. data/lib/action_dispatch/middleware/executor.rb +21 -0
  104. data/lib/action_dispatch/middleware/flash.rb +128 -91
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +43 -16
  106. data/lib/action_dispatch/middleware/reloader.rb +6 -83
  107. data/lib/action_dispatch/middleware/remote_ip.rb +151 -49
  108. data/lib/action_dispatch/middleware/request_id.rb +19 -15
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +38 -34
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +94 -44
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -4
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +36 -61
  114. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  115. data/lib/action_dispatch/middleware/stack.rb +33 -41
  116. data/lib/action_dispatch/middleware/static.rb +92 -48
  117. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +134 -5
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  136. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  138. data/lib/action_dispatch/railtie.rb +29 -8
  139. data/lib/action_dispatch/request/session.rb +234 -0
  140. data/lib/action_dispatch/request/utils.rb +78 -0
  141. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  142. data/lib/action_dispatch/routing/inspector.rb +225 -0
  143. data/lib/action_dispatch/routing/mapper.rb +1329 -582
  144. data/lib/action_dispatch/routing/polymorphic_routes.rb +237 -94
  145. data/lib/action_dispatch/routing/redirection.rb +120 -50
  146. data/lib/action_dispatch/routing/route_set.rb +545 -322
  147. data/lib/action_dispatch/routing/routes_proxy.rb +37 -7
  148. data/lib/action_dispatch/routing/url_for.rb +103 -34
  149. data/lib/action_dispatch/routing.rb +66 -99
  150. data/lib/action_dispatch/system_test_case.rb +147 -0
  151. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  152. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  153. data/lib/action_dispatch/system_testing/server.rb +31 -0
  154. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  158. data/lib/action_dispatch/testing/assertions/response.rb +53 -42
  159. data/lib/action_dispatch/testing/assertions/routing.rb +79 -74
  160. data/lib/action_dispatch/testing/assertions.rb +15 -9
  161. data/lib/action_dispatch/testing/integration.rb +361 -207
  162. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  163. data/lib/action_dispatch/testing/test_process.rb +28 -19
  164. data/lib/action_dispatch/testing/test_request.rb +30 -33
  165. data/lib/action_dispatch/testing/test_response.rb +35 -11
  166. data/lib/action_dispatch.rb +42 -32
  167. data/lib/action_pack/gem_version.rb +17 -0
  168. data/lib/action_pack/version.rb +7 -7
  169. data/lib/action_pack.rb +4 -2
  170. metadata +116 -175
  171. data/lib/abstract_controller/layouts.rb +0 -423
  172. data/lib/abstract_controller/view_paths.rb +0 -96
  173. data/lib/action_controller/caching/actions.rb +0 -185
  174. data/lib/action_controller/caching/fragments.rb +0 -127
  175. data/lib/action_controller/caching/pages.rb +0 -187
  176. data/lib/action_controller/caching/sweeping.rb +0 -97
  177. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  178. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  179. data/lib/action_controller/deprecated.rb +0 -3
  180. data/lib/action_controller/metal/compatibility.rb +0 -65
  181. data/lib/action_controller/metal/hide_actions.rb +0 -41
  182. data/lib/action_controller/metal/rack_delegation.rb +0 -26
  183. data/lib/action_controller/metal/responder.rb +0 -286
  184. data/lib/action_controller/metal/session_management.rb +0 -14
  185. data/lib/action_controller/middleware.rb +0 -39
  186. data/lib/action_controller/railties/paths.rb +0 -25
  187. data/lib/action_controller/record_identifier.rb +0 -85
  188. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  189. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  190. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  191. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  192. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  193. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  194. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  195. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  196. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  197. data/lib/action_dispatch/middleware/head.rb +0 -18
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -75
  199. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  200. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  201. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  202. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  203. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  204. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  205. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  206. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  207. data/lib/action_dispatch/testing/assertions/dom.rb +0 -37
  208. data/lib/action_dispatch/testing/assertions/selector.rb +0 -435
  209. data/lib/action_dispatch/testing/assertions/tag.rb +0 -138
  210. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  211. data/lib/action_view/asset_paths.rb +0 -142
  212. data/lib/action_view/base.rb +0 -220
  213. data/lib/action_view/buffers.rb +0 -43
  214. data/lib/action_view/context.rb +0 -36
  215. data/lib/action_view/flows.rb +0 -79
  216. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  217. data/lib/action_view/helpers/asset_paths.rb +0 -7
  218. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  219. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  220. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  221. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  222. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  223. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  224. data/lib/action_view/helpers/cache_helper.rb +0 -64
  225. data/lib/action_view/helpers/capture_helper.rb +0 -203
  226. data/lib/action_view/helpers/controller_helper.rb +0 -25
  227. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  228. data/lib/action_view/helpers/date_helper.rb +0 -1062
  229. data/lib/action_view/helpers/debug_helper.rb +0 -40
  230. data/lib/action_view/helpers/form_helper.rb +0 -1486
  231. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  232. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  233. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  234. data/lib/action_view/helpers/number_helper.rb +0 -622
  235. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  236. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  237. data/lib/action_view/helpers/rendering_helper.rb +0 -92
  238. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  239. data/lib/action_view/helpers/tag_helper.rb +0 -167
  240. data/lib/action_view/helpers/text_helper.rb +0 -426
  241. data/lib/action_view/helpers/translation_helper.rb +0 -91
  242. data/lib/action_view/helpers/url_helper.rb +0 -693
  243. data/lib/action_view/helpers.rb +0 -60
  244. data/lib/action_view/locale/en.yml +0 -160
  245. data/lib/action_view/log_subscriber.rb +0 -28
  246. data/lib/action_view/lookup_context.rb +0 -258
  247. data/lib/action_view/path_set.rb +0 -101
  248. data/lib/action_view/railtie.rb +0 -55
  249. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  250. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  251. data/lib/action_view/renderer/renderer.rb +0 -61
  252. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  253. data/lib/action_view/renderer/template_renderer.rb +0 -95
  254. data/lib/action_view/template/error.rb +0 -128
  255. data/lib/action_view/template/handlers/builder.rb +0 -26
  256. data/lib/action_view/template/handlers/erb.rb +0 -125
  257. data/lib/action_view/template/handlers.rb +0 -50
  258. data/lib/action_view/template/resolver.rb +0 -298
  259. data/lib/action_view/template/text.rb +0 -30
  260. data/lib/action_view/template.rb +0 -337
  261. data/lib/action_view/test_case.rb +0 -246
  262. data/lib/action_view/testing/resolvers.rb +0 -49
  263. data/lib/action_view.rb +0 -84
  264. data/lib/sprockets/assets.rake +0 -99
  265. data/lib/sprockets/bootstrap.rb +0 -37
  266. data/lib/sprockets/compressors.rb +0 -83
  267. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  268. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  269. data/lib/sprockets/helpers.rb +0 -6
  270. data/lib/sprockets/railtie.rb +0 -62
  271. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,12 +1,87 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'active_support/core_ext/hash/keys'
3
- require 'active_support/core_ext/module/attribute_accessors'
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"
4
8
 
5
9
  module ActionDispatch
6
10
  class Request
7
11
  def cookie_jar
8
- env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
12
+ fetch_header("action_dispatch.cookies".freeze) 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".freeze
26
+ end
27
+
28
+ def cookie_jar=(jar)
29
+ set_header "action_dispatch.cookies".freeze, 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
62
+ end
63
+
64
+ def secret_token
65
+ get_header Cookies::SECRET_TOKEN
9
66
  end
67
+
68
+ def secret_key_base
69
+ get_header Cookies::SECRET_KEY_BASE
70
+ end
71
+
72
+ def cookies_serializer
73
+ get_header Cookies::COOKIES_SERIALIZER
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
+ # :startdoc:
10
85
  end
11
86
 
12
87
  # \Cookies are read and written through ActionController#cookies.
@@ -15,34 +90,43 @@ module ActionDispatch
15
90
  # being written will be sent out with the response. Reading a cookie does not get
16
91
  # the cookie object itself back, just the value it holds.
17
92
  #
18
- # Examples for writing:
93
+ # Examples of writing:
19
94
  #
20
95
  # # Sets a simple session cookie.
21
96
  # # This cookie will be deleted when the user's browser is closed.
22
97
  # cookies[:user_name] = "david"
23
98
  #
24
- # # Assign an array of values to a cookie.
25
- # cookies[:lat_lon] = [47.68, -122.37]
99
+ # # Cookie values are String based. Other data types need to be serialized.
100
+ # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
26
101
  #
27
102
  # # Sets a cookie that expires in 1 hour.
28
- # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
103
+ # cookies[:login] = { value: "XJ-122", expires: 1.hour }
104
+ #
105
+ # # Sets a cookie that expires at a specific time.
106
+ # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
29
107
  #
30
- # # Sets a signed cookie, which prevents a user from tampering with its value.
31
- # # The cookie is signed by your app's <tt>config.secret_token</tt> value.
32
- # # Rails generates this value by default when you create a new Rails app.
108
+ # # Sets a signed cookie, which prevents users from tampering with its value.
109
+ # # It can be read using the signed method `cookies.signed[:name]`
33
110
  # cookies.signed[:user_id] = current_user.id
34
111
  #
112
+ # # Sets an encrypted cookie value before sending it to the client which
113
+ # # prevent users from reading and tampering with its value.
114
+ # # It can be read using the encrypted method `cookies.encrypted[:name]`
115
+ # cookies.encrypted[:discount] = 45
116
+ #
35
117
  # # Sets a "permanent" cookie (which expires in 20 years from now).
36
118
  # cookies.permanent[:login] = "XJ-122"
37
119
  #
38
120
  # # You can also chain these methods:
39
- # cookies.permanent.signed[:login] = "XJ-122"
121
+ # cookies.signed.permanent[:login] = "XJ-122"
40
122
  #
41
- # Examples for reading:
123
+ # Examples of reading:
42
124
  #
43
- # cookies[:user_name] # => "david"
44
- # cookies.size # => 2
45
- # cookies[:lat_lon] # => [47.68, -122.37]
125
+ # cookies[:user_name] # => "david"
126
+ # cookies.size # => 2
127
+ # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
128
+ # cookies.signed[:login] # => "XJ-122"
129
+ # cookies.encrypted[:discount] # => 45
46
130
  #
47
131
  # Example for deleting:
48
132
  #
@@ -50,43 +134,153 @@ module ActionDispatch
50
134
  #
51
135
  # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
52
136
  #
53
- # cookies[:key] = {
54
- # :value => 'a yummy cookie',
55
- # :expires => 1.year.from_now,
56
- # :domain => 'domain.com'
137
+ # cookies[:name] = {
138
+ # value: 'a yummy cookie',
139
+ # expires: 1.year,
140
+ # domain: 'domain.com'
57
141
  # }
58
142
  #
59
- # cookies.delete(:key, :domain => 'domain.com')
143
+ # cookies.delete(:name, domain: 'domain.com')
60
144
  #
61
145
  # The option symbols for setting cookies are:
62
146
  #
63
- # * <tt>:value</tt> - The cookie's value or list of values (as an array).
147
+ # * <tt>:value</tt> - The cookie's value.
64
148
  # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
65
149
  # of the application.
66
150
  # * <tt>:domain</tt> - The domain for which this cookie applies so you can
67
151
  # restrict to the domain level. If you use a schema like www.example.com
68
152
  # and want to share session with user.example.com set <tt>:domain</tt>
69
153
  # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
70
- # <tt>:all</tt> again when deleting keys.
154
+ # <tt>:all</tt> or <tt>Array</tt> again when deleting cookies.
71
155
  #
72
- # :domain => nil # Does not sets cookie domain. (default)
73
- # :domain => :all # Allow the cookie for the top most level
74
- # domain and subdomains.
156
+ # domain: nil # Does not set cookie domain. (default)
157
+ # domain: :all # Allow the cookie for the top most level
158
+ # # domain and subdomains.
159
+ # domain: %w(.example.com .example.org) # Allow the cookie
160
+ # # for concrete domain names.
75
161
  #
76
- # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
77
- # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
162
+ # * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
163
+ # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
164
+ # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 2.
165
+ # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object.
166
+ # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
78
167
  # Default is +false+.
79
168
  # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
80
169
  # only HTTP. Defaults to +false+.
81
170
  class Cookies
82
- HTTP_HEADER = "Set-Cookie".freeze
83
- TOKEN_KEY = "action_dispatch.secret_token".freeze
171
+ HTTP_HEADER = "Set-Cookie".freeze
172
+ GENERATOR_KEY = "action_dispatch.key_generator".freeze
173
+ SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
174
+ ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
175
+ ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
176
+ AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze
177
+ USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption".freeze
178
+ ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher".freeze
179
+ SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest".freeze
180
+ SECRET_TOKEN = "action_dispatch.secret_token".freeze
181
+ SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
182
+ COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
183
+ COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
184
+ COOKIES_ROTATIONS = "action_dispatch.cookies_rotations".freeze
185
+
186
+ # Cookies can typically store 4096 bytes.
187
+ MAX_COOKIE_SIZE = 4096
84
188
 
85
189
  # Raised when storing more than 4K of session data.
86
- class CookieOverflow < StandardError; end
190
+ CookieOverflow = Class.new StandardError
191
+
192
+ # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed.
193
+ module ChainedCookieJars
194
+ # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
195
+ #
196
+ # cookies.permanent[:prefers_open_id] = true
197
+ # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
198
+ #
199
+ # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
200
+ #
201
+ # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
202
+ #
203
+ # cookies.permanent.signed[:remember_me] = current_user.id
204
+ # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
205
+ def permanent
206
+ @permanent ||= PermanentCookieJar.new(self)
207
+ end
208
+
209
+ # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
210
+ # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
211
+ # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
212
+ #
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
+ # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
217
+ #
218
+ # Example:
219
+ #
220
+ # cookies.signed[:discount] = 45
221
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
222
+ #
223
+ # cookies.signed[:discount] # => 45
224
+ def signed
225
+ @signed ||= SignedKeyRotatingCookieJar.new(self)
226
+ end
227
+
228
+ # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
229
+ # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
230
+ #
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
+ # 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.
236
+ #
237
+ # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
238
+ #
239
+ # Example:
240
+ #
241
+ # cookies.encrypted[:discount] = 45
242
+ # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
243
+ #
244
+ # cookies.encrypted[:discount] # => 45
245
+ def encrypted
246
+ @encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
247
+ end
248
+
249
+ # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
250
+ # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
251
+ def signed_or_encrypted
252
+ @signed_or_encrypted ||=
253
+ if request.secret_key_base.present?
254
+ encrypted
255
+ else
256
+ signed
257
+ end
258
+ end
259
+
260
+ private
261
+
262
+ def upgrade_legacy_signed_cookies?
263
+ request.secret_token.present? && request.secret_key_base.present?
264
+ end
265
+
266
+ def upgrade_legacy_hmac_aes_cbc_cookies?
267
+ request.secret_key_base.present? &&
268
+ request.encrypted_signed_cookie_salt.present? &&
269
+ request.encrypted_cookie_salt.present? &&
270
+ request.use_authenticated_cookie_encryption
271
+ end
272
+
273
+ def encrypted_cookie_cipher
274
+ request.encrypted_cookie_cipher || "aes-256-gcm"
275
+ end
276
+
277
+ def signed_cookie_digest
278
+ request.signed_cookie_digest || "SHA1"
279
+ end
280
+ end
87
281
 
88
282
  class CookieJar #:nodoc:
89
- include Enumerable
283
+ include Enumerable, ChainedCookieJars
90
284
 
91
285
  # This regular expression is used to split the levels of a domain.
92
286
  # The top level domain can be any string without a period or
@@ -102,24 +296,28 @@ module ActionDispatch
102
296
  # $& => example.local
103
297
  DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
104
298
 
105
- def self.build(request)
106
- secret = request.env[TOKEN_KEY]
107
- host = request.host
108
- secure = request.ssl?
109
-
110
- new(secret, host, secure).tap do |hash|
111
- hash.update(request.cookies)
299
+ def self.build(req, cookies)
300
+ new(req).tap do |hash|
301
+ hash.update(cookies)
112
302
  end
113
303
  end
114
304
 
115
- def initialize(secret = nil, host = nil, secure = false)
116
- @secret = secret
305
+ attr_reader :request
306
+
307
+ def initialize(request)
117
308
  @set_cookies = {}
118
309
  @delete_cookies = {}
119
- @host = host
120
- @secure = secure
121
- @closed = false
310
+ @request = request
122
311
  @cookies = {}
312
+ @committed = false
313
+ end
314
+
315
+ def committed?; @committed; end
316
+
317
+ def commit!
318
+ @committed = true
319
+ @set_cookies.freeze
320
+ @delete_cookies.freeze
123
321
  end
124
322
 
125
323
  def each(&block)
@@ -131,205 +329,335 @@ module ActionDispatch
131
329
  @cookies[name.to_s]
132
330
  end
133
331
 
332
+ def fetch(name, *args, &block)
333
+ @cookies.fetch(name.to_s, *args, &block)
334
+ end
335
+
134
336
  def key?(name)
135
337
  @cookies.key?(name.to_s)
136
338
  end
137
339
  alias :has_key? :key?
138
340
 
341
+ # Returns the cookies as Hash.
342
+ alias :to_hash :to_h
343
+
139
344
  def update(other_hash)
140
345
  @cookies.update other_hash.stringify_keys
141
346
  self
142
347
  end
143
348
 
144
- def handle_options(options) #:nodoc:
349
+ def update_cookies_from_jar
350
+ request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
351
+ set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
352
+
353
+ @cookies.update set_cookies if set_cookies
354
+ end
355
+
356
+ def to_header
357
+ @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
358
+ end
359
+
360
+ def handle_options(options) # :nodoc:
361
+ if options[:expires].respond_to?(:from_now)
362
+ options[:expires] = options[:expires].from_now
363
+ end
364
+
145
365
  options[:path] ||= "/"
146
366
 
147
- if options[:domain] == :all
148
- # if there is a provided tld length then we use it otherwise default domain regexp
367
+ if options[:domain] == :all || options[:domain] == "all"
368
+ # If there is a provided tld length then we use it otherwise default domain regexp.
149
369
  domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
150
370
 
151
- # if host is not ip and matches domain regexp
371
+ # If host is not ip and matches domain regexp.
152
372
  # (ip confirms to domain regexp so we explicitly check for ip)
153
- options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
373
+ options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
154
374
  ".#{$&}"
155
375
  end
156
376
  elsif options[:domain].is_a? Array
157
- # if host matches one of the supplied domains without a dot in front of it
158
- options[:domain] = options[:domain].find {|domain| @host.include? domain[/^\.?(.*)$/, 1] }
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(/^\./, "") }
159
379
  end
160
380
  end
161
381
 
162
- # Sets the cookie named +name+. The second argument may be the very cookie
163
- # value, or a hash of options as documented above.
164
- def []=(key, options)
382
+ # Sets the cookie named +name+. The second argument may be the cookie's
383
+ # value or a hash of options as documented above.
384
+ def []=(name, options)
165
385
  if options.is_a?(Hash)
166
386
  options.symbolize_keys!
167
387
  value = options[:value]
168
388
  else
169
389
  value = options
170
- options = { :value => value }
390
+ options = { value: value }
171
391
  end
172
392
 
173
393
  handle_options(options)
174
394
 
175
- if @cookies[key.to_s] != value or options[:expires]
176
- @cookies[key.to_s] = value
177
- @set_cookies[key.to_s] = options
178
- @delete_cookies.delete(key.to_s)
395
+ if @cookies[name.to_s] != value || options[:expires]
396
+ @cookies[name.to_s] = value
397
+ @set_cookies[name.to_s] = options
398
+ @delete_cookies.delete(name.to_s)
179
399
  end
180
400
 
181
401
  value
182
402
  end
183
403
 
184
404
  # Removes the cookie on the client machine by setting the value to an empty string
185
- # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
405
+ # and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
186
406
  # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
187
- def delete(key, options = {})
188
- options.symbolize_keys!
407
+ def delete(name, options = {})
408
+ return unless @cookies.has_key? name.to_s
189
409
 
410
+ options.symbolize_keys!
190
411
  handle_options(options)
191
412
 
192
- value = @cookies.delete(key.to_s)
193
- @delete_cookies[key.to_s] = options
413
+ value = @cookies.delete(name.to_s)
414
+ @delete_cookies[name.to_s] = options
194
415
  value
195
416
  end
196
417
 
197
- # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
198
- def clear(options = {})
199
- @cookies.each_key{ |k| delete(k, options) }
200
- end
201
-
202
- # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
203
- #
204
- # cookies.permanent[:prefers_open_id] = true
205
- # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
206
- #
207
- # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
208
- #
209
- # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
210
- #
211
- # cookies.permanent.signed[:remember_me] = current_user.id
212
- # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
213
- def permanent
214
- @permanent ||= PermanentCookieJar.new(self, @secret)
418
+ # Whether the given cookie is to be deleted by this CookieJar.
419
+ # Like <tt>[]=</tt>, you can pass in an options hash to test if a
420
+ # deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
421
+ def deleted?(name, options = {})
422
+ options.symbolize_keys!
423
+ handle_options(options)
424
+ @delete_cookies[name.to_s] == options
215
425
  end
216
426
 
217
- # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
218
- # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
219
- # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
220
- # be raised.
221
- #
222
- # This jar requires that you set a suitable secret for the verification on your app's config.secret_token.
223
- #
224
- # Example:
225
- #
226
- # cookies.signed[:discount] = 45
227
- # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
228
- #
229
- # cookies.signed[:discount] # => 45
230
- def signed
231
- @signed ||= SignedCookieJar.new(self, @secret)
427
+ # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie.
428
+ def clear(options = {})
429
+ @cookies.each_key { |k| delete(k, options) }
232
430
  end
233
431
 
234
432
  def write(headers)
235
- @set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
236
- @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
237
- end
238
-
239
- def recycle! #:nodoc:
240
- @set_cookies.clear
241
- @delete_cookies.clear
433
+ if header = make_set_cookie_header(headers[HTTP_HEADER])
434
+ headers[HTTP_HEADER] = header
435
+ end
242
436
  end
243
437
 
244
- mattr_accessor :always_write_cookie
245
- self.always_write_cookie = false
438
+ mattr_accessor :always_write_cookie, default: false
246
439
 
247
440
  private
248
441
 
442
+ def escape(string)
443
+ ::Rack::Utils.escape(string)
444
+ end
445
+
446
+ def make_set_cookie_header(header)
447
+ header = @set_cookies.inject(header) { |m, (k, v)|
448
+ if write_cookie?(v)
449
+ ::Rack::Utils.add_cookie_to_header(m, k, v)
450
+ else
451
+ m
452
+ end
453
+ }
454
+ @delete_cookies.inject(header) { |m, (k, v)|
455
+ ::Rack::Utils.add_remove_cookie_to_header(m, k, v)
456
+ }
457
+ end
458
+
249
459
  def write_cookie?(cookie)
250
- @secure || !cookie[:secure] || always_write_cookie
460
+ request.ssl? || !cookie[:secure] || always_write_cookie
251
461
  end
252
462
  end
253
463
 
254
- class PermanentCookieJar < CookieJar #:nodoc:
255
- def initialize(parent_jar, secret)
256
- @parent_jar, @secret = parent_jar, secret
464
+ class AbstractCookieJar # :nodoc:
465
+ include ChainedCookieJars
466
+
467
+ def initialize(parent_jar)
468
+ @parent_jar = parent_jar
257
469
  end
258
470
 
259
- def []=(key, options)
471
+ def [](name)
472
+ if data = @parent_jar[name.to_s]
473
+ parse name, data
474
+ end
475
+ end
476
+
477
+ def []=(name, options)
260
478
  if options.is_a?(Hash)
261
479
  options.symbolize_keys!
262
480
  else
263
- options = { :value => options }
481
+ options = { value: options }
264
482
  end
265
483
 
266
- options[:expires] = 20.years.from_now
267
- @parent_jar[key] = options
484
+ commit(options)
485
+ @parent_jar[name] = options
268
486
  end
269
487
 
270
- def signed
271
- @signed ||= SignedCookieJar.new(self, @secret)
488
+ protected
489
+ def request; @parent_jar.request; end
490
+
491
+ private
492
+ def expiry_options(options)
493
+ if request.use_authenticated_cookie_encryption
494
+ if options[:expires].respond_to?(:from_now)
495
+ { expires_in: options[:expires] }
496
+ else
497
+ { expires_at: options[:expires] }
498
+ end
499
+ else
500
+ {}
501
+ end
502
+ end
503
+
504
+ def parse(name, data); data; end
505
+ def commit(options); end
506
+ end
507
+
508
+ class PermanentCookieJar < AbstractCookieJar # :nodoc:
509
+ private
510
+ def commit(options)
511
+ options[:expires] = 20.years.from_now
512
+ end
513
+ end
514
+
515
+ class JsonSerializer # :nodoc:
516
+ def self.load(value)
517
+ ActiveSupport::JSON.decode(value)
272
518
  end
273
519
 
274
- def method_missing(method, *arguments, &block)
275
- @parent_jar.send(method, *arguments, &block)
520
+ def self.dump(value)
521
+ ActiveSupport::JSON.encode(value)
276
522
  end
277
523
  end
278
524
 
279
- class SignedCookieJar < CookieJar #:nodoc:
280
- MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
281
- SECRET_MIN_LENGTH = 30 # Characters
525
+ module SerializedCookieJars # :nodoc:
526
+ MARSHAL_SIGNATURE = "\x04\x08".freeze
527
+ SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
282
528
 
283
- def initialize(parent_jar, secret)
284
- ensure_secret_secure(secret)
285
- @parent_jar = parent_jar
286
- @verifier = ActiveSupport::MessageVerifier.new(secret)
287
- end
529
+ protected
530
+ def needs_migration?(value)
531
+ request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
532
+ end
288
533
 
289
- def [](name)
290
- if signed_message = @parent_jar[name]
291
- @verifier.verify(signed_message)
534
+ def serialize(value)
535
+ serializer.dump(value)
536
+ end
537
+
538
+ def deserialize(name)
539
+ rotate = false
540
+ value = yield -> { rotate = true }
541
+
542
+ if value
543
+ case
544
+ when needs_migration?(value)
545
+ self[name] = Marshal.load(value)
546
+ when rotate
547
+ self[name] = serializer.load(value)
548
+ else
549
+ serializer.load(value)
550
+ end
551
+ end
552
+ end
553
+
554
+ def serializer
555
+ serializer = request.cookies_serializer || :marshal
556
+ case serializer
557
+ when :marshal
558
+ Marshal
559
+ when :json, :hybrid
560
+ JsonSerializer
561
+ else
562
+ serializer
563
+ end
564
+ end
565
+
566
+ def digest
567
+ request.cookies_digest || "SHA1"
568
+ end
569
+ end
570
+
571
+ class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
572
+ include SerializedCookieJars
573
+
574
+ def initialize(parent_jar)
575
+ super
576
+
577
+ secret = request.key_generator.generate_key(request.signed_cookie_salt)
578
+ @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
579
+
580
+ request.cookies_rotations.signed.each do |*secrets, **options|
581
+ @verifier.rotate(*secrets, serializer: SERIALIZER, **options)
582
+ end
583
+
584
+ if upgrade_legacy_signed_cookies?
585
+ @verifier.rotate request.secret_token, serializer: SERIALIZER
292
586
  end
293
- rescue ActiveSupport::MessageVerifier::InvalidSignature
294
- nil
295
587
  end
296
588
 
297
- def []=(key, options)
298
- if options.is_a?(Hash)
299
- options.symbolize_keys!
300
- options[:value] = @verifier.generate(options[:value])
589
+ private
590
+ def parse(name, signed_message)
591
+ deserialize(name) do |rotate|
592
+ @verifier.verified(signed_message, on_rotation: rotate)
593
+ end
594
+ end
595
+
596
+ def commit(options)
597
+ options[:value] = @verifier.generate(serialize(options[:value]), expiry_options(options))
598
+
599
+ raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
600
+ end
601
+ end
602
+
603
+ class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
604
+ include SerializedCookieJars
605
+
606
+ def initialize(parent_jar)
607
+ super
608
+
609
+ if request.use_authenticated_cookie_encryption
610
+ key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
611
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len)
612
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER)
301
613
  else
302
- options = { :value => @verifier.generate(options) }
614
+ key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")
615
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len)
616
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
617
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
303
618
  end
304
619
 
305
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
306
- @parent_jar[key] = options
307
- end
620
+ request.cookies_rotations.encrypted.each do |*secrets, **options|
621
+ @encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
622
+ end
623
+
624
+ if upgrade_legacy_hmac_aes_cbc_cookies?
625
+ legacy_cipher = "aes-256-cbc"
626
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher))
627
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
308
628
 
309
- def method_missing(method, *arguments, &block)
310
- @parent_jar.send(method, *arguments, &block)
629
+ @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
630
+ end
631
+
632
+ if upgrade_legacy_signed_cookies?
633
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
634
+ end
311
635
  end
312
636
 
313
- protected
637
+ private
638
+ def parse(name, encrypted_message)
639
+ deserialize(name) do |rotate|
640
+ @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
641
+ end
642
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
643
+ parse_legacy_signed_message(name, encrypted_message)
644
+ end
645
+
646
+ def commit(options)
647
+ options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), expiry_options(options))
314
648
 
315
- # To prevent users from using something insecure like "Password" we make sure that the
316
- # secret they've provided is at least 30 characters in length.
317
- def ensure_secret_secure(secret)
318
- if secret.blank?
319
- raise ArgumentError, "A secret is required to generate an " +
320
- "integrity hash for cookie session data. Use " +
321
- "config.secret_token = \"some secret phrase of at " +
322
- "least #{SECRET_MIN_LENGTH} characters\"" +
323
- "in config/initializers/secret_token.rb"
649
+ raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
324
650
  end
325
651
 
326
- if secret.length < SECRET_MIN_LENGTH
327
- raise ArgumentError, "Secret should be something secure, " +
328
- "like \"#{SecureRandom.hex(16)}\". The value you " +
329
- "provided, \"#{secret}\", is shorter than the minimum length " +
330
- "of #{SECRET_MIN_LENGTH} characters"
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
331
660
  end
332
- end
333
661
  end
334
662
 
335
663
  def initialize(app)
@@ -337,13 +665,17 @@ module ActionDispatch
337
665
  end
338
666
 
339
667
  def call(env)
340
- cookie_jar = nil
668
+ request = ActionDispatch::Request.new env
669
+
341
670
  status, headers, body = @app.call(env)
342
671
 
343
- if cookie_jar = env['action_dispatch.cookies']
344
- cookie_jar.write(headers)
345
- if headers[HTTP_HEADER].respond_to?(:join)
346
- headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
672
+ if request.have_cookie_jar?
673
+ cookie_jar = request.cookie_jar
674
+ unless cookie_jar.committed?
675
+ cookie_jar.write(headers)
676
+ if headers[HTTP_HEADER].respond_to?(:join)
677
+ headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
678
+ end
347
679
  end
348
680
  end
349
681