actionpack 7.1.5.1 → 7.2.0.beta1

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 (157) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -642
  3. data/lib/abstract_controller/asset_paths.rb +2 -0
  4. data/lib/abstract_controller/base.rb +102 -98
  5. data/lib/abstract_controller/caching/fragments.rb +50 -53
  6. data/lib/abstract_controller/caching.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +66 -64
  8. data/lib/abstract_controller/collector.rb +6 -6
  9. data/lib/abstract_controller/deprecator.rb +2 -0
  10. data/lib/abstract_controller/error.rb +2 -0
  11. data/lib/abstract_controller/helpers.rb +70 -85
  12. data/lib/abstract_controller/logger.rb +2 -0
  13. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  14. data/lib/abstract_controller/rendering.rb +13 -12
  15. data/lib/abstract_controller/translation.rb +12 -13
  16. data/lib/abstract_controller/url_for.rb +8 -6
  17. data/lib/abstract_controller.rb +2 -0
  18. data/lib/action_controller/api/api_rendering.rb +2 -0
  19. data/lib/action_controller/api.rb +74 -72
  20. data/lib/action_controller/base.rb +155 -117
  21. data/lib/action_controller/caching.rb +15 -12
  22. data/lib/action_controller/deprecator.rb +2 -0
  23. data/lib/action_controller/form_builder.rb +20 -17
  24. data/lib/action_controller/log_subscriber.rb +3 -1
  25. data/lib/action_controller/metal/allow_browser.rb +119 -0
  26. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  27. data/lib/action_controller/metal/conditional_get.rb +188 -174
  28. data/lib/action_controller/metal/content_security_policy.rb +25 -24
  29. data/lib/action_controller/metal/cookies.rb +4 -2
  30. data/lib/action_controller/metal/data_streaming.rb +64 -55
  31. data/lib/action_controller/metal/default_headers.rb +5 -3
  32. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
  34. data/lib/action_controller/metal/exceptions.rb +11 -9
  35. data/lib/action_controller/metal/flash.rb +12 -10
  36. data/lib/action_controller/metal/head.rb +12 -10
  37. data/lib/action_controller/metal/helpers.rb +63 -55
  38. data/lib/action_controller/metal/http_authentication.rb +214 -203
  39. data/lib/action_controller/metal/implicit_render.rb +17 -15
  40. data/lib/action_controller/metal/instrumentation.rb +15 -12
  41. data/lib/action_controller/metal/live.rb +113 -107
  42. data/lib/action_controller/metal/logging.rb +6 -4
  43. data/lib/action_controller/metal/mime_responds.rb +151 -142
  44. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  45. data/lib/action_controller/metal/params_wrapper.rb +57 -59
  46. data/lib/action_controller/metal/permissions_policy.rb +13 -12
  47. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  48. data/lib/action_controller/metal/redirecting.rb +108 -82
  49. data/lib/action_controller/metal/renderers.rb +50 -49
  50. data/lib/action_controller/metal/rendering.rb +103 -75
  51. data/lib/action_controller/metal/request_forgery_protection.rb +162 -133
  52. data/lib/action_controller/metal/rescue.rb +11 -9
  53. data/lib/action_controller/metal/streaming.rb +138 -136
  54. data/lib/action_controller/metal/strong_parameters.rb +483 -478
  55. data/lib/action_controller/metal/testing.rb +2 -0
  56. data/lib/action_controller/metal/url_for.rb +17 -15
  57. data/lib/action_controller/metal.rb +58 -57
  58. data/lib/action_controller/railtie.rb +3 -0
  59. data/lib/action_controller/railties/helpers.rb +2 -0
  60. data/lib/action_controller/renderer.rb +42 -36
  61. data/lib/action_controller/template_assertions.rb +4 -2
  62. data/lib/action_controller/test_case.rb +146 -126
  63. data/lib/action_controller.rb +5 -1
  64. data/lib/action_dispatch/constants.rb +2 -0
  65. data/lib/action_dispatch/deprecator.rb +2 -0
  66. data/lib/action_dispatch/http/cache.rb +27 -26
  67. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  68. data/lib/action_dispatch/http/content_security_policy.rb +48 -59
  69. data/lib/action_dispatch/http/filter_parameters.rb +13 -14
  70. data/lib/action_dispatch/http/filter_redirect.rb +15 -1
  71. data/lib/action_dispatch/http/headers.rb +22 -22
  72. data/lib/action_dispatch/http/mime_negotiation.rb +30 -41
  73. data/lib/action_dispatch/http/mime_type.rb +25 -21
  74. data/lib/action_dispatch/http/mime_types.rb +2 -0
  75. data/lib/action_dispatch/http/parameters.rb +11 -9
  76. data/lib/action_dispatch/http/permissions_policy.rb +26 -36
  77. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  78. data/lib/action_dispatch/http/request.rb +75 -95
  79. data/lib/action_dispatch/http/response.rb +61 -61
  80. data/lib/action_dispatch/http/upload.rb +18 -16
  81. data/lib/action_dispatch/http/url.rb +75 -73
  82. data/lib/action_dispatch/journey/formatter.rb +13 -6
  83. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  84. data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
  85. data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
  86. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  87. data/lib/action_dispatch/journey/nodes/node.rb +6 -5
  88. data/lib/action_dispatch/journey/parser.rb +4 -3
  89. data/lib/action_dispatch/journey/parser_extras.rb +2 -0
  90. data/lib/action_dispatch/journey/path/pattern.rb +4 -1
  91. data/lib/action_dispatch/journey/route.rb +9 -7
  92. data/lib/action_dispatch/journey/router/utils.rb +16 -15
  93. data/lib/action_dispatch/journey/router.rb +4 -2
  94. data/lib/action_dispatch/journey/routes.rb +4 -2
  95. data/lib/action_dispatch/journey/scanner.rb +4 -2
  96. data/lib/action_dispatch/journey/visitors.rb +2 -0
  97. data/lib/action_dispatch/journey.rb +2 -0
  98. data/lib/action_dispatch/log_subscriber.rb +2 -0
  99. data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
  100. data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
  101. data/lib/action_dispatch/middleware/callbacks.rb +3 -1
  102. data/lib/action_dispatch/middleware/cookies.rb +119 -104
  103. data/lib/action_dispatch/middleware/debug_exceptions.rb +13 -5
  104. data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
  105. data/lib/action_dispatch/middleware/debug_view.rb +2 -0
  106. data/lib/action_dispatch/middleware/exception_wrapper.rb +6 -11
  107. data/lib/action_dispatch/middleware/executor.rb +2 -0
  108. data/lib/action_dispatch/middleware/flash.rb +63 -51
  109. data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
  110. data/lib/action_dispatch/middleware/public_exceptions.rb +8 -6
  111. data/lib/action_dispatch/middleware/reloader.rb +5 -3
  112. data/lib/action_dispatch/middleware/remote_ip.rb +77 -72
  113. data/lib/action_dispatch/middleware/request_id.rb +14 -9
  114. data/lib/action_dispatch/middleware/server_timing.rb +4 -2
  115. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
  116. data/lib/action_dispatch/middleware/session/cache_store.rb +13 -8
  117. data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
  118. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
  119. data/lib/action_dispatch/middleware/show_exceptions.rb +16 -16
  120. data/lib/action_dispatch/middleware/ssl.rb +43 -40
  121. data/lib/action_dispatch/middleware/stack.rb +11 -10
  122. data/lib/action_dispatch/middleware/static.rb +33 -31
  123. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +1 -1
  124. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
  125. data/lib/action_dispatch/railtie.rb +2 -3
  126. data/lib/action_dispatch/request/session.rb +23 -21
  127. data/lib/action_dispatch/request/utils.rb +2 -0
  128. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  129. data/lib/action_dispatch/routing/inspector.rb +6 -4
  130. data/lib/action_dispatch/routing/mapper.rb +623 -625
  131. data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
  132. data/lib/action_dispatch/routing/redirection.rb +37 -32
  133. data/lib/action_dispatch/routing/route_set.rb +60 -46
  134. data/lib/action_dispatch/routing/routes_proxy.rb +6 -4
  135. data/lib/action_dispatch/routing/url_for.rb +130 -125
  136. data/lib/action_dispatch/routing.rb +150 -148
  137. data/lib/action_dispatch/system_test_case.rb +91 -81
  138. data/lib/action_dispatch/system_testing/browser.rb +4 -2
  139. data/lib/action_dispatch/system_testing/driver.rb +2 -0
  140. data/lib/action_dispatch/system_testing/server.rb +2 -0
  141. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +32 -21
  142. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  143. data/lib/action_dispatch/testing/assertion_response.rb +8 -6
  144. data/lib/action_dispatch/testing/assertions/response.rb +26 -23
  145. data/lib/action_dispatch/testing/assertions/routing.rb +153 -84
  146. data/lib/action_dispatch/testing/assertions.rb +2 -0
  147. data/lib/action_dispatch/testing/integration.rb +223 -222
  148. data/lib/action_dispatch/testing/request_encoder.rb +2 -0
  149. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  150. data/lib/action_dispatch/testing/test_process.rb +12 -8
  151. data/lib/action_dispatch/testing/test_request.rb +3 -1
  152. data/lib/action_dispatch/testing/test_response.rb +27 -26
  153. data/lib/action_dispatch.rb +22 -32
  154. data/lib/action_pack/gem_version.rb +6 -4
  155. data/lib/action_pack/version.rb +3 -1
  156. data/lib/action_pack.rb +17 -16
  157. metadata +33 -16
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "base64"
4
6
  require "active_support/security_utils"
