actionpack 4.2.10 → 7.2.0.rc1

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