actionpack 4.2.8 → 5.2.4.2
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 +285 -444
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -7
- data/lib/abstract_controller.rb +12 -5
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +45 -49
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
- data/lib/abstract_controller/callbacks.rb +47 -31
- data/lib/abstract_controller/collector.rb +8 -11
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +25 -25
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
- data/lib/abstract_controller/rendering.rb +42 -41
- data/lib/abstract_controller/translation.rb +10 -7
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/action_controller.rb +29 -21
- data/lib/action_controller/api.rb +149 -0
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/base.rb +27 -19
- data/lib/action_controller/caching.rb +14 -57
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +10 -15
- data/lib/action_controller/metal.rb +98 -83
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +118 -44
- data/lib/action_controller/metal/content_security_policy.rb +52 -0
- data/lib/action_controller/metal/cookies.rb +3 -3
- data/lib/action_controller/metal/data_streaming.rb +27 -46
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
- data/lib/action_controller/metal/exceptions.rb +8 -14
- data/lib/action_controller/metal/flash.rb +4 -3
- data/lib/action_controller/metal/force_ssl.rb +23 -21
- data/lib/action_controller/metal/head.rb +21 -19
- data/lib/action_controller/metal/helpers.rb +24 -14
- data/lib/action_controller/metal/http_authentication.rb +64 -57
- data/lib/action_controller/metal/implicit_render.rb +62 -8
- data/lib/action_controller/metal/instrumentation.rb +19 -21
- data/lib/action_controller/metal/live.rb +90 -106
- data/lib/action_controller/metal/mime_responds.rb +33 -46
- data/lib/action_controller/metal/parameter_encoding.rb +51 -0
- data/lib/action_controller/metal/params_wrapper.rb +61 -53
- data/lib/action_controller/metal/redirecting.rb +49 -28
- data/lib/action_controller/metal/renderers.rb +87 -44
- data/lib/action_controller/metal/rendering.rb +72 -50
- data/lib/action_controller/metal/request_forgery_protection.rb +203 -92
- data/lib/action_controller/metal/rescue.rb +9 -16
- data/lib/action_controller/metal/streaming.rb +12 -10
- data/lib/action_controller/metal/strong_parameters.rb +582 -165
- data/lib/action_controller/metal/testing.rb +2 -17
- data/lib/action_controller/metal/url_for.rb +19 -10
- data/lib/action_controller/railtie.rb +28 -10
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +117 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +280 -411
- data/lib/action_dispatch.rb +27 -19
- data/lib/action_dispatch/http/cache.rb +93 -47
- data/lib/action_dispatch/http/content_security_policy.rb +272 -0
- data/lib/action_dispatch/http/filter_parameters.rb +26 -20
- data/lib/action_dispatch/http/filter_redirect.rb +10 -11
- data/lib/action_dispatch/http/headers.rb +55 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +60 -41
- data/lib/action_dispatch/http/mime_type.rb +134 -121
- data/lib/action_dispatch/http/mime_types.rb +20 -6
- data/lib/action_dispatch/http/parameter_filter.rb +25 -11
- data/lib/action_dispatch/http/parameters.rb +98 -39
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +200 -118
- data/lib/action_dispatch/http/response.rb +225 -110
- data/lib/action_dispatch/http/upload.rb +12 -6
- data/lib/action_dispatch/http/url.rb +110 -28
- data/lib/action_dispatch/journey.rb +7 -5
- data/lib/action_dispatch/journey/formatter.rb +55 -32
- data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
- data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
- data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
- data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
- data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
- data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
- data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
- data/lib/action_dispatch/journey/nodes/node.rb +18 -6
- data/lib/action_dispatch/journey/parser.rb +23 -22
- data/lib/action_dispatch/journey/parser.y +3 -2
- data/lib/action_dispatch/journey/parser_extras.rb +12 -4
- data/lib/action_dispatch/journey/path/pattern.rb +50 -44
- data/lib/action_dispatch/journey/route.rb +106 -28
- data/lib/action_dispatch/journey/router.rb +35 -23
- data/lib/action_dispatch/journey/router/utils.rb +20 -11
- data/lib/action_dispatch/journey/routes.rb +18 -16
- data/lib/action_dispatch/journey/scanner.rb +18 -15
- data/lib/action_dispatch/journey/visitors.rb +99 -52
- data/lib/action_dispatch/middleware/callbacks.rb +1 -2
- data/lib/action_dispatch/middleware/cookies.rb +304 -193
- data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +78 -54
- data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
- data/lib/action_dispatch/middleware/reloader.rb +5 -91
- data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
- data/lib/action_dispatch/middleware/request_id.rb +17 -9
- data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -25
- data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
- data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -67
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
- data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
- data/lib/action_dispatch/middleware/ssl.rb +114 -36
- data/lib/action_dispatch/middleware/stack.rb +31 -44
- data/lib/action_dispatch/middleware/static.rb +57 -50
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
- data/lib/action_dispatch/railtie.rb +19 -11
- data/lib/action_dispatch/request/session.rb +106 -59
- data/lib/action_dispatch/request/utils.rb +67 -24
- data/lib/action_dispatch/routing.rb +17 -18
- data/lib/action_dispatch/routing/endpoint.rb +9 -2
- data/lib/action_dispatch/routing/inspector.rb +58 -67
- data/lib/action_dispatch/routing/mapper.rb +734 -447
- data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -139
- data/lib/action_dispatch/routing/redirection.rb +36 -26
- data/lib/action_dispatch/routing/route_set.rb +321 -291
- data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
- data/lib/action_dispatch/routing/url_for.rb +65 -25
- data/lib/action_dispatch/system_test_case.rb +147 -0
- data/lib/action_dispatch/system_testing/browser.rb +49 -0
- data/lib/action_dispatch/system_testing/driver.rb +59 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
- data/lib/action_dispatch/testing/assertion_response.rb +47 -0
- data/lib/action_dispatch/testing/assertions.rb +6 -4
- data/lib/action_dispatch/testing/assertions/response.rb +45 -20
- data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
- data/lib/action_dispatch/testing/integration.rb +347 -209
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +28 -22
- data/lib/action_dispatch/testing/test_request.rb +27 -34
- data/lib/action_dispatch/testing/test_response.rb +35 -7
- data/lib/action_pack.rb +4 -2
- data/lib/action_pack/gem_version.rb +5 -3
- data/lib/action_pack/version.rb +3 -1
- metadata +56 -39
- 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/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- 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,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionController
|
2
4
|
module Head
|
3
5
|
# Returns a response that has no content (merely headers). The options
|
@@ -17,13 +19,17 @@ module ActionController
|
|
17
19
|
#
|
18
20
|
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols.
|
19
21
|
def head(status, options = {})
|
20
|
-
|
21
|
-
|
22
|
+
if status.is_a?(Hash)
|
23
|
+
raise ArgumentError, "#{status.inspect} is not a valid value for `status`."
|
24
|
+
end
|
25
|
+
|
26
|
+
status ||= :ok
|
27
|
+
|
22
28
|
location = options.delete(:location)
|
23
29
|
content_type = options.delete(:content_type)
|
24
30
|
|
25
31
|
options.each do |key, value|
|
26
|
-
headers[key.to_s.dasherize.split(
|
32
|
+
headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s
|
27
33
|
end
|
28
34
|
|
29
35
|
self.status = status
|
@@ -31,28 +37,24 @@ module ActionController
|
|
31
37
|
|
32
38
|
self.response_body = ""
|
33
39
|
|
34
|
-
if include_content?(
|
40
|
+
if include_content?(response_code)
|
35
41
|
self.content_type = content_type || (Mime[formats.first] if formats)
|
36
|
-
|
37
|
-
else
|
38
|
-
headers.delete('Content-Type')
|
39
|
-
headers.delete('Content-Length')
|
42
|
+
response.charset = false
|
40
43
|
end
|
41
|
-
|
44
|
+
|
42
45
|
true
|
43
46
|
end
|
44
47
|
|
45
48
|
private
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
49
|
+
def include_content?(status)
|
50
|
+
case status
|
51
|
+
when 100..199
|
52
|
+
false
|
53
|
+
when 204, 205, 304
|
54
|
+
false
|
55
|
+
else
|
56
|
+
true
|
57
|
+
end
|
55
58
|
end
|
56
|
-
end
|
57
59
|
end
|
58
60
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionController
|
2
4
|
# The \Rails framework provides a large number of helpers for working with assets, dates, forms,
|
3
5
|
# numbers and model objects, to name a few. These helpers are available to all templates
|
@@ -5,10 +7,10 @@ module ActionController
|
|
5
7
|
#
|
6
8
|
# In addition to using the standard template helpers provided, creating custom helpers to
|
7
9
|
# extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
|
8
|
-
# will include all helpers. These helpers are only accessible on the controller through <tt
|
10
|
+
# will include all helpers. These helpers are only accessible on the controller through <tt>#helpers</tt>
|
9
11
|
#
|
10
|
-
# In previous versions of \Rails the controller will include a helper
|
11
|
-
#
|
12
|
+
# In previous versions of \Rails the controller will include a helper which
|
13
|
+
# matches the name of the controller, e.g., <tt>MyController</tt> will automatically
|
12
14
|
# include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
|
13
15
|
#
|
14
16
|
# Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
|
@@ -44,7 +46,7 @@ module ActionController
|
|
44
46
|
# the output might look like this:
|
45
47
|
#
|
46
48
|
# 23 Aug 11:30 | Carolina Railhawks Soccer Match
|
47
|
-
# N/A | Carolina
|
49
|
+
# N/A | Carolina Railhawks Training Workshop
|
48
50
|
#
|
49
51
|
module Helpers
|
50
52
|
extend ActiveSupport::Concern
|
@@ -53,9 +55,8 @@ module ActionController
|
|
53
55
|
include AbstractController::Helpers
|
54
56
|
|
55
57
|
included do
|
56
|
-
class_attribute :helpers_path, :
|
57
|
-
|
58
|
-
self.include_all_helpers = true
|
58
|
+
class_attribute :helpers_path, default: []
|
59
|
+
class_attribute :include_all_helpers, default: true
|
59
60
|
end
|
60
61
|
|
61
62
|
module ClassMethods
|
@@ -71,9 +72,9 @@ module ActionController
|
|
71
72
|
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
|
72
73
|
end
|
73
74
|
|
74
|
-
# Provides a proxy to access
|
75
|
+
# Provides a proxy to access helper methods from outside the view.
|
75
76
|
def helpers
|
76
|
-
@helper_proxy ||= begin
|
77
|
+
@helper_proxy ||= begin
|
77
78
|
proxy = ActionView::Base.new
|
78
79
|
proxy.config = config.inheritable_copy
|
79
80
|
proxy.extend(_helpers)
|
@@ -93,10 +94,14 @@ module ActionController
|
|
93
94
|
super(args)
|
94
95
|
end
|
95
96
|
|
97
|
+
# Returns a list of helper names in a given path.
|
98
|
+
#
|
99
|
+
# ActionController::Base.all_helpers_from_path 'app/helpers'
|
100
|
+
# # => ["application", "chart", "rubygems"]
|
96
101
|
def all_helpers_from_path(path)
|
97
102
|
helpers = Array(path).flat_map do |_path|
|
98
103
|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
|
99
|
-
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
|
104
|
+
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) }
|
100
105
|
names.sort!
|
101
106
|
end
|
102
107
|
helpers.uniq!
|
@@ -104,10 +109,15 @@ module ActionController
|
|
104
109
|
end
|
105
110
|
|
106
111
|
private
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
112
|
+
# Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
|
113
|
+
def all_application_helpers
|
114
|
+
all_helpers_from_path(helpers_path)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Provides a proxy to access helper methods from outside the view.
|
119
|
+
def helpers
|
120
|
+
@_helper_proxy ||= view_context
|
111
121
|
end
|
112
122
|
end
|
113
123
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
require "active_support/security_utils"
|
3
5
|
|
4
6
|
module ActionController
|
5
7
|
# Makes it dead easy to do HTTP Basic, Digest and Token authentication.
|
@@ -28,14 +30,14 @@ module ActionController
|
|
28
30
|
# class ApplicationController < ActionController::Base
|
29
31
|
# before_action :set_account, :authenticate
|
30
32
|
#
|
31
|
-
#
|
33
|
+
# private
|
32
34
|
# def set_account
|
33
35
|
# @account = Account.find_by(url_name: request.subdomains.first)
|
34
36
|
# end
|
35
37
|
#
|
36
38
|
# def authenticate
|
37
39
|
# case request.format
|
38
|
-
# when Mime
|
40
|
+
# when Mime[:xml], Mime[:atom]
|
39
41
|
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
|
40
42
|
# @current_user = user
|
41
43
|
# else
|
@@ -70,25 +72,25 @@ module ActionController
|
|
70
72
|
before_action(options.except(:name, :password, :realm)) do
|
71
73
|
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
|
72
74
|
# This comparison uses & so that it doesn't short circuit and
|
73
|
-
# uses `
|
75
|
+
# uses `secure_compare` so that length information
|
74
76
|
# isn't leaked.
|
75
|
-
ActiveSupport::SecurityUtils.
|
76
|
-
ActiveSupport::SecurityUtils.
|
77
|
+
ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
|
78
|
+
ActiveSupport::SecurityUtils.secure_compare(password, options[:password])
|
77
79
|
end
|
78
80
|
end
|
79
81
|
end
|
80
82
|
end
|
81
83
|
|
82
|
-
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
|
83
|
-
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
|
84
|
+
def authenticate_or_request_with_http_basic(realm = "Application", message = nil, &login_procedure)
|
85
|
+
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm, message)
|
84
86
|
end
|
85
87
|
|
86
88
|
def authenticate_with_http_basic(&login_procedure)
|
87
89
|
HttpAuthentication::Basic.authenticate(request, &login_procedure)
|
88
90
|
end
|
89
91
|
|
90
|
-
def request_http_basic_authentication(realm = "Application")
|
91
|
-
HttpAuthentication::Basic.authentication_request(self, realm)
|
92
|
+
def request_http_basic_authentication(realm = "Application", message = nil)
|
93
|
+
HttpAuthentication::Basic.authentication_request(self, realm, message)
|
92
94
|
end
|
93
95
|
end
|
94
96
|
|
@@ -99,33 +101,34 @@ module ActionController
|
|
99
101
|
end
|
100
102
|
|
101
103
|
def has_basic_credentials?(request)
|
102
|
-
request.authorization.present? && (auth_scheme(request) ==
|
104
|
+
request.authorization.present? && (auth_scheme(request).downcase == "basic")
|
103
105
|
end
|
104
106
|
|
105
107
|
def user_name_and_password(request)
|
106
|
-
decode_credentials(request).split(
|
108
|
+
decode_credentials(request).split(":", 2)
|
107
109
|
end
|
108
110
|
|
109
111
|
def decode_credentials(request)
|
110
|
-
::Base64.decode64(auth_param(request) ||
|
112
|
+
::Base64.decode64(auth_param(request) || "")
|
111
113
|
end
|
112
114
|
|
113
115
|
def auth_scheme(request)
|
114
|
-
request.authorization.split(
|
116
|
+
request.authorization.to_s.split(" ", 2).first
|
115
117
|
end
|
116
118
|
|
117
119
|
def auth_param(request)
|
118
|
-
request.authorization.split(
|
120
|
+
request.authorization.to_s.split(" ", 2).second
|
119
121
|
end
|
120
122
|
|
121
123
|
def encode_credentials(user_name, password)
|
122
124
|
"Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
|
123
125
|
end
|
124
126
|
|
125
|
-
def authentication_request(controller, realm)
|
126
|
-
|
127
|
+
def authentication_request(controller, realm, message)
|
128
|
+
message ||= "HTTP Basic: Access denied.\n"
|
129
|
+
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"'.freeze, "".freeze)}")
|
127
130
|
controller.status = 401
|
128
|
-
controller.response_body =
|
131
|
+
controller.response_body = message
|
129
132
|
end
|
130
133
|
end
|
131
134
|
|
@@ -175,8 +178,8 @@ module ActionController
|
|
175
178
|
extend self
|
176
179
|
|
177
180
|
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)
|
181
|
+
def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
|
182
|
+
authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
|
180
183
|
end
|
181
184
|
|
182
185
|
# Authenticate with HTTP Digest, returns true or false
|
@@ -207,7 +210,7 @@ module ActionController
|
|
207
210
|
password = password_procedure.call(credentials[:username])
|
208
211
|
return false unless password
|
209
212
|
|
210
|
-
method = request.
|
213
|
+
method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD")
|
211
214
|
uri = credentials[:uri]
|
212
215
|
|
213
216
|
[true, false].any? do |trailing_question_mark|
|
@@ -223,19 +226,19 @@ module ActionController
|
|
223
226
|
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
|
224
227
|
# Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
|
225
228
|
# of a plain-text password.
|
226
|
-
def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
|
229
|
+
def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
|
227
230
|
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(
|
231
|
+
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
|
232
|
+
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
|
230
233
|
end
|
231
234
|
|
232
235
|
def ha1(credentials, password)
|
233
|
-
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(
|
236
|
+
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
|
234
237
|
end
|
235
238
|
|
236
239
|
def encode_credentials(http_method, credentials, password, password_is_ha1)
|
237
240
|
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(
|
241
|
+
"Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ")
|
239
242
|
end
|
240
243
|
|
241
244
|
def decode_credentials_header(request)
|
@@ -243,9 +246,9 @@ module ActionController
|
|
243
246
|
end
|
244
247
|
|
245
248
|
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(/^"|"$/,
|
249
|
+
ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair|
|
250
|
+
key, value = pair.split("=", 2)
|
251
|
+
[key.strip, value.to_s.gsub(/^"|"$/, "").delete("'")]
|
249
252
|
end]
|
250
253
|
end
|
251
254
|
|
@@ -264,8 +267,8 @@ module ActionController
|
|
264
267
|
end
|
265
268
|
|
266
269
|
def secret_token(request)
|
267
|
-
key_generator = request.
|
268
|
-
http_auth_salt = request.
|
270
|
+
key_generator = request.key_generator
|
271
|
+
http_auth_salt = request.http_auth_salt
|
269
272
|
key_generator.generate_key(http_auth_salt)
|
270
273
|
end
|
271
274
|
|
@@ -309,21 +312,20 @@ module ActionController
|
|
309
312
|
end
|
310
313
|
|
311
314
|
# 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.
|
315
|
+
# is a PATCH, PUT, or POST, and if the client is a browser or web service.
|
313
316
|
# 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
|
317
|
+
# allow a user to use new nonce without prompting the user again for their
|
315
318
|
# username and password.
|
316
|
-
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
|
319
|
+
def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)
|
317
320
|
return false if value.nil?
|
318
321
|
t = ::Base64.decode64(value).split(":").first.to_i
|
319
322
|
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
|
320
323
|
end
|
321
324
|
|
322
|
-
# Opaque based on
|
325
|
+
# Opaque based on digest of secret key
|
323
326
|
def opaque(secret_key)
|
324
327
|
::Digest::MD5.hexdigest(secret_key)
|
325
328
|
end
|
326
|
-
|
327
329
|
end
|
328
330
|
|
329
331
|
# Makes it dead easy to do HTTP Token authentication.
|
@@ -346,7 +348,9 @@ module ActionController
|
|
346
348
|
# private
|
347
349
|
# def authenticate
|
348
350
|
# authenticate_or_request_with_http_token do |token, options|
|
349
|
-
#
|
351
|
+
# # Compare the tokens in a time-constant manner, to mitigate
|
352
|
+
# # timing attacks.
|
353
|
+
# ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
|
350
354
|
# end
|
351
355
|
# end
|
352
356
|
# end
|
@@ -358,14 +362,14 @@ module ActionController
|
|
358
362
|
# class ApplicationController < ActionController::Base
|
359
363
|
# before_action :set_account, :authenticate
|
360
364
|
#
|
361
|
-
#
|
365
|
+
# private
|
362
366
|
# def set_account
|
363
367
|
# @account = Account.find_by(url_name: request.subdomains.first)
|
364
368
|
# end
|
365
369
|
#
|
366
370
|
# def authenticate
|
367
371
|
# case request.format
|
368
|
-
# when Mime
|
372
|
+
# when Mime[:xml], Mime[:atom]
|
369
373
|
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
|
370
374
|
# @current_user = user
|
371
375
|
# else
|
@@ -400,22 +404,22 @@ module ActionController
|
|
400
404
|
#
|
401
405
|
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
|
402
406
|
module Token
|
403
|
-
TOKEN_KEY =
|
404
|
-
TOKEN_REGEX = /^Token
|
407
|
+
TOKEN_KEY = "token="
|
408
|
+
TOKEN_REGEX = /^(Token|Bearer)\s+/
|
405
409
|
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
|
406
410
|
extend self
|
407
411
|
|
408
412
|
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)
|
413
|
+
def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
|
414
|
+
authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
|
411
415
|
end
|
412
416
|
|
413
417
|
def authenticate_with_http_token(&login_procedure)
|
414
418
|
Token.authenticate(self, &login_procedure)
|
415
419
|
end
|
416
420
|
|
417
|
-
def request_http_token_authentication(realm = "Application")
|
418
|
-
Token.authentication_request(self, realm)
|
421
|
+
def request_http_token_authentication(realm = "Application", message = nil)
|
422
|
+
Token.authentication_request(self, realm, message)
|
419
423
|
end
|
420
424
|
end
|
421
425
|
|
@@ -440,15 +444,17 @@ module ActionController
|
|
440
444
|
end
|
441
445
|
end
|
442
446
|
|
443
|
-
# Parses the token and options out of the token
|
444
|
-
# the header
|
447
|
+
# Parses the token and options out of the token Authorization header.
|
448
|
+
# The value for the Authorization header is expected to have the prefix
|
449
|
+
# <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
|
445
450
|
# Authorization: Token token="abc", nonce="def"
|
446
|
-
# Then the returned token is "abc"
|
451
|
+
# Then the returned token is <tt>"abc"</tt>, and the options are
|
452
|
+
# <tt>{nonce: "def"}</tt>
|
447
453
|
#
|
448
454
|
# request - ActionDispatch::Request instance with the current headers.
|
449
455
|
#
|
450
|
-
# Returns an Array of [String, Hash] if a token is present.
|
451
|
-
# Returns nil if no token is found.
|
456
|
+
# Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
|
457
|
+
# Returns +nil+ if no token is found.
|
452
458
|
def token_and_options(request)
|
453
459
|
authorization_request = request.authorization.to_s
|
454
460
|
if authorization_request[TOKEN_REGEX]
|
@@ -468,14 +474,14 @@ module ActionController
|
|
468
474
|
|
469
475
|
# This removes the <tt>"</tt> characters wrapping the value.
|
470
476
|
def rewrite_param_values(array_params)
|
471
|
-
array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/,
|
477
|
+
array_params.each { |param| (param[1] || "".dup).gsub! %r/^"|"$/, "" }
|
472
478
|
end
|
473
479
|
|
474
480
|
# This method takes an authorization body and splits up the key-value
|
475
481
|
# pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
|
476
482
|
# delimiters defined in +AUTHN_PAIR_DELIMITERS+.
|
477
483
|
def raw_params(auth)
|
478
|
-
_raw_params = auth.sub(TOKEN_REGEX,
|
484
|
+
_raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
|
479
485
|
|
480
486
|
if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
|
481
487
|
_raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
|
@@ -497,15 +503,16 @@ module ActionController
|
|
497
503
|
"Token #{values * ", "}"
|
498
504
|
end
|
499
505
|
|
500
|
-
# Sets a WWW-Authenticate to let the client know a token is desired.
|
506
|
+
# Sets a WWW-Authenticate header to let the client know a token is desired.
|
501
507
|
#
|
502
508
|
# controller - ActionController::Base instance for the outgoing response.
|
503
509
|
# realm - String realm to use in the header.
|
504
510
|
#
|
505
511
|
# Returns nothing.
|
506
|
-
def authentication_request(controller, realm)
|
507
|
-
|
508
|
-
controller.
|
512
|
+
def authentication_request(controller, realm, message = nil)
|
513
|
+
message ||= "HTTP Token: Access denied.\n"
|
514
|
+
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
|
515
|
+
controller.__send__ :render, plain: message, status: :unauthorized
|
509
516
|
end
|
510
517
|
end
|
511
518
|
end
|