5
7
  require "active_support/core_ext/array/access"
@@ -7,62 +9,63 @@ require "active_support/core_ext/array/access"
7
9
  module ActionController
8
10
  # HTTP Basic, Digest, and Token authentication.
9
11
  module HttpAuthentication
10
- # = HTTP \Basic authentication
12
+ # # HTTP Basic authentication
11
13
  #
12
- # === Simple \Basic example
14
+ # ### Simple Basic example
13
15
  #
14
- # class PostsController < ApplicationController
15
- # http_basic_authenticate_with name: "dhh", password: "secret", except: :index
16
+ # class PostsController < ApplicationController
17
+ # http_basic_authenticate_with name: "dhh", password: "secret", except: :index
16
18
  #
17
- # def index
18
- # render plain: "Everyone can see me!"
19
- # end
19
+ # def index
20
+ # render plain: "Everyone can see me!"
21
+ # end
20
22
  #
21
- # def edit
22
- # render plain: "I'm only accessible if you know the password"
23
+ # def edit
24
+ # render plain: "I'm only accessible if you know the password"
25
+ # end
23
26
  # end
24
- # end
25
27
  #
26
- # === Advanced \Basic example
28
+ # ### Advanced Basic example
27
29
  #
28
- # Here is a more advanced \Basic example where only Atom feeds and the XML API are protected by HTTP authentication.
29
- # The regular HTML interface is protected by a session approach:
30
+ # Here is a more advanced Basic example where only Atom feeds and the XML API
31
+ # are protected by HTTP authentication. The regular HTML interface is protected
32
+ # by a session approach:
30
33
  #
