actionpack 7.1.3 → 7.2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -501
  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 +15 -7
  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 +198 -126
  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 +123 -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 +210 -205
  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 +525 -480
  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 +86 -60
  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 +10 -3
  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 +44 -38
  69. data/lib/action_dispatch/http/filter_parameters.rb +18 -9
  70. data/lib/action_dispatch/http/filter_redirect.rb +22 -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 +31 -24
  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 +20 -44
  77. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  78. data/lib/action_dispatch/http/request.rb +94 -75
  79. data/lib/action_dispatch/http/response.rb +73 -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 +8 -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 +31 -21
  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/rescues/missing_exact_template.html.erb +1 -1
  125. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
  126. data/lib/action_dispatch/railtie.rb +2 -4
  127. data/lib/action_dispatch/request/session.rb +23 -21
  128. data/lib/action_dispatch/request/utils.rb +2 -0
  129. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  130. data/lib/action_dispatch/routing/inspector.rb +5 -3
  131. data/lib/action_dispatch/routing/mapper.rb +671 -636
  132. data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
  133. data/lib/action_dispatch/routing/redirection.rb +37 -32
  134. data/lib/action_dispatch/routing/route_set.rb +59 -45
  135. data/lib/action_dispatch/routing/routes_proxy.rb +6 -4
  136. data/lib/action_dispatch/routing/url_for.rb +130 -125
  137. data/lib/action_dispatch/routing.rb +150 -148
  138. data/lib/action_dispatch/system_test_case.rb +91 -81
  139. data/lib/action_dispatch/system_testing/browser.rb +10 -3
  140. data/lib/action_dispatch/system_testing/driver.rb +3 -1
  141. data/lib/action_dispatch/system_testing/server.rb +2 -0
  142. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +32 -21
  143. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  144. data/lib/action_dispatch/testing/assertion_response.rb +8 -6
  145. data/lib/action_dispatch/testing/assertions/response.rb +26 -23
  146. data/lib/action_dispatch/testing/assertions/routing.rb +153 -84
  147. data/lib/action_dispatch/testing/assertions.rb +2 -0
  148. data/lib/action_dispatch/testing/integration.rb +223 -222
  149. data/lib/action_dispatch/testing/request_encoder.rb +2 -0
  150. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  151. data/lib/action_dispatch/testing/test_process.rb +12 -8
  152. data/lib/action_dispatch/testing/test_request.rb +3 -1
  153. data/lib/action_dispatch/testing/test_response.rb +27 -26
  154. data/lib/action_dispatch.rb +22 -28
  155. data/lib/action_pack/gem_version.rb +6 -4
  156. data/lib/action_pack/version.rb +3 -1
  157. data/lib/action_pack.rb +17 -16
  158. metadata +39 -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,67 +143,68 @@ 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)
@@ -212,9 +216,9 @@ module ActionController
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+ should return a non-nil
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,24 +503,21 @@ 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
- WHITESPACED_AUTHN_PAIR_DELIMITERS = /\s*#{AUTHN_PAIR_DELIMITERS}\s*/
511
- private_constant :WHITESPACED_AUTHN_PAIR_DELIMITERS
512
-
513
- # This method takes an authorization body and splits up the key-value
514
- # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
515
- # delimiters defined in +AUTHN_PAIR_DELIMITERS+.
516
+ # This method takes an authorization body and splits up the key-value pairs by
517
+ # the standardized `:`, `;`, or `\t` delimiters defined in
518
+ # `AUTHN_PAIR_DELIMITERS`.
516
519
  def raw_params(auth)
517
- _raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS)
520
+ _raw_params = auth.sub(TOKEN_REGEX, "").split(AUTHN_PAIR_DELIMITERS).map(&:strip)
518
521
  _raw_params.reject!(&:empty?)
519
522
 
520
523
  if !_raw_params.first&.start_with?(TOKEN_KEY)
@@ -528,10 +531,11 @@ module ActionController
528
531
  #
529
532
  # Returns String.
530
533
  #
531
- # ==== Parameters
534
+ # #### Parameters
535
+ #
536
+ # * `token` - String token.
537
+ # * `options` - Optional Hash of the options.
532
538
  #
533
- # * +token+ - String token.
534
- # * +options+ - Optional Hash of the options.
535
539
  def encode_credentials(token, options = {})
536
540
  values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
537
541
  "#{key}=#{value.to_s.inspect}"
@@ -543,10 +547,11 @@ module ActionController
543
547
  #
544
548
  # Returns nothing.
545
549
  #
546
- # ==== Parameters
550
+ # #### Parameters
551
+ #
552
+ # * `controller` - ActionController::Base instance for the outgoing response.
553
+ # * `realm` - String realm to use in the header.
547
554
  #
548
- # * +controller+ - ActionController::Base instance for the outgoing response.
549
- # * +realm+ - String realm to use in the header.
550
555
  def authentication_request(controller, realm, message = nil)
551
556
  message ||= "HTTP Token: Access denied.\n"
552
557
  controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")