actionpack 4.2.11.1 → 6.0.3

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