31
- # class ApplicationController < ActionController::Base
32
- # before_action :set_account, :authenticate
34
+ # class ApplicationController < ActionController::Base
35
+ # before_action :set_account, :authenticate
33
36
  #
34
- # private
35
- # def set_account
36
- # @account = Account.find_by(url_name: request.subdomains.first)
37
- # end
37
+ # private
38
+ # def set_account
39
+ # @account = Account.find_by(url_name: request.subdomains.first)
40
+ # end
38
41
  #
39
- # def authenticate
40
- # case request.format
41
- # when Mime[:xml], Mime[:atom]
42
- # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
43
- # @current_user = user
44
- # else
45
- # request_http_basic_authentication
46
- # end
47
- # else
48
- # if session_authenticated?
49
- # @current_user = @account.users.find(session[:authenticated][:user_id])
42
+ # def authenticate
43
+ # case request.format
44
+ # when Mime[:xml], Mime[:atom]
45
+ # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
46
+ # @current_user = user
47
+ # else
48
+ # request_http_basic_authentication
49
+ # end
50
50
  # else
51
- # redirect_to(login_url) and return false
51
+ # if session_authenticated?
52
+ # @current_user = @account.users.find(session[:authenticated][:user_id])
53
+ # else
54
+ # redirect_to(login_url) and return false
55
+ # end
52
56
  # end
53
57
  # end
54
- # end
55
- # end
58
+ # end
56
59
  #
57
60
  # In your integration tests, you can do something like this:
58
61
  #
59
- # def test_access_granted_from_xml
60
- # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
62
+ # def test_access_granted_from_xml
63
+ # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
61
64
  #
62
- # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
65
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
63
66
  #
64
- # assert_equal 200, status
65
- # end
67
+ # assert_equal 200, status
68
+ # end
66
69
  module Basic
67
70
  extend self
68
71
 
@@ -70,7 +73,7 @@ module ActionController
70
73
  extend ActiveSupport::Concern
