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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +86 -600
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -14
- data/lib/abstract_controller/asset_paths.rb +5 -1
- data/lib/abstract_controller/base.rb +166 -136
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +126 -57
- data/lib/abstract_controller/collector.rb +13 -15
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +181 -132
- data/lib/abstract_controller/logger.rb +5 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
- data/lib/abstract_controller/rendering.rb +56 -56
- data/lib/abstract_controller/translation.rb +29 -15
- data/lib/abstract_controller/url_for.rb +15 -11
- data/lib/abstract_controller.rb +21 -5
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +154 -0
- data/lib/action_controller/base.rb +219 -155
- data/lib/action_controller/caching.rb +28 -68
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +35 -22
- data/lib/action_controller/metal/allow_browser.rb +119 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +259 -122
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +9 -5
- data/lib/action_controller/metal/data_streaming.rb +87 -104
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
- data/lib/action_controller/metal/exceptions.rb +71 -24
- data/lib/action_controller/metal/flash.rb +26 -19
- data/lib/action_controller/metal/head.rb +45 -36
- data/lib/action_controller/metal/helpers.rb +80 -64
- data/lib/action_controller/metal/http_authentication.rb +297 -244
- data/lib/action_controller/metal/implicit_render.rb +57 -9
- data/lib/action_controller/metal/instrumentation.rb +76 -64
- data/lib/action_controller/metal/live.rb +238 -176
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +177 -166
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +145 -118
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +203 -64
- data/lib/action_controller/metal/renderers.rb +108 -65
- data/lib/action_controller/metal/rendering.rb +216 -56
- data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
- data/lib/action_controller/metal/rescue.rb +19 -21
- data/lib/action_controller/metal/streaming.rb +179 -138
- data/lib/action_controller/metal/strong_parameters.rb +1058 -382
- data/lib/action_controller/metal/testing.rb +11 -17
- data/lib/action_controller/metal/url_for.rb +37 -21
- data/lib/action_controller/metal.rb +236 -138
- data/lib/action_controller/railtie.rb +89 -11
- data/lib/action_controller/railties/helpers.rb +5 -1
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +425 -497
- data/lib/action_controller.rb +44 -22
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +119 -63
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +364 -0
- data/lib/action_dispatch/http/filter_parameters.rb +36 -34
- data/lib/action_dispatch/http/filter_redirect.rb +24 -12
- data/lib/action_dispatch/http/headers.rb +66 -31
- data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
- data/lib/action_dispatch/http/mime_type.rb +196 -136
- data/lib/action_dispatch/http/mime_types.rb +25 -7
- data/lib/action_dispatch/http/parameters.rb +97 -45
- data/lib/action_dispatch/http/permissions_policy.rb +187 -0
- data/lib/action_dispatch/http/rack_cache.rb +6 -0
- data/lib/action_dispatch/http/request.rb +299 -170
- data/lib/action_dispatch/http/response.rb +311 -160
- data/lib/action_dispatch/http/upload.rb +52 -23
- data/lib/action_dispatch/http/url.rb +201 -125
- data/lib/action_dispatch/journey/formatter.rb +110 -50
- data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
- data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
- data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
- data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
- data/lib/action_dispatch/journey/nodes/node.rb +100 -20
- data/lib/action_dispatch/journey/parser.rb +19 -17
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +14 -4
- data/lib/action_dispatch/journey/path/pattern.rb +79 -63
- data/lib/action_dispatch/journey/route.rb +108 -44
- data/lib/action_dispatch/journey/router/utils.rb +41 -29
- data/lib/action_dispatch/journey/router.rb +64 -57
- data/lib/action_dispatch/journey/routes.rb +23 -21
- data/lib/action_dispatch/journey/scanner.rb +28 -17
- data/lib/action_dispatch/journey/visitors.rb +100 -54
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/journey.rb +7 -5
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +7 -6
- data/lib/action_dispatch/middleware/cookies.rb +471 -328
- data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +143 -101
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
- data/lib/action_dispatch/middleware/reloader.rb +10 -92
- data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
- data/lib/action_dispatch/middleware/request_id.rb +29 -15
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
- data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
- data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
- data/lib/action_dispatch/middleware/ssl.rb +134 -36
- data/lib/action_dispatch/middleware/stack.rb +109 -44
- data/lib/action_dispatch/middleware/static.rb +159 -90
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
- data/lib/action_dispatch/railtie.rb +44 -16
- data/lib/action_dispatch/request/session.rb +159 -69
- data/lib/action_dispatch/request/utils.rb +97 -23
- data/lib/action_dispatch/routing/endpoint.rb +11 -2
- data/lib/action_dispatch/routing/inspector.rb +195 -106
- data/lib/action_dispatch/routing/mapper.rb +1338 -955
- data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
- data/lib/action_dispatch/routing/redirection.rb +78 -51
- data/lib/action_dispatch/routing/route_set.rb +460 -374
- data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
- data/lib/action_dispatch/routing/url_for.rb +172 -124
- data/lib/action_dispatch/routing.rb +159 -158
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +84 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +71 -39
- data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
- data/lib/action_dispatch/testing/assertions.rb +9 -6
- data/lib/action_dispatch/testing/integration.rb +486 -306
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +35 -22
- data/lib/action_dispatch/testing/test_request.rb +29 -34
- data/lib/action_dispatch/testing/test_response.rb +48 -15
- data/lib/action_dispatch.rb +82 -40
- data/lib/action_pack/gem_version.rb +8 -4
- data/lib/action_pack/version.rb +6 -2
- data/lib/action_pack.rb +21 -18
- metadata +146 -56
- data/lib/action_controller/caching/fragments.rb +0 -103
- data/lib/action_controller/metal/force_ssl.rb +0 -97
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/http/parameter_filter.rb +0 -72
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,64 +1,71 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
#
|
10
|
+
# HTTP Basic, Digest, and Token authentication.
|
6
11
|
module HttpAuthentication
|
7
|
-
#
|
12
|
+
# # HTTP Basic authentication
|
8
13
|
#
|
9
|
-
#
|
14
|
+
# ### Simple Basic example
|
10
15
|
#
|
11
|
-
#
|
12
|
-
#
|
16
|
+
# class PostsController < ApplicationController
|
17
|
+
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
|
13
18
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
19
|
+
# def index
|
20
|
+
# render plain: "Everyone can see me!"
|
21
|
+
# end
|
17
22
|
#
|
18
|
-
#
|
19
|
-
#
|
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
|
-
#
|
28
|
+
# ### Advanced Basic example
|
24
29
|
#
|
25
|
-
# Here is a more advanced
|
26
|
-
#
|
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
|
-
#
|
29
|
-
#
|
34
|
+
# class ApplicationController < ActionController::Base
|
35
|
+
# before_action :set_account, :authenticate
|
30
36
|
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
37
|
+
# private
|
38
|
+
# def set_account
|
39
|
+
# @account = Account.find_by(url_name: request.subdomains.first)
|
40
|
+
# end
|
35
41
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
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
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
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
|
-
#
|
52
|
-
# end
|
58
|
+
# end
|
53
59
|
#
|
54
60
|
# In your integration tests, you can do something like this:
|
55
61
|
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
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
|
-
#
|
61
|
-
#
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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 =
|
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) ==
|
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(
|
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(
|
127
|
+
request.authorization.to_s.split(" ", 2).first
|
115
128
|
end
|
116
129
|
|
117
130
|
def auth_param(request)
|
118
|
-
request.authorization.split(
|
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
|
-
|
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 =
|
142
|
+
controller.response_body = message
|
129
143
|
end
|
130
144
|
end
|
131
145
|
|
132
|
-
#
|
146
|
+
# # HTTP Digest authentication
|
133
147
|
#
|
134
|
-
#
|
148
|
+
# ### Simple Digest example
|
135
149
|
#
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
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
|
-
#
|
156
|
+
# before_action :authenticate, except: [:index]
|
143
157
|
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
158
|
+
# def index
|
159
|
+
# render plain: "Everyone can see me!"
|
160
|
+
# end
|
147
161
|
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
162
|
+
# def edit
|
163
|
+
# render plain: "I'm only accessible if you know the password"
|
164
|
+
# end
|
151
165
|
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
#
|
166
|
+
# private
|
167
|
+
# def authenticate
|
168
|
+
# authenticate_or_request_with_http_digest(REALM) do |username|
|
169
|
+
# USERS[username]
|
170
|
+
# end
|
156
171
|
# end
|
157
|
-
#
|
158
|
-
# end
|
172
|
+
# end
|
159
173
|
#
|
160
|
-
#
|
174
|
+
# ### Notes
|
161
175
|
#
|
162
|
-
# The
|
163
|
-
# or the ha1 digest hash so the framework can appropriately hash to
|
164
|
-
# credentials. Returning
|
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
|
167
|
-
# the password file or database is compromised, the attacker
|
168
|
-
#
|
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
|
172
|
-
# they reach your application. You can debug this situation by logging
|
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
|
-
|
179
|
-
|
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
|
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
|
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
|
199
|
-
# First try the password as a ha1 digest password. If this
|
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.
|
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
|
224
|
-
#
|
225
|
-
#
|
226
|
-
|
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+/,
|
247
|
-
key, value = pair.split(
|
248
|
-
[key.strip, value.to_s.gsub(/^"|"$/,
|
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.
|
268
|
-
http_auth_salt = request.
|
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
|
275
|
-
# It is recommended that this string be base64 or
|
276
|
-
# Specifically, since the string is passed in the header lines
|
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
|
-
#
|
280
|
-
#
|
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
|
-
#
|
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
|
-
#
|
286
|
-
#
|
287
|
-
#
|
288
|
-
# reject the request if it did not match the nonce
|
289
|
-
# if the time-stamp value is not recent enough. In this way
|
290
|
-
#
|
291
|
-
#
|
292
|
-
#
|
293
|
-
#
|
294
|
-
#
|
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
|
297
|
-
# protect against a replay attack. Or, an
|
298
|
-
#
|
299
|
-
#
|
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
|
302
|
-
# key from the Rails session secret generated upon creation of project.
|
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
|
-
#
|
313
|
-
#
|
314
|
-
#
|
315
|
-
|
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
|
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
|
-
#
|
353
|
+
# # HTTP Token authentication
|
330
354
|
#
|
331
|
-
# Simple Token example
|
355
|
+
# ### Simple Token example
|
332
356
|
#
|
333
|
-
#
|
334
|
-
#
|
357
|
+
# class PostsController < ApplicationController
|
358
|
+
# TOKEN = "secret"
|
335
359
|
#
|
336
|
-
#
|
360
|
+
# before_action :authenticate, except: [ :index ]
|
337
361
|
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
#
|
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
|
-
#
|
347
|
-
#
|
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
|
356
|
-
#
|
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
|
-
#
|
359
|
-
#
|
384
|
+
# class ApplicationController < ActionController::Base
|
385
|
+
# before_action :set_account, :authenticate
|
360
386
|
#
|
361
|
-
#
|
362
|
-
#
|
363
|
-
#
|
364
|
-
#
|
387
|
+
# private
|
388
|
+
# def set_account
|
389
|
+
# @account = Account.find_by(url_name: request.subdomains.first)
|
390
|
+
# end
|
365
391
|
#
|
366
|
-
#
|
367
|
-
#
|
368
|
-
#
|
369
|
-
#
|
370
|
-
#
|
371
|
-
#
|
372
|
-
#
|
373
|
-
#
|
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
|
-
#
|
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
|
-
#
|
382
|
-
# end
|
383
|
-
#
|
408
|
+
# end
|
384
409
|
#
|
385
410
|
# In your integration tests, you can do something like this:
|
386
411
|
#
|
387
|
-
#
|
388
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
424
|
+
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
|
402
425
|
module Token
|
403
|
-
TOKEN_KEY =
|
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
|
-
|
410
|
-
|
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
|
-
|
418
|
-
|
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
|
-
#
|
457
|
+
# If token Authorization header is present, call the login procedure with the
|
458
|
+
# present token and options.
|
424
459
|
#
|
425
|
-
#
|
426
|
-
#
|
460
|
+
# Returns the return value of `login_procedure` if a token is found. Returns
|
461
|
+
# `nil` if no token is found.
|
427
462
|
#
|
428
|
-
#
|
429
|
-
#
|
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
|
444
|
-
# the header
|
445
|
-
#
|
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
|
-
#
|
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
|
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
|
-
|
475
|
-
|
476
|
-
|
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,
|
523
|
+
_raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS)
|
524
|
+
_raw_params.reject!(&:empty?)
|
479
525
|
|
480
|
-
if !
|
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
|
-
|
507
|
-
|
508
|
-
|
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
|