actionpack 4.2.11.1 → 6.1.3.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 +4 -4
- data/CHANGELOG.md +291 -489
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -9
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +81 -51
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/callbacks.rb +61 -33
- data/lib/abstract_controller/collector.rb +9 -13
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +115 -99
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +21 -3
- data/lib/abstract_controller/rendering.rb +48 -47
- data/lib/abstract_controller/translation.rb +17 -8
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +13 -5
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/api.rb +150 -0
- data/lib/action_controller/base.rb +29 -24
- data/lib/action_controller/caching.rb +12 -57
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +17 -19
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +134 -46
- data/lib/action_controller/metal/content_security_policy.rb +51 -0
- data/lib/action_controller/metal/cookies.rb +6 -4
- data/lib/action_controller/metal/data_streaming.rb +30 -50
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +21 -16
- data/lib/action_controller/metal/exceptions.rb +63 -15
- data/lib/action_controller/metal/flash.rb +9 -8
- data/lib/action_controller/metal/head.rb +26 -21
- data/lib/action_controller/metal/helpers.rb +37 -18
- data/lib/action_controller/metal/http_authentication.rb +81 -73
- data/lib/action_controller/metal/implicit_render.rb +53 -9
- data/lib/action_controller/metal/instrumentation.rb +32 -35
- data/lib/action_controller/metal/live.rb +102 -120
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +49 -47
- data/lib/action_controller/metal/parameter_encoding.rb +82 -0
- data/lib/action_controller/metal/params_wrapper.rb +83 -66
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +53 -32
- data/lib/action_controller/metal/renderers.rb +87 -44
- data/lib/action_controller/metal/rendering.rb +77 -50
- data/lib/action_controller/metal/request_forgery_protection.rb +267 -103
- data/lib/action_controller/metal/rescue.rb +10 -17
- data/lib/action_controller/metal/streaming.rb +12 -11
- data/lib/action_controller/metal/strong_parameters.rb +714 -186
- data/lib/action_controller/metal/testing.rb +2 -17
- data/lib/action_controller/metal/url_for.rb +19 -10
- data/lib/action_controller/metal.rb +104 -87
- data/lib/action_controller/railtie.rb +28 -10
- data/lib/action_controller/railties/helpers.rb +3 -1
- data/lib/action_controller/renderer.rb +141 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +296 -422
- data/lib/action_controller.rb +34 -23
- data/lib/action_dispatch/http/cache.rb +107 -56
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +286 -0
- data/lib/action_dispatch/http/filter_parameters.rb +32 -25
- data/lib/action_dispatch/http/filter_redirect.rb +10 -12
- data/lib/action_dispatch/http/headers.rb +55 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +79 -51
- data/lib/action_dispatch/http/mime_type.rb +153 -121
- data/lib/action_dispatch/http/mime_types.rb +20 -6
- data/lib/action_dispatch/http/parameters.rb +90 -40
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +226 -121
- data/lib/action_dispatch/http/response.rb +248 -113
- data/lib/action_dispatch/http/upload.rb +21 -7
- data/lib/action_dispatch/http/url.rb +182 -100
- data/lib/action_dispatch/journey/formatter.rb +90 -43
- data/lib/action_dispatch/journey/gtg/builder.rb +28 -41
- data/lib/action_dispatch/journey/gtg/simulator.rb +11 -16
- data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -21
- data/lib/action_dispatch/journey/nfa/dot.rb +3 -14
- data/lib/action_dispatch/journey/nodes/node.rb +29 -15
- data/lib/action_dispatch/journey/parser.rb +17 -16
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +12 -4
- data/lib/action_dispatch/journey/path/pattern.rb +58 -54
- data/lib/action_dispatch/journey/route.rb +100 -32
- data/lib/action_dispatch/journey/router/utils.rb +29 -18
- data/lib/action_dispatch/journey/router.rb +55 -51
- data/lib/action_dispatch/journey/routes.rb +17 -17
- data/lib/action_dispatch/journey/scanner.rb +26 -17
- data/lib/action_dispatch/journey/visitors.rb +98 -54
- data/lib/action_dispatch/journey.rb +5 -5
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/callbacks.rb +3 -6
- data/lib/action_dispatch/middleware/cookies.rb +347 -217
- data/lib/action_dispatch/middleware/debug_exceptions.rb +135 -63
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +115 -71
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +78 -54
- data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
- data/lib/action_dispatch/middleware/reloader.rb +5 -91
- data/lib/action_dispatch/middleware/remote_ip.rb +53 -45
- data/lib/action_dispatch/middleware/request_id.rb +17 -10
- data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
- data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
- data/lib/action_dispatch/middleware/session/cookie_store.rb +74 -75
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
- data/lib/action_dispatch/middleware/show_exceptions.rb +28 -23
- data/lib/action_dispatch/middleware/ssl.rb +118 -35
- data/lib/action_dispatch/middleware/stack.rb +82 -41
- data/lib/action_dispatch/middleware/static.rb +156 -89
- 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 +4 -14
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +105 -8
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -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 +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.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 +87 -64
- data/lib/action_dispatch/railtie.rb +27 -13
- data/lib/action_dispatch/request/session.rb +109 -61
- data/lib/action_dispatch/request/utils.rb +90 -23
- data/lib/action_dispatch/routing/endpoint.rb +9 -2
- data/lib/action_dispatch/routing/inspector.rb +141 -102
- data/lib/action_dispatch/routing/mapper.rb +811 -473
- data/lib/action_dispatch/routing/polymorphic_routes.rb +167 -143
- data/lib/action_dispatch/routing/redirection.rb +37 -27
- data/lib/action_dispatch/routing/route_set.rb +363 -331
- data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
- data/lib/action_dispatch/routing/url_for.rb +66 -26
- data/lib/action_dispatch/routing.rb +36 -36
- data/lib/action_dispatch/system_test_case.rb +190 -0
- data/lib/action_dispatch/system_testing/browser.rb +86 -0
- data/lib/action_dispatch/system_testing/driver.rb +67 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +138 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +29 -0
- data/lib/action_dispatch/testing/assertion_response.rb +46 -0
- data/lib/action_dispatch/testing/assertions/response.rb +44 -22
- data/lib/action_dispatch/testing/assertions/routing.rb +47 -31
- data/lib/action_dispatch/testing/assertions.rb +6 -4
- data/lib/action_dispatch/testing/integration.rb +391 -220
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +53 -22
- data/lib/action_dispatch/testing/test_request.rb +27 -34
- data/lib/action_dispatch/testing/test_response.rb +11 -11
- data/lib/action_dispatch.rb +35 -21
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +4 -2
- metadata +78 -48
- 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/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,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
|
@@ -54,8 +56,9 @@ module ActionController
|
|
54
56
|
# In your integration tests, you can do something like this:
|
55
57
|
#
|
56
58
|
# def test_access_granted_from_xml
|
57
|
-
#
|
58
|
-
#
|
59
|
+
# authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
|
60
|
+
#
|
61
|
+
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
|
59
62
|
#
|
60
63
|
# assert_equal 200, status
|
61
64
|
# end
|
@@ -66,29 +69,30 @@ module ActionController
|
|
66
69
|
extend ActiveSupport::Concern
|
67
70
|
|
68
71
|
module ClassMethods
|
69
|
-
def http_basic_authenticate_with(
|
70
|
-
before_action(options
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
def http_basic_authenticate_with(name:, password:, realm: nil, **options)
|
73
|
+
before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
|
78
|
+
authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
|
79
|
+
# This comparison uses & so that it doesn't short circuit and
|
80
|
+
# uses `secure_compare` so that length information isn't leaked.
|
81
|
+
ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
|
82
|
+
ActiveSupport::SecurityUtils.secure_compare(given_password, password)
|
79
83
|
end
|
80
84
|
end
|
81
85
|
|
82
|
-
def authenticate_or_request_with_http_basic(realm =
|
83
|
-
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
|
86
|
+
def authenticate_or_request_with_http_basic(realm = nil, message = nil, &login_procedure)
|
87
|
+
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm || "Application", message)
|
84
88
|
end
|
85
89
|
|
86
90
|
def authenticate_with_http_basic(&login_procedure)
|
87
91
|
HttpAuthentication::Basic.authenticate(request, &login_procedure)
|
88
92
|
end
|
89
93
|
|
90
|
-
def request_http_basic_authentication(realm = "Application")
|
91
|
-
HttpAuthentication::Basic.authentication_request(self, realm)
|
94
|
+
def request_http_basic_authentication(realm = "Application", message = nil)
|
95
|
+
HttpAuthentication::Basic.authentication_request(self, realm, message)
|
92
96
|
end
|
93
97
|
end
|
94
98
|
|
@@ -99,33 +103,34 @@ module ActionController
|
|
99
103
|
end
|
100
104
|
|
101
105
|
def has_basic_credentials?(request)
|
102
|
-
request.authorization.present? && (auth_scheme(request) ==
|
106
|
+
request.authorization.present? && (auth_scheme(request).downcase == "basic")
|
103
107
|
end
|
104
108
|
|
105
109
|
def user_name_and_password(request)
|
106
|
-
decode_credentials(request).split(
|
110
|
+
decode_credentials(request).split(":", 2)
|
107
111
|
end
|
108
112
|
|
109
113
|
def decode_credentials(request)
|
110
|
-
::Base64.decode64(auth_param(request) ||
|
114
|
+
::Base64.decode64(auth_param(request) || "")
|
111
115
|
end
|
112
116
|
|
113
117
|
def auth_scheme(request)
|
114
|
-
request.authorization.split(
|
118
|
+
request.authorization.to_s.split(" ", 2).first
|
115
119
|
end
|
116
120
|
|
117
121
|
def auth_param(request)
|
118
|
-
request.authorization.split(
|
122
|
+
request.authorization.to_s.split(" ", 2).second
|
119
123
|
end
|
120
124
|
|
121
125
|
def encode_credentials(user_name, password)
|
122
126
|
"Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
|
123
127
|
end
|
124
128
|
|
125
|
-
def authentication_request(controller, realm)
|
126
|
-
|
129
|
+
def authentication_request(controller, realm, message)
|
130
|
+
message ||= "HTTP Basic: Access denied.\n"
|
131
|
+
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"', "")}")
|
127
132
|
controller.status = 401
|
128
|
-
controller.response_body =
|
133
|
+
controller.response_body = message
|
129
134
|
end
|
130
135
|
end
|
131
136
|
|
@@ -133,7 +138,7 @@ module ActionController
|
|
133
138
|
#
|
134
139
|
# === Simple \Digest example
|
135
140
|
#
|
136
|
-
# require
|
141
|
+
# require "digest/md5"
|
137
142
|
# class PostsController < ApplicationController
|
138
143
|
# REALM = "SuperSecret"
|
139
144
|
# USERS = {"dhh" => "secret", #plain text password
|
@@ -175,8 +180,8 @@ module ActionController
|
|
175
180
|
extend self
|
176
181
|
|
177
182
|
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)
|
183
|
+
def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
|
184
|
+
authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
|
180
185
|
end
|
181
186
|
|
182
187
|
# Authenticate with HTTP Digest, returns true or false
|
@@ -207,7 +212,7 @@ module ActionController
|
|
207
212
|
password = password_procedure.call(credentials[:username])
|
208
213
|
return false unless password
|
209
214
|
|
210
|
-
method = request.
|
215
|
+
method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD")
|
211
216
|
uri = credentials[:uri]
|
212
217
|
|
213
218
|
[true, false].any? do |trailing_question_mark|
|
@@ -223,19 +228,19 @@ module ActionController
|
|
223
228
|
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
|
224
229
|
# Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
|
225
230
|
# of a plain-text password.
|
226
|
-
def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
|
231
|
+
def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
|
227
232
|
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(
|
233
|
+
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
|
234
|
+
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
|
230
235
|
end
|
231
236
|
|
232
237
|
def ha1(credentials, password)
|
233
|
-
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(
|
238
|
+
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
|
234
239
|
end
|
235
240
|
|
236
241
|
def encode_credentials(http_method, credentials, password, password_is_ha1)
|
237
242
|
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(
|
243
|
+
"Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ")
|
239
244
|
end
|
240
245
|
|
241
246
|
def decode_credentials_header(request)
|
@@ -243,9 +248,9 @@ module ActionController
|
|
243
248
|
end
|
244
249
|
|
245
250
|
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(/^"|"$/,
|
251
|
+
ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair|
|
252
|
+
key, value = pair.split("=", 2)
|
253
|
+
[key.strip, value.to_s.gsub(/^"|"$/, "").delete("'")]
|
249
254
|
end]
|
250
255
|
end
|
251
256
|
|
@@ -264,8 +269,8 @@ module ActionController
|
|
264
269
|
end
|
265
270
|
|
266
271
|
def secret_token(request)
|
267
|
-
key_generator = request.
|
268
|
-
http_auth_salt = request.
|
272
|
+
key_generator = request.key_generator
|
273
|
+
http_auth_salt = request.http_auth_salt
|
269
274
|
key_generator.generate_key(http_auth_salt)
|
270
275
|
end
|
271
276
|
|
@@ -309,21 +314,20 @@ module ActionController
|
|
309
314
|
end
|
310
315
|
|
311
316
|
# 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.
|
317
|
+
# is a PATCH, PUT, or POST, and if the client is a browser or web service.
|
313
318
|
# 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
|
319
|
+
# allow a user to use new nonce without prompting the user again for their
|
315
320
|
# username and password.
|
316
|
-
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
|
321
|
+
def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)
|
317
322
|
return false if value.nil?
|
318
323
|
t = ::Base64.decode64(value).split(":").first.to_i
|
319
324
|
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
|
320
325
|
end
|
321
326
|
|
322
|
-
# Opaque based on
|
327
|
+
# Opaque based on digest of secret key
|
323
328
|
def opaque(secret_key)
|
324
329
|
::Digest::MD5.hexdigest(secret_key)
|
325
330
|
end
|
326
|
-
|
327
331
|
end
|
328
332
|
|
329
333
|
# Makes it dead easy to do HTTP Token authentication.
|
@@ -346,7 +350,9 @@ module ActionController
|
|
346
350
|
# private
|
347
351
|
# def authenticate
|
348
352
|
# authenticate_or_request_with_http_token do |token, options|
|
349
|
-
#
|
353
|
+
# # Compare the tokens in a time-constant manner, to mitigate
|
354
|
+
# # timing attacks.
|
355
|
+
# ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
|
350
356
|
# end
|
351
357
|
# end
|
352
358
|
# end
|
@@ -358,14 +364,14 @@ module ActionController
|
|
358
364
|
# class ApplicationController < ActionController::Base
|
359
365
|
# before_action :set_account, :authenticate
|
360
366
|
#
|
361
|
-
#
|
367
|
+
# private
|
362
368
|
# def set_account
|
363
369
|
# @account = Account.find_by(url_name: request.subdomains.first)
|
364
370
|
# end
|
365
371
|
#
|
366
372
|
# def authenticate
|
367
373
|
# case request.format
|
368
|
-
# when Mime
|
374
|
+
# when Mime[:xml], Mime[:atom]
|
369
375
|
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
|
370
376
|
# @current_user = user
|
371
377
|
# else
|
@@ -385,10 +391,9 @@ module ActionController
|
|
385
391
|
# In your integration tests, you can do something like this:
|
386
392
|
#
|
387
393
|
# def test_access_granted_from_xml
|
388
|
-
#
|
389
|
-
#
|
390
|
-
#
|
391
|
-
# )
|
394
|
+
# authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
|
395
|
+
#
|
396
|
+
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
|
392
397
|
#
|
393
398
|
# assert_equal 200, status
|
394
399
|
# end
|
@@ -400,22 +405,22 @@ module ActionController
|
|
400
405
|
#
|
401
406
|
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
|
402
407
|
module Token
|
403
|
-
TOKEN_KEY =
|
404
|
-
TOKEN_REGEX = /^Token
|
405
|
-
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t
|
408
|
+
TOKEN_KEY = "token="
|
409
|
+
TOKEN_REGEX = /^(Token|Bearer)\s+/
|
410
|
+
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t)/
|
406
411
|
extend self
|
407
412
|
|
408
413
|
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)
|
414
|
+
def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
|
415
|
+
authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
|
411
416
|
end
|
412
417
|
|
413
418
|
def authenticate_with_http_token(&login_procedure)
|
414
419
|
Token.authenticate(self, &login_procedure)
|
415
420
|
end
|
416
421
|
|
417
|
-
def request_http_token_authentication(realm = "Application")
|
418
|
-
Token.authentication_request(self, realm)
|
422
|
+
def request_http_token_authentication(realm = "Application", message = nil)
|
423
|
+
Token.authentication_request(self, realm, message)
|
419
424
|
end
|
420
425
|
end
|
421
426
|
|
@@ -440,15 +445,17 @@ module ActionController
|
|
440
445
|
end
|
441
446
|
end
|
442
447
|
|
443
|
-
# Parses the token and options out of the token
|
444
|
-
# the header
|
448
|
+
# Parses the token and options out of the token Authorization header.
|
449
|
+
# The value for the Authorization header is expected to have the prefix
|
450
|
+
# <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
|
445
451
|
# Authorization: Token token="abc", nonce="def"
|
446
|
-
# Then the returned token is "abc"
|
452
|
+
# Then the returned token is <tt>"abc"</tt>, and the options are
|
453
|
+
# <tt>{nonce: "def"}</tt>
|
447
454
|
#
|
448
455
|
# request - ActionDispatch::Request instance with the current headers.
|
449
456
|
#
|
450
|
-
# Returns an Array of [String, Hash] if a token is present.
|
451
|
-
# Returns nil if no token is found.
|
457
|
+
# Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
|
458
|
+
# Returns +nil+ if no token is found.
|
452
459
|
def token_and_options(request)
|
453
460
|
authorization_request = request.authorization.to_s
|
454
461
|
if authorization_request[TOKEN_REGEX]
|
@@ -468,16 +475,16 @@ module ActionController
|
|
468
475
|
|
469
476
|
# This removes the <tt>"</tt> characters wrapping the value.
|
470
477
|
def rewrite_param_values(array_params)
|
471
|
-
array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/,
|
478
|
+
array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
|
472
479
|
end
|
473
480
|
|
474
481
|
# This method takes an authorization body and splits up the key-value
|
475
482
|
# pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
|
476
483
|
# delimiters defined in +AUTHN_PAIR_DELIMITERS+.
|
477
484
|
def raw_params(auth)
|
478
|
-
_raw_params = auth.sub(TOKEN_REGEX,
|
485
|
+
_raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
|
479
486
|
|
480
|
-
if !
|
487
|
+
if !_raw_params.first&.start_with?(TOKEN_KEY)
|
481
488
|
_raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
|
482
489
|
end
|
483
490
|
|
@@ -497,15 +504,16 @@ module ActionController
|
|
497
504
|
"Token #{values * ", "}"
|
498
505
|
end
|
499
506
|
|
500
|
-
# Sets a WWW-Authenticate to let the client know a token is desired.
|
507
|
+
# Sets a WWW-Authenticate header to let the client know a token is desired.
|
501
508
|
#
|
502
509
|
# controller - ActionController::Base instance for the outgoing response.
|
503
510
|
# realm - String realm to use in the header.
|
504
511
|
#
|
505
512
|
# Returns nothing.
|
506
|
-
def authentication_request(controller, realm)
|
507
|
-
|
508
|
-
controller.
|
513
|
+
def authentication_request(controller, realm, message = nil)
|
514
|
+
message ||= "HTTP Token: Access denied.\n"
|
515
|
+
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")
|
516
|
+
controller.__send__ :render, plain: message, status: :unauthorized
|
509
517
|
end
|
510
518
|
end
|
511
519
|
end
|
@@ -1,19 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionController
|
4
|
+
# Handles implicit rendering for a controller action that does not
|
5
|
+
# explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
|
6
|
+
#
|
7
|
+
# For API controllers, the implicit response is always <tt>204 No Content</tt>.
|
8
|
+
#
|
9
|
+
# For all other controllers, we use these heuristics to decide whether to
|
10
|
+
# render a template, raise an error for a missing template, or respond with
|
11
|
+
# <tt>204 No Content</tt>:
|
12
|
+
#
|
13
|
+
# First, if we DO find a template, it's rendered. Template lookup accounts
|
14
|
+
# for the action name, locales, format, variant, template handlers, and more
|
15
|
+
# (see +render+ for details).
|
16
|
+
#
|
17
|
+
# Second, if we DON'T find a template but the controller action does have
|
18
|
+
# templates for other formats, variants, etc., then we trust that you meant
|
19
|
+
# to provide a template for this response, too, and we raise
|
20
|
+
# <tt>ActionController::UnknownFormat</tt> with an explanation.
|
21
|
+
#
|
22
|
+
# Third, if we DON'T find a template AND the request is a page load in a web
|
23
|
+
# browser (technically, a non-XHR GET request for an HTML response) where
|
24
|
+
# you reasonably expect to have rendered a template, then we raise
|
25
|
+
# <tt>ActionController::MissingExactTemplate</tt> with an explanation.
|
26
|
+
#
|
27
|
+
# Finally, if we DON'T find a template AND the request isn't a browser page
|
28
|
+
# load, then we implicitly respond with <tt>204 No Content</tt>.
|
2
29
|
module ImplicitRender
|
3
|
-
|
4
|
-
|
5
|
-
default_render unless performed?
|
6
|
-
ret
|
7
|
-
end
|
30
|
+
# :stopdoc:
|
31
|
+
include BasicImplicitRender
|
8
32
|
|
9
|
-
def default_render
|
10
|
-
|
33
|
+
def default_render
|
34
|
+
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
|
35
|
+
render
|
36
|
+
elsif any_templates?(action_name.to_s, _prefixes)
|
37
|
+
message = "#{self.class.name}\##{action_name} is missing a template " \
|
38
|
+
"for this request format and variant.\n" \
|
39
|
+
"\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \
|
40
|
+
"\nrequest.variant: #{request.variant.inspect}"
|
41
|
+
|
42
|
+
raise ActionController::UnknownFormat, message
|
43
|
+
elsif interactive_browser_request?
|
44
|
+
message = "#{self.class.name}\##{action_name} is missing a template for request formats: #{request.formats.map(&:to_s).join(',')}"
|
45
|
+
raise ActionController::MissingExactTemplate, message
|
46
|
+
else
|
47
|
+
logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
|
48
|
+
super
|
49
|
+
end
|
11
50
|
end
|
12
51
|
|
13
52
|
def method_for_action(action_name)
|
14
53
|
super || if template_exists?(action_name.to_s, _prefixes)
|
15
|
-
|
16
|
-
|
54
|
+
"default_render"
|
55
|
+
end
|
17
56
|
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def interactive_browser_request?
|
60
|
+
request.get? && request.format == Mime[:html] && !request.xhr?
|
61
|
+
end
|
18
62
|
end
|
19
63
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "benchmark"
|
4
|
+
require "abstract_controller/logger"
|
3
5
|
|
4
6
|
module ActionController
|
5
7
|
# Adds instrumentation to several ends in ActionController::Base. It also provides
|
6
|
-
# some hooks related with process_action
|
8
|
+
# some hooks related with process_action. This allows an ORM like Active Record
|
7
9
|
# and/or DataMapper to plug in ActionController and show related information.
|
8
10
|
#
|
9
11
|
# Check ActiveRecord::Railties::ControllerRuntime for an example.
|
@@ -11,34 +13,34 @@ module ActionController
|
|
11
13
|
extend ActiveSupport::Concern
|
12
14
|
|
13
15
|
include AbstractController::Logger
|
14
|
-
include ActionController::RackDelegation
|
15
16
|
|
16
17
|
attr_internal :view_runtime
|
17
18
|
|
18
|
-
def process_action(*
|
19
|
+
def process_action(*)
|
19
20
|
raw_payload = {
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
21
|
+
controller: self.class.name,
|
22
|
+
action: action_name,
|
23
|
+
request: request,
|
24
|
+
params: request.filtered_parameters,
|
25
|
+
headers: request.headers,
|
26
|
+
format: request.format.ref,
|
27
|
+
method: request.request_method,
|
28
|
+
path: request.fullpath
|
26
29
|
}
|
27
30
|
|
28
|
-
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload
|
31
|
+
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
|
29
32
|
|
30
33
|
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
34
|
+
result = super
|
35
|
+
payload[:response] = response
|
36
|
+
payload[:status] = response.status
|
37
|
+
result
|
38
|
+
ensure
|
39
|
+
append_info_to_payload(payload)
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
|
-
def render(*
|
43
|
+
def render(*)
|
42
44
|
render_output = nil
|
43
45
|
self.view_runtime = cleanup_view_runtime do
|
44
46
|
Benchmark.ms { render_output = super }
|
@@ -46,9 +48,9 @@ module ActionController
|
|
46
48
|
render_output
|
47
49
|
end
|
48
50
|
|
49
|
-
def send_file(path, options={})
|
51
|
+
def send_file(path, options = {})
|
50
52
|
ActiveSupport::Notifications.instrument("send_file.action_controller",
|
51
|
-
options.merge(:
|
53
|
+
options.merge(path: path)) do
|
52
54
|
super
|
53
55
|
end
|
54
56
|
end
|
@@ -59,8 +61,8 @@ module ActionController
|
|
59
61
|
end
|
60
62
|
end
|
61
63
|
|
62
|
-
def redirect_to(*
|
63
|
-
ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
|
64
|
+
def redirect_to(*)
|
65
|
+
ActiveSupport::Notifications.instrument("redirect_to.action_controller", request: request) do |payload|
|
64
66
|
result = super
|
65
67
|
payload[:status] = response.status
|
66
68
|
payload[:location] = response.filtered_location
|
@@ -69,28 +71,24 @@ module ActionController
|
|
69
71
|
end
|
70
72
|
|
71
73
|
private
|
72
|
-
|
73
74
|
# A hook invoked every time a before callback is halted.
|
74
|
-
def halted_callback_hook(filter)
|
75
|
-
ActiveSupport::Notifications.instrument("halted_callback.action_controller", :
|
75
|
+
def halted_callback_hook(filter, _)
|
76
|
+
ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
|
76
77
|
end
|
77
78
|
|
78
|
-
# A hook which allows you to clean up any time taken into account in
|
79
|
-
# views
|
79
|
+
# A hook which allows you to clean up any time, wrongly taken into account in
|
80
|
+
# views, like database querying time.
|
80
81
|
#
|
81
82
|
# def cleanup_view_runtime
|
82
83
|
# super - time_taken_in_something_expensive
|
83
84
|
# end
|
84
|
-
#
|
85
|
-
# :api: plugin
|
86
|
-
def cleanup_view_runtime #:nodoc:
|
85
|
+
def cleanup_view_runtime # :doc:
|
87
86
|
yield
|
88
87
|
end
|
89
88
|
|
90
89
|
# Every time after an action is processed, this method is invoked
|
91
90
|
# with the payload, so you can add more information.
|
92
|
-
# :
|
93
|
-
def append_info_to_payload(payload) #:nodoc:
|
91
|
+
def append_info_to_payload(payload) # :doc:
|
94
92
|
payload[:view_runtime] = view_runtime
|
95
93
|
end
|
96
94
|
|
@@ -98,7 +96,6 @@ module ActionController
|
|
98
96
|
# A hook which allows other frameworks to log what happened during
|
99
97
|
# controller process action. This method should return an array
|
100
98
|
# with the messages to be added.
|
101
|
-
# :api: plugin
|
102
99
|
def log_process_action(payload) #:nodoc:
|
103
100
|
messages, view_runtime = [], payload[:view_runtime]
|
104
101
|
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
|