71
74
 
72
75
  module ClassMethods
73
- # Enables HTTP \Basic authentication.
76
+ # Enables HTTP Basic authentication.
74
77
  #
75
78
  # See ActionController::HttpAuthentication::Basic for example usage.
76
79
  def http_basic_authenticate_with(name:, password:, realm: nil, **options)
@@ -82,8 +85,8 @@ module ActionController
82
85
 
83
86
  def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
84
87
  authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
85
- # This comparison uses & so that it doesn't short circuit and
86
- # uses `secure_compare` so that length information isn't leaked.
88
+ # This comparison uses & so that it doesn't short circuit and uses
89
+ # `secure_compare` so that length information isn't leaked.
87
90
  ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) &
88
91
  ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password)
89
92
  end
@@ -140,81 +143,82 @@ module ActionController
140
143
  end
141
144
  end
142
145
 
143
- # = HTTP \Digest authentication
146
+ # # HTTP Digest authentication
144
147
  #
145
- # === Simple \Digest example
148
+ # ### Simple Digest example
146
149
  #
147
- # require "openssl"
148
- # class PostsController < ApplicationController
149
- # REALM = "SuperSecret"
150
- # USERS = {"dhh" => "secret", #plain text password
151
- # "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
150
+ # require "openssl"
151
+ # class PostsController < ApplicationController
152
+ # REALM = "SuperSecret"
153
+ # USERS = {"dhh" => "secret", #plain text password
154
+ # "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
152
155
  #
153
- # before_action :authenticate, except: [:index]
156
+ # before_action :authenticate, except: [:index]
154
157
  #
155
- # def index
156
- # render plain: "Everyone can see me!"
157
- # end
158
+ # def index
159
+ # render plain: "Everyone can see me!"
160
+ # end
158
161
  #
159
- # def edit
160
- # render plain: "I'm only accessible if you know the password"
161
- # end
162
+ # def edit
163
+ # render plain: "I'm only accessible if you know the password"
164
+ # end
162
165
  #
163
- # private
164
- # def authenticate
165
- # authenticate_or_request_with_http_digest(REALM) do |username|
166
- # USERS[username]
166
+ # private
167
+ # def authenticate
168
+ # authenticate_or_request_with_http_digest(REALM) do |username|
169
+ # USERS[username]
170
+ # end
167
171
  # end
168
- # end
169
- # end
172
+ # end
170
173
  #
171
- # === Notes
174
+ # ### Notes
172
175
  #
173
- # The +authenticate_or_request_with_http_digest+ block must return the user's password
174
- # or the ha1 digest hash so the framework can appropriately hash to check the user's
175
- # credentials. Returning +nil+ will cause authentication to fail.
176
+ # The `authenticate_or_request_with_http_digest` block must return the user's
177
+ # password or the ha1 digest hash so the framework can appropriately hash to
178
+ # check the user's credentials. Returning `nil` will cause authentication to
179
+ # fail.
176
180
  #
177
- # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
178
- # the password file or database is compromised, the attacker would be able to use the ha1 hash to
179
- # authenticate as the user at this +realm+, but would not have the user's password to try using at
180
- # other sites.
181
+ # Storing the ha1 hash: MD5(username:realm:password), is better than storing a
182
+ # plain password. If the password file or database is compromised, the attacker
183
+ # would be able to use the ha1 hash to authenticate as the user at this `realm`,
184
+ # but would not have the user's password to try using at other sites.
181
185
  #
182
- # In rare instances, web servers or front proxies strip authorization headers before
183
- # they reach your application. You can debug this situation by logging all environment
184
- # variables, and check for HTTP_AUTHORIZATION, amongst others.
186
+ # In rare instances, web servers or front proxies strip authorization headers
187
+ # before they reach your application. You can debug this situation by logging
188
+ # all environment variables, and check for HTTP_AUTHORIZATION, amongst others.
185
189
  module Digest
186
190
  extend self
187
191
 
188
192
  module ControllerMethods
189
- # Authenticate using an HTTP \Digest, or otherwise render an HTTP header
190
- # requesting the client to send a \Digest.
193
+ # Authenticate using an HTTP Digest, or otherwise render an HTTP header
194
+ # requesting the client to send a Digest.
191
195
  #
192
196
  # See ActionController::HttpAuthentication::Digest for example usage.
193
197
  def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
194
198
  authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
195
199
  end
196
200
 
197
- # Authenticate using an HTTP \Digest. Returns true if authentication is
201
+ # Authenticate using an HTTP Digest. Returns true if authentication is
198
202
  # successful, false otherwise.
199
203
  def authenticate_with_http_digest(realm = "Application", &password_procedure)
200
204
  HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
201
205
  end
202
206
 
203
- # Render an HTTP header requesting the client to send a \Digest for
207
+ # Render an HTTP header requesting the client to send a Digest for
204
208
  # authentication.
205
209
  def request_http_digest_authentication(realm = "Application", message = nil)
206
210
  HttpAuthentication::Digest.authentication_request(self, realm, message)
207
211
  end
208
212
  end
209
213
 
210
- # Returns true on a valid response, false otherwise.
214
+ # Returns false on a valid response, true otherwise.
211
215
  def authenticate(request, realm, &password_procedure)
212
216
  request.authorization && validate_digest_response(request, realm, &password_procedure)
213
217
  end
214
218
 
215
- # Returns false unless the request credentials response value matches the expected value.
216
- # First try the password as a ha1 digest password. If this fails, then try it as a plain
217
- # text password.
219
+ # Returns false unless the request credentials response value matches the
220
+ # expected value. First try the password as a ha1 digest password. If this
221
+ # fails, then try it as a plain text password.
218
222
  def validate_digest_response(request, realm, &password_procedure)
219
223
  secret_key = secret_token(request)
220
224
  credentials = decode_credentials_header(request)
@@ -237,9 +241,10 @@ module ActionController
237
241
  end
238
242
  end
239
243
 
240
- # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
241
- # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
242
- # of a plain-text password.
244
+ # Returns the expected response for a request of `http_method` to `uri` with the
245
+ # decoded `credentials` and the expected `password` Optional parameter
246
+ # `password_is_ha1` is set to `true` by default, since best practice is to store
247
+ # ha1 digest instead of a plain-text password.
243
248
  def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
244
249
  ha1 = password_is_ha1 ? password : ha1(credentials, password)
245
250
  ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
@@ -288,36 +293,40 @@ module ActionController
288
293
 
289
294
  # Uses an MD5 digest based on time to generate a value to be used only once.
290
295
  #
291
- # A server-specified data string which should be uniquely generated each time a 401 response is made.
292
- # It is recommended that this string be base64 or hexadecimal data.
293
- # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
296
+ # A server-specified data string which should be uniquely generated each time a
297
+ # 401 response is made. It is recommended that this string be base64 or
298
+ # hexadecimal data. Specifically, since the string is passed in the header lines
299
+ # as a quoted string, the double-quote character is not allowed.
294
300
  #
295
- # The contents of the nonce are implementation dependent.
296
- # The quality of the implementation depends on a good choice.
297
- # A nonce might, for example, be constructed as the base 64 encoding of
301
+ # The contents of the nonce are implementation dependent. The quality of the
302
+ # implementation depends on a good choice. A nonce might, for example, be
303
+ # constructed as the base 64 encoding of
298
304
  #
299
- # time-stamp H(time-stamp ":" ETag ":" private-key)
305
+ # time-stamp H(time-stamp ":" ETag ":" private-key)
300
306
  #
301
- # where time-stamp is a server-generated time or other non-repeating value,
302
- # ETag is the value of the HTTP ETag header associated with the requested entity,
303
- # and private-key is data known only to the server.
304
- # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
305
- # reject the request if it did not match the nonce from that header or
306
- # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
307
- # The inclusion of the ETag prevents a replay request for an updated version of the resource.
308
- # (Note: including the IP address of the client in the nonce would appear to offer the server the ability
309
- # to limit the reuse of the nonce to the same client that originally got it.
310
- # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
311
- # Also, IP address spoofing is not that hard.)
307
+ # where time-stamp is a server-generated time or other non-repeating value, ETag
308
+ # is the value of the HTTP ETag header associated with the requested entity, and
309
+ # private-key is data known only to the server. With a nonce of this form a
310
+ # server would recalculate the hash portion after receiving the client
311
+ # authentication header and reject the request if it did not match the nonce
312
+ # from that header or if the time-stamp value is not recent enough. In this way
313
+ # the server can limit the time of the nonce's validity. The inclusion of the
314
+ # ETag prevents a replay request for an updated version of the resource. (Note:
315
+ # including the IP address of the client in the nonce would appear to offer the
316
+ # server the ability to limit the reuse of the nonce to the same client that
317
+ # originally got it. However, that would break proxy farms, where requests from
318
+ # a single user often go through different proxies in the farm. Also, IP address
319
+ # spoofing is not that hard.)
312
320
  #
313
- # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
314
- # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
315
- # POST, PUT, or PATCH requests, and a time-stamp for GET requests. For more details on the issues involved see Section 4
316
- # of this document.
321
+ # An implementation might choose not to accept a previously used nonce or a
322
+ # previously used digest, in order to protect against a replay attack. Or, an
323
+ # implementation might choose to use one-time nonces or digests for POST, PUT,
324
+ # or PATCH requests, and a time-stamp for GET requests. For more details on the
325
+ # issues involved see Section 4 of this document.
317
326
  #
318
- # The nonce is opaque to the client. Composed of Time, and hash of Time with secret
319
- # key from the \Rails session secret generated upon creation of project. Ensures
320
- # the time cannot be modified by client.
327
+ # The nonce is opaque to the client. Composed of Time, and hash of Time with
328
+ # secret key from the Rails session secret generated upon creation of project.
329
+ # Ensures the time cannot be modified by client.
321
330
  def nonce(secret_key, time = Time.now)
322
331
  t = time.to_i
323
332
  hashed = [t, secret_key]
@@ -325,11 +334,10 @@ module ActionController
325
334
  ::Base64.strict_encode64("#{t}:#{digest}")
326
335
  end
327
336
 
328
- # Might want a shorter timeout depending on whether the request
329
- # is a PATCH, PUT, or POST, and if the client is a browser or web service.
330
- # Can be much shorter if the Stale directive is implemented. This would
331
- # allow a user to use new nonce without prompting the user again for their
332
- # username and password.
337
+ # Might want a shorter timeout depending on whether the request is a PATCH, PUT,
338
+ # or POST, and if the client is a browser or web service. Can be much shorter if
339
+ # the Stale directive is implemented. This would allow a user to use new nonce
340
+ # without prompting the user again for their username and password.
333
341
  def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)
334
342
  return false if value.nil?
335
343
  t = ::Base64.decode64(value).split(":").first.to_i
@@ -342,80 +350,78 @@ module ActionController
342
350
  end
343
351
  end
344
352
 
345
- # = HTTP \Token authentication
353
+ # # HTTP Token authentication
346
354
  #
347
- # === Simple \Token example
355
+ # ### Simple Token example
348
356
  #
349
- # class PostsController < ApplicationController
350
- # TOKEN = "secret"
357
+ # class PostsController < ApplicationController
358
+ # TOKEN = "secret"
351
359
  #
352
- # before_action :authenticate, except: [ :index ]
353
- #
354
- # def index
355
- # render plain: "Everyone can see me!"
356
- # end
360
+ # before_action :authenticate, except: [ :index ]
357
361
  #
358
- # def edit
359
- # render plain: "I'm only accessible if you know the password"
360
- # end
362
+ # def index
363
+ # render plain: "Everyone can see me!"
364
+ # end
361
365
  #
362
- # private
363
- # def authenticate
364
- # authenticate_or_request_with_http_token do |token, options|
365
- # # Compare the tokens in a time-constant manner, to mitigate
366
- # # timing attacks.
367
- # ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
368
- # end
366
+ # def edit
367
+ # render plain: "I'm only accessible if you know the password"
369
368
  # end
370
- # end
371
369
  #
370
+ # private
371
+ # def authenticate
372
+ # authenticate_or_request_with_http_token do |token, options|
373
+ # # Compare the tokens in a time-constant manner, to mitigate
374
+ # # timing attacks.
375
+ # ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
376
+ # end
377
+ # end
378
+ # end
372
379
  #
373
- # Here is a more advanced Token example where only Atom feeds and the XML API are protected by HTTP token authentication.
374
- # The regular HTML interface is protected by a session approach:
380
+ # Here is a more advanced Token example where only Atom feeds and the XML API
381
+ # are protected by HTTP token authentication. The regular HTML interface is
382
+ # protected by a session approach:
375
383
  #
376
- # class ApplicationController < ActionController::Base
377
- # before_action :set_account, :authenticate
384
+ # class ApplicationController < ActionController::Base
385
+ # before_action :set_account, :authenticate
378
386
  #
379
- # private
380
- # def set_account
381
- # @account = Account.find_by(url_name: request.subdomains.first)
382
- # end
387
+ # private
388
+ # def set_account
389
+ # @account = Account.find_by(url_name: request.subdomains.first)
390
+ # end
383
391
  #
384
- # def authenticate
385
- # case request.format
386
- # when Mime[:xml], Mime[:atom]
387
- # if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
388
- # @current_user = user
389
- # else
390
- # request_http_token_authentication
391
- # end
392
- # else
393
- # if session_authenticated?
394
- # @current_user = @account.users.find(session[:authenticated][:user_id])
392
+ # def authenticate
393
+ # case request.format
394
+ # when Mime[:xml], Mime[:atom]
395
+ # if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
396
+ # @current_user = user
397
+ # else
398
+ # request_http_token_authentication
399
+ # end
395
400
  # else
396
- # redirect_to(login_url) and return false
401
+ # if session_authenticated?
402
+ # @current_user = @account.users.find(session[:authenticated][:user_id])
403
+ # else
404
+ # redirect_to(login_url) and return false
405
+ # end
397
406
  # end
398
407
  # end
399
- # end
400
- # end
401
- #
408
+ # end
402
409
  #
403
410
  # In your integration tests, you can do something like this:
404
411
  #
405
- # def test_access_granted_from_xml
406
- # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
412
+ # def test_access_granted_from_xml
413
+ # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
407
414
  #
408
- # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
409
- #
410
- # assert_equal 200, status
411
- # end
415
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
412
416
  #
417
+ # assert_equal 200, status
418
+ # end
413
419
  #
414
- # On shared hosts, Apache sometimes doesn't pass authentication headers to
415
- # FCGI instances. If your environment matches this description and you cannot
420
+ # On shared hosts, Apache sometimes doesn't pass authentication headers to FCGI
421
+ # instances. If your environment matches this description and you cannot
416
422
  # authenticate, try this rule in your Apache setup:
417
423
  #
418
- # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
424
+ # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
419
425
  module Token
420
426
  TOKEN_KEY = "token="
421
427
  TOKEN_REGEX = /^(Token|Bearer)\s+/
@@ -423,19 +429,18 @@ module ActionController
423
429
  extend self
424
430
 
425
431
  module ControllerMethods
426
- # Authenticate using an HTTP Bearer token, or otherwise render an HTTP
427
- # header requesting the client to send a Bearer token. For the authentication
428
- # to be considered successful, +login_procedure+ must not return a false
429
- # value. Typically, the authenticated user is returned.
432
+ # Authenticate using an HTTP Bearer token, or otherwise render an HTTP header
433
+ # requesting the client to send a Bearer token. For the authentication to be
434
+ # considered successful, `login_procedure` should return a non-nil value.
435
+ # Typically, the authenticated user is returned.
430
436
  #
431
437
  # See ActionController::HttpAuthentication::Token for example usage.
432
438
  def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
433
439
  authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
434
440
  end
435
441
 
436
- # Authenticate using an HTTP Bearer token.
437
- # Returns the return value of +login_procedure+ if a
438
- # token is found. Returns +nil+ if no token is found.
442
+ # Authenticate using an HTTP Bearer token. Returns the return value of
443
+ # `login_procedure` if a token is found. Returns `nil` if no token is found.
439
444
  #
440
445
  # See ActionController::HttpAuthentication::Token for example usage.
441
446
  def authenticate_with_http_token(&login_procedure)
@@ -449,19 +454,20 @@ module ActionController
449
454
  end
450
455
  end
451
456
 
452
- # If token Authorization header is present, call the login
453
- # procedure with the present token and options.
457
+ # If token Authorization header is present, call the login procedure with the
458
+ # present token and options.
454
459
  #
455
- # Returns the return value of +login_procedure+ if a
456
- # token is found. Returns +nil+ if no token is found.
460
+ # Returns the return value of `login_procedure` if a token is found. Returns
461
+ # `nil` if no token is found.
457
462
  #
458
- # ==== Parameters
463
+ # #### Parameters
459
464
  #
460
- # * +controller+ - ActionController::Base instance for the current request.
461
- # * +login_procedure+ - Proc to call if a token is present. The Proc
462
- # should take two arguments:
465
+ # * `controller` - ActionController::Base instance for the current request.
466
+ # * `login_procedure` - Proc to call if a token is present. The Proc should
467
+ # take two arguments:
468
+ #
469
+ # authenticate(controller) { |token, options| ... }
463
470
  #
464
- # authenticate(controller) { |token, options| ... }
465
471
  #
466
472
  def authenticate(controller, &login_procedure)
467
473
  token, options = token_and_options(controller.request)
@@ -470,21 +476,21 @@ module ActionController
470
476
  end
471
477
  end
472
478
 
473
- # Parses the token and options out of the token Authorization header.
474
- # The value for the Authorization header is expected to have the prefix
475
- # <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
479
+ # Parses the token and options out of the token Authorization header. The value
480
+ # for the Authorization header is expected to have the prefix `"Token"` or
481
+ # `"Bearer"`. If the header looks like this:
482
+ #
483
+ # Authorization: Token token="abc", nonce="def"
476
484
  #
477
- # Authorization: Token token="abc", nonce="def"
485
+ # Then the returned token is `"abc"`, and the options are `{nonce: "def"}`.
478
486
  #
479
- # Then the returned token is <tt>"abc"</tt>, and the options are
480
- # <tt>{nonce: "def"}</tt>.
487
+ # Returns an `Array` of `[String, Hash]` if a token is present. Returns `nil` if
488
+ # no token is found.
481
489
  #
482
- # Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
483
- # Returns +nil+ if no token is found.
490
+ # #### Parameters
484
491
  #
485
- # ==== Parameters
492
+ # * `request` - ActionDispatch::Request instance with the current headers.
486
493
  #
487
- # * +request+ - ActionDispatch::Request instance with the current headers.
488
494
  def token_and_options(request)
489
495
  authorization_request = request.authorization.to_s
490
496
  if authorization_request[TOKEN_REGEX]
@@ -497,21 +503,24 @@ module ActionController
497
503
  rewrite_param_values params_array_from raw_params auth
498
504
  end
499
505
 
500
- # Takes +raw_params+ and turns it into an array of parameters.
506
+ # Takes `raw_params` and turns it into an array of parameters.
501
507
  def params_array_from(raw_params)
502
508
  raw_params.map { |param| param.split %r/=(.+)?/ }
503
509
  end
504
510
 
505
- # This removes the <tt>"</tt> characters wrapping the value.
511
+ # This removes the `"` characters wrapping the value.
506
512
  def rewrite_param_values(array_params)
507
513
  array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
508
514
  end
509
515
 
510
- # This method takes an authorization body and splits up the key-value
511
- # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
512
- # delimiters defined in +AUTHN_PAIR_DELIMITERS+.
516
+ WHITESPACED_AUTHN_PAIR_DELIMITERS = /\s*#{AUTHN_PAIR_DELIMITERS}\s*/
517
+ private_constant :WHITESPACED_AUTHN_PAIR_DELIMITERS
518
+
519
+ # This method takes an authorization body and splits up the key-value pairs by
520
+ # the standardized `:`, `;`, or `\t` delimiters defined in
521
+ # `AUTHN_PAIR_DELIMITERS`.
513
522
  def raw_params(auth)
514
- _raw_params = auth.sub(TOKEN_REGEX, "").split(AUTHN_PAIR_DELIMITERS).map(&:strip)
523
+ _raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS)
515
524
  _raw_params.reject!(&:empty?)
516
525
 
517
526
  if !_raw_params.first&.start_with?(TOKEN_KEY)
@@ -525,10 +534,11 @@ module ActionController
525
534
  #
526
535
  # Returns String.
527
536
  #
528
- # ==== Parameters
537
+ # #### Parameters
538
+ #
539
+ # * `token` - String token.
540
+ # * `options` - Optional Hash of the options.
529
541
  #
530
- # * +token+ - String token.
531
- # * +options+ - Optional Hash of the options.
532
542
  def encode_credentials(token, options = {})
533
543
  values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
534
544
  "#{key}=#{value.to_s.inspect}"
@@ -540,10 +550,11 @@ module ActionController
540
550
  #
541
551
  # Returns nothing.
542
552
  #
543
- # ==== Parameters
553
+ # #### Parameters
554
+ #
555
+ # * `controller` - ActionController::Base instance for the outgoing response.
556
+ # * `realm` - String realm to use in the header.
544
557
  #
545
- # * +controller+ - ActionController::Base instance for the outgoing response.
546
- # * +realm+ - String realm to use in the header.
547
558
  def authentication_request(controller, realm, message = nil)
548
559
  message ||= "HTTP Token: Access denied.\n"
549
560
  controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")