actionpack 3.2.22.5 → 5.2.4
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 +279 -603
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -297
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +82 -52
- data/lib/abstract_controller/caching/fragments.rb +166 -0
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/callbacks.rb +117 -103
- data/lib/abstract_controller/collector.rb +18 -7
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +65 -38
- data/lib/abstract_controller/logger.rb +3 -2
- data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
- data/lib/abstract_controller/rendering.rb +77 -129
- data/lib/abstract_controller/translation.rb +21 -3
- data/lib/abstract_controller/url_for.rb +9 -7
- data/lib/abstract_controller.rb +12 -13
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/api.rb +149 -0
- data/lib/action_controller/base.rb +81 -40
- data/lib/action_controller/caching.rb +22 -62
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +30 -18
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +190 -47
- 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 +40 -65
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
- data/lib/action_controller/metal/exceptions.rb +19 -12
- data/lib/action_controller/metal/flash.rb +42 -9
- data/lib/action_controller/metal/force_ssl.rb +79 -19
- data/lib/action_controller/metal/head.rb +35 -10
- data/lib/action_controller/metal/helpers.rb +31 -21
- data/lib/action_controller/metal/http_authentication.rb +182 -134
- data/lib/action_controller/metal/implicit_render.rb +62 -8
- data/lib/action_controller/metal/instrumentation.rb +28 -26
- data/lib/action_controller/metal/live.rb +312 -0
- data/lib/action_controller/metal/mime_responds.rb +159 -163
- data/lib/action_controller/metal/parameter_encoding.rb +51 -0
- data/lib/action_controller/metal/params_wrapper.rb +146 -93
- data/lib/action_controller/metal/redirecting.rb +80 -56
- data/lib/action_controller/metal/renderers.rb +119 -47
- data/lib/action_controller/metal/rendering.rb +89 -32
- data/lib/action_controller/metal/request_forgery_protection.rb +373 -41
- data/lib/action_controller/metal/rescue.rb +9 -16
- data/lib/action_controller/metal/streaming.rb +39 -45
- data/lib/action_controller/metal/strong_parameters.rb +1086 -0
- data/lib/action_controller/metal/testing.rb +8 -29
- data/lib/action_controller/metal/url_for.rb +43 -32
- data/lib/action_controller/metal.rb +112 -106
- data/lib/action_controller/railtie.rb +56 -18
- data/lib/action_controller/railties/helpers.rb +24 -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 +402 -347
- data/lib/action_controller.rb +31 -30
- data/lib/action_dispatch/http/cache.rb +133 -34
- data/lib/action_dispatch/http/content_security_policy.rb +272 -0
- data/lib/action_dispatch/http/filter_parameters.rb +40 -24
- data/lib/action_dispatch/http/filter_redirect.rb +37 -0
- data/lib/action_dispatch/http/headers.rb +117 -16
- data/lib/action_dispatch/http/mime_negotiation.rb +98 -33
- data/lib/action_dispatch/http/mime_type.rb +198 -146
- data/lib/action_dispatch/http/mime_types.rb +22 -7
- data/lib/action_dispatch/http/parameter_filter.rb +61 -49
- data/lib/action_dispatch/http/parameters.rb +94 -51
- data/lib/action_dispatch/http/rack_cache.rb +4 -3
- data/lib/action_dispatch/http/request.rb +262 -117
- data/lib/action_dispatch/http/response.rb +400 -86
- data/lib/action_dispatch/http/upload.rb +66 -29
- data/lib/action_dispatch/http/url.rb +232 -60
- data/lib/action_dispatch/journey/formatter.rb +189 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
- data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
- data/lib/action_dispatch/journey/nodes/node.rb +140 -0
- data/lib/action_dispatch/journey/parser.rb +199 -0
- data/lib/action_dispatch/journey/parser.y +50 -0
- data/lib/action_dispatch/journey/parser_extras.rb +31 -0
- data/lib/action_dispatch/journey/path/pattern.rb +199 -0
- data/lib/action_dispatch/journey/route.rb +203 -0
- data/lib/action_dispatch/journey/router/utils.rb +102 -0
- data/lib/action_dispatch/journey/router.rb +156 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +64 -0
- data/lib/action_dispatch/journey/visitors.rb +268 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/middleware/callbacks.rb +17 -13
- data/lib/action_dispatch/middleware/cookies.rb +494 -162
- data/lib/action_dispatch/middleware/debug_exceptions.rb +176 -53
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +103 -38
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +128 -91
- data/lib/action_dispatch/middleware/public_exceptions.rb +43 -16
- data/lib/action_dispatch/middleware/reloader.rb +6 -83
- data/lib/action_dispatch/middleware/remote_ip.rb +151 -49
- data/lib/action_dispatch/middleware/request_id.rb +19 -15
- data/lib/action_dispatch/middleware/session/abstract_store.rb +38 -34
- data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
- data/lib/action_dispatch/middleware/session/cookie_store.rb +94 -44
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -4
- data/lib/action_dispatch/middleware/show_exceptions.rb +36 -61
- data/lib/action_dispatch/middleware/ssl.rb +150 -0
- data/lib/action_dispatch/middleware/stack.rb +33 -41
- data/lib/action_dispatch/middleware/static.rb +92 -48
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -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 +134 -5
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
- data/lib/action_dispatch/railtie.rb +29 -8
- data/lib/action_dispatch/request/session.rb +234 -0
- data/lib/action_dispatch/request/utils.rb +78 -0
- data/lib/action_dispatch/routing/endpoint.rb +17 -0
- data/lib/action_dispatch/routing/inspector.rb +225 -0
- data/lib/action_dispatch/routing/mapper.rb +1329 -582
- data/lib/action_dispatch/routing/polymorphic_routes.rb +237 -94
- data/lib/action_dispatch/routing/redirection.rb +120 -50
- data/lib/action_dispatch/routing/route_set.rb +545 -322
- data/lib/action_dispatch/routing/routes_proxy.rb +37 -7
- data/lib/action_dispatch/routing/url_for.rb +103 -34
- data/lib/action_dispatch/routing.rb +66 -99
- 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/response.rb +53 -42
- data/lib/action_dispatch/testing/assertions/routing.rb +79 -74
- data/lib/action_dispatch/testing/assertions.rb +15 -9
- data/lib/action_dispatch/testing/integration.rb +361 -207
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +28 -19
- data/lib/action_dispatch/testing/test_request.rb +30 -33
- data/lib/action_dispatch/testing/test_response.rb +35 -11
- data/lib/action_dispatch.rb +42 -32
- data/lib/action_pack/gem_version.rb +17 -0
- data/lib/action_pack/version.rb +7 -7
- data/lib/action_pack.rb +4 -2
- metadata +116 -175
- data/lib/abstract_controller/layouts.rb +0 -423
- data/lib/abstract_controller/view_paths.rb +0 -96
- data/lib/action_controller/caching/actions.rb +0 -185
- data/lib/action_controller/caching/fragments.rb +0 -127
- data/lib/action_controller/caching/pages.rb +0 -187
- data/lib/action_controller/caching/sweeping.rb +0 -97
- data/lib/action_controller/deprecated/integration_test.rb +0 -2
- data/lib/action_controller/deprecated/performance_test.rb +0 -1
- data/lib/action_controller/deprecated.rb +0 -3
- data/lib/action_controller/metal/compatibility.rb +0 -65
- data/lib/action_controller/metal/hide_actions.rb +0 -41
- data/lib/action_controller/metal/rack_delegation.rb +0 -26
- data/lib/action_controller/metal/responder.rb +0 -286
- data/lib/action_controller/metal/session_management.rb +0 -14
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/railties/paths.rb +0 -25
- data/lib/action_controller/record_identifier.rb +0 -85
- data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
- data/lib/action_controller/vendor/html-scanner.rb +0 -20
- data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
- data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
- data/lib/action_dispatch/middleware/head.rb +0 -18
- data/lib/action_dispatch/middleware/params_parser.rb +0 -75
- data/lib/action_dispatch/middleware/rescue.rb +0 -26
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -37
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -435
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -138
- data/lib/action_dispatch/testing/performance_test.rb +0 -10
- data/lib/action_view/asset_paths.rb +0 -142
- data/lib/action_view/base.rb +0 -220
- data/lib/action_view/buffers.rb +0 -43
- data/lib/action_view/context.rb +0 -36
- data/lib/action_view/flows.rb +0 -79
- data/lib/action_view/helpers/active_model_helper.rb +0 -50
- data/lib/action_view/helpers/asset_paths.rb +0 -7
- data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
- data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
- data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
- data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
- data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
- data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
- data/lib/action_view/helpers/cache_helper.rb +0 -64
- data/lib/action_view/helpers/capture_helper.rb +0 -203
- data/lib/action_view/helpers/controller_helper.rb +0 -25
- data/lib/action_view/helpers/csrf_helper.rb +0 -32
- data/lib/action_view/helpers/date_helper.rb +0 -1062
- data/lib/action_view/helpers/debug_helper.rb +0 -40
- data/lib/action_view/helpers/form_helper.rb +0 -1486
- data/lib/action_view/helpers/form_options_helper.rb +0 -658
- data/lib/action_view/helpers/form_tag_helper.rb +0 -685
- data/lib/action_view/helpers/javascript_helper.rb +0 -110
- data/lib/action_view/helpers/number_helper.rb +0 -622
- data/lib/action_view/helpers/output_safety_helper.rb +0 -38
- data/lib/action_view/helpers/record_tag_helper.rb +0 -111
- data/lib/action_view/helpers/rendering_helper.rb +0 -92
- data/lib/action_view/helpers/sanitize_helper.rb +0 -259
- data/lib/action_view/helpers/tag_helper.rb +0 -167
- data/lib/action_view/helpers/text_helper.rb +0 -426
- data/lib/action_view/helpers/translation_helper.rb +0 -91
- data/lib/action_view/helpers/url_helper.rb +0 -693
- data/lib/action_view/helpers.rb +0 -60
- data/lib/action_view/locale/en.yml +0 -160
- data/lib/action_view/log_subscriber.rb +0 -28
- data/lib/action_view/lookup_context.rb +0 -258
- data/lib/action_view/path_set.rb +0 -101
- data/lib/action_view/railtie.rb +0 -55
- data/lib/action_view/renderer/abstract_renderer.rb +0 -41
- data/lib/action_view/renderer/partial_renderer.rb +0 -415
- data/lib/action_view/renderer/renderer.rb +0 -61
- data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
- data/lib/action_view/renderer/template_renderer.rb +0 -95
- data/lib/action_view/template/error.rb +0 -128
- data/lib/action_view/template/handlers/builder.rb +0 -26
- data/lib/action_view/template/handlers/erb.rb +0 -125
- data/lib/action_view/template/handlers.rb +0 -50
- data/lib/action_view/template/resolver.rb +0 -298
- data/lib/action_view/template/text.rb +0 -30
- data/lib/action_view/template.rb +0 -337
- data/lib/action_view/test_case.rb +0 -246
- data/lib/action_view/testing/resolvers.rb +0 -49
- data/lib/action_view.rb +0 -84
- data/lib/sprockets/assets.rake +0 -99
- data/lib/sprockets/bootstrap.rb +0 -37
- data/lib/sprockets/compressors.rb +0 -83
- data/lib/sprockets/helpers/isolated_helper.rb +0 -13
- data/lib/sprockets/helpers/rails_helper.rb +0 -182
- data/lib/sprockets/helpers.rb +0 -6
- data/lib/sprockets/railtie.rb +0 -62
- data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,22 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
require "active_support/security_utils"
|
4
5
|
|
5
6
|
module ActionController
|
7
|
+
# Makes it dead easy to do HTTP Basic, Digest and Token authentication.
|
6
8
|
module HttpAuthentication
|
7
|
-
# Makes it dead easy to do HTTP \Basic
|
9
|
+
# Makes it dead easy to do HTTP \Basic authentication.
|
8
10
|
#
|
9
11
|
# === Simple \Basic example
|
10
12
|
#
|
11
13
|
# class PostsController < ApplicationController
|
12
|
-
# http_basic_authenticate_with :
|
14
|
+
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
|
13
15
|
#
|
14
16
|
# def index
|
15
|
-
# render :
|
17
|
+
# render plain: "Everyone can see me!"
|
16
18
|
# end
|
17
19
|
#
|
18
20
|
# def edit
|
19
|
-
# render :
|
21
|
+
# render plain: "I'm only accessible if you know the password"
|
20
22
|
# end
|
21
23
|
# end
|
22
24
|
#
|
@@ -26,16 +28,16 @@ module ActionController
|
|
26
28
|
# the regular HTML interface is protected by a session approach:
|
27
29
|
#
|
28
30
|
# class ApplicationController < ActionController::Base
|
29
|
-
#
|
31
|
+
# before_action :set_account, :authenticate
|
30
32
|
#
|
31
|
-
#
|
33
|
+
# private
|
32
34
|
# def set_account
|
33
|
-
# @account = Account.
|
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,54 +56,11 @@ 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
|
-
# 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
|
60
|
-
# )
|
59
|
+
# @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
|
60
|
+
# get "/notes/1.xml"
|
61
61
|
#
|
62
62
|
# assert_equal 200, status
|
63
63
|
# end
|
64
|
-
#
|
65
|
-
# === Simple \Digest example
|
66
|
-
#
|
67
|
-
# require 'digest/md5'
|
68
|
-
# class PostsController < ApplicationController
|
69
|
-
# REALM = "SuperSecret"
|
70
|
-
# USERS = {"dhh" => "secret", #plain text password
|
71
|
-
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
|
72
|
-
#
|
73
|
-
# before_filter :authenticate, :except => [:index]
|
74
|
-
#
|
75
|
-
# def index
|
76
|
-
# render :text => "Everyone can see me!"
|
77
|
-
# end
|
78
|
-
#
|
79
|
-
# def edit
|
80
|
-
# render :text => "I'm only accessible if you know the password"
|
81
|
-
# end
|
82
|
-
#
|
83
|
-
# private
|
84
|
-
# def authenticate
|
85
|
-
# authenticate_or_request_with_http_digest(REALM) do |username|
|
86
|
-
# USERS[username]
|
87
|
-
# end
|
88
|
-
# end
|
89
|
-
# end
|
90
|
-
#
|
91
|
-
# === Notes
|
92
|
-
#
|
93
|
-
# The +authenticate_or_request_with_http_digest+ block must return the user's password
|
94
|
-
# or the ha1 digest hash so the framework can appropriately hash to check the user's
|
95
|
-
# credentials. Returning +nil+ will cause authentication to fail.
|
96
|
-
#
|
97
|
-
# Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
|
98
|
-
# the password file or database is compromised, the attacker would be able to use the ha1 hash to
|
99
|
-
# authenticate as the user at this +realm+, but would not have the user's password to try using at
|
100
|
-
# other sites.
|
101
|
-
#
|
102
|
-
# In rare instances, web servers or front proxies strip authorization headers before
|
103
|
-
# they reach your application. You can debug this situation by logging all environment
|
104
|
-
# variables, and check for HTTP_AUTHORIZATION, amongst others.
|
105
64
|
module Basic
|
106
65
|
extend self
|
107
66
|
|
@@ -110,62 +69,117 @@ module ActionController
|
|
110
69
|
|
111
70
|
module ClassMethods
|
112
71
|
def http_basic_authenticate_with(options = {})
|
113
|
-
|
72
|
+
before_action(options.except(:name, :password, :realm)) do
|
114
73
|
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
|
115
74
|
# This comparison uses & so that it doesn't short circuit and
|
116
|
-
# uses `
|
75
|
+
# uses `secure_compare` so that length information
|
117
76
|
# isn't leaked.
|
118
|
-
ActiveSupport::SecurityUtils.
|
119
|
-
ActiveSupport::SecurityUtils.
|
77
|
+
ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
|
78
|
+
ActiveSupport::SecurityUtils.secure_compare(password, options[:password])
|
120
79
|
end
|
121
80
|
end
|
122
81
|
end
|
123
82
|
end
|
124
83
|
|
125
|
-
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
|
126
|
-
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)
|
127
86
|
end
|
128
87
|
|
129
88
|
def authenticate_with_http_basic(&login_procedure)
|
130
89
|
HttpAuthentication::Basic.authenticate(request, &login_procedure)
|
131
90
|
end
|
132
91
|
|
133
|
-
def request_http_basic_authentication(realm = "Application")
|
134
|
-
HttpAuthentication::Basic.authentication_request(self, realm)
|
92
|
+
def request_http_basic_authentication(realm = "Application", message = nil)
|
93
|
+
HttpAuthentication::Basic.authentication_request(self, realm, message)
|
135
94
|
end
|
136
95
|
end
|
137
96
|
|
138
97
|
def authenticate(request, &login_procedure)
|
139
|
-
|
98
|
+
if has_basic_credentials?(request)
|
140
99
|
login_procedure.call(*user_name_and_password(request))
|
141
100
|
end
|
142
101
|
end
|
143
102
|
|
103
|
+
def has_basic_credentials?(request)
|
104
|
+
request.authorization.present? && (auth_scheme(request).downcase == "basic")
|
105
|
+
end
|
106
|
+
|
144
107
|
def user_name_and_password(request)
|
145
|
-
decode_credentials(request).split(
|
108
|
+
decode_credentials(request).split(":", 2)
|
146
109
|
end
|
147
110
|
|
148
111
|
def decode_credentials(request)
|
149
|
-
::Base64.decode64(request
|
112
|
+
::Base64.decode64(auth_param(request) || "")
|
113
|
+
end
|
114
|
+
|
115
|
+
def auth_scheme(request)
|
116
|
+
request.authorization.to_s.split(" ", 2).first
|
117
|
+
end
|
118
|
+
|
119
|
+
def auth_param(request)
|
120
|
+
request.authorization.to_s.split(" ", 2).second
|
150
121
|
end
|
151
122
|
|
152
123
|
def encode_credentials(user_name, password)
|
153
124
|
"Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
|
154
125
|
end
|
155
126
|
|
156
|
-
def authentication_request(controller, realm)
|
157
|
-
|
158
|
-
controller.
|
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)}")
|
159
130
|
controller.status = 401
|
131
|
+
controller.response_body = message
|
160
132
|
end
|
161
133
|
end
|
162
134
|
|
135
|
+
# Makes it dead easy to do HTTP \Digest authentication.
|
136
|
+
#
|
137
|
+
# === Simple \Digest example
|
138
|
+
#
|
139
|
+
# require 'digest/md5'
|
140
|
+
# class PostsController < ApplicationController
|
141
|
+
# REALM = "SuperSecret"
|
142
|
+
# USERS = {"dhh" => "secret", #plain text password
|
143
|
+
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
|
144
|
+
#
|
145
|
+
# before_action :authenticate, except: [:index]
|
146
|
+
#
|
147
|
+
# def index
|
148
|
+
# render plain: "Everyone can see me!"
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# def edit
|
152
|
+
# render plain: "I'm only accessible if you know the password"
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# private
|
156
|
+
# def authenticate
|
157
|
+
# authenticate_or_request_with_http_digest(REALM) do |username|
|
158
|
+
# USERS[username]
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# === Notes
|
164
|
+
#
|
165
|
+
# The +authenticate_or_request_with_http_digest+ block must return the user's password
|
166
|
+
# or the ha1 digest hash so the framework can appropriately hash to check the user's
|
167
|
+
# credentials. Returning +nil+ will cause authentication to fail.
|
168
|
+
#
|
169
|
+
# Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
|
170
|
+
# the password file or database is compromised, the attacker would be able to use the ha1 hash to
|
171
|
+
# authenticate as the user at this +realm+, but would not have the user's password to try using at
|
172
|
+
# other sites.
|
173
|
+
#
|
174
|
+
# In rare instances, web servers or front proxies strip authorization headers before
|
175
|
+
# they reach your application. You can debug this situation by logging all environment
|
176
|
+
# variables, and check for HTTP_AUTHORIZATION, amongst others.
|
163
177
|
module Digest
|
164
178
|
extend self
|
165
179
|
|
166
180
|
module ControllerMethods
|
167
|
-
def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
|
168
|
-
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)
|
169
183
|
end
|
170
184
|
|
171
185
|
# Authenticate with HTTP Digest, returns true or false
|
@@ -196,8 +210,8 @@ module ActionController
|
|
196
210
|
password = password_procedure.call(credentials[:username])
|
197
211
|
return false unless password
|
198
212
|
|
199
|
-
method = request.
|
200
|
-
uri = credentials[:uri]
|
213
|
+
method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD")
|
214
|
+
uri = credentials[:uri]
|
201
215
|
|
202
216
|
[true, false].any? do |trailing_question_mark|
|
203
217
|
[true, false].any? do |password_is_ha1|
|
@@ -212,19 +226,19 @@ module ActionController
|
|
212
226
|
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
|
213
227
|
# Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
|
214
228
|
# of a plain-text password.
|
215
|
-
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)
|
216
230
|
ha1 = password_is_ha1 ? password : ha1(credentials, password)
|
217
|
-
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(
|
218
|
-
::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(":"))
|
219
233
|
end
|
220
234
|
|
221
235
|
def ha1(credentials, password)
|
222
|
-
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(
|
236
|
+
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
|
223
237
|
end
|
224
238
|
|
225
239
|
def encode_credentials(http_method, credentials, password, password_is_ha1)
|
226
240
|
credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
|
227
|
-
"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(", ")
|
228
242
|
end
|
229
243
|
|
230
244
|
def decode_credentials_header(request)
|
@@ -232,9 +246,9 @@ module ActionController
|
|
232
246
|
end
|
233
247
|
|
234
248
|
def decode_credentials(header)
|
235
|
-
HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/,
|
236
|
-
key, value = pair.split(
|
237
|
-
[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("'")]
|
238
252
|
end]
|
239
253
|
end
|
240
254
|
|
@@ -248,14 +262,14 @@ module ActionController
|
|
248
262
|
def authentication_request(controller, realm, message = nil)
|
249
263
|
message ||= "HTTP Digest: Access denied.\n"
|
250
264
|
authentication_header(controller, realm)
|
251
|
-
controller.response_body = message
|
252
265
|
controller.status = 401
|
266
|
+
controller.response_body = message
|
253
267
|
end
|
254
268
|
|
255
269
|
def secret_token(request)
|
256
|
-
|
257
|
-
|
258
|
-
|
270
|
+
key_generator = request.key_generator
|
271
|
+
http_auth_salt = request.http_auth_salt
|
272
|
+
key_generator.generate_key(http_auth_salt)
|
259
273
|
end
|
260
274
|
|
261
275
|
# Uses an MD5 digest based on time to generate a value to be used only once.
|
@@ -268,7 +282,7 @@ module ActionController
|
|
268
282
|
# The quality of the implementation depends on a good choice.
|
269
283
|
# A nonce might, for example, be constructed as the base 64 encoding of
|
270
284
|
#
|
271
|
-
#
|
285
|
+
# time-stamp H(time-stamp ":" ETag ":" private-key)
|
272
286
|
#
|
273
287
|
# where time-stamp is a server-generated time or other non-repeating value,
|
274
288
|
# ETag is the value of the HTTP ETag header associated with the requested entity,
|
@@ -284,7 +298,7 @@ module ActionController
|
|
284
298
|
#
|
285
299
|
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
|
286
300
|
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
|
287
|
-
# POST or
|
301
|
+
# POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
|
288
302
|
# of this document.
|
289
303
|
#
|
290
304
|
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
|
@@ -294,24 +308,24 @@ module ActionController
|
|
294
308
|
t = time.to_i
|
295
309
|
hashed = [t, secret_key]
|
296
310
|
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
|
297
|
-
::Base64.
|
311
|
+
::Base64.strict_encode64("#{t}:#{digest}")
|
298
312
|
end
|
299
313
|
|
300
314
|
# Might want a shorter timeout depending on whether the request
|
301
|
-
# is a 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.
|
302
316
|
# Can be much shorter if the Stale directive is implemented. This would
|
303
|
-
# 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
|
304
318
|
# username and password.
|
305
|
-
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)
|
320
|
+
return false if value.nil?
|
306
321
|
t = ::Base64.decode64(value).split(":").first.to_i
|
307
322
|
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
|
308
323
|
end
|
309
324
|
|
310
|
-
# Opaque based on
|
325
|
+
# Opaque based on digest of secret key
|
311
326
|
def opaque(secret_key)
|
312
327
|
::Digest::MD5.hexdigest(secret_key)
|
313
328
|
end
|
314
|
-
|
315
329
|
end
|
316
330
|
|
317
331
|
# Makes it dead easy to do HTTP Token authentication.
|
@@ -321,20 +335,22 @@ module ActionController
|
|
321
335
|
# class PostsController < ApplicationController
|
322
336
|
# TOKEN = "secret"
|
323
337
|
#
|
324
|
-
#
|
338
|
+
# before_action :authenticate, except: [ :index ]
|
325
339
|
#
|
326
340
|
# def index
|
327
|
-
# render :
|
341
|
+
# render plain: "Everyone can see me!"
|
328
342
|
# end
|
329
343
|
#
|
330
344
|
# def edit
|
331
|
-
# render :
|
345
|
+
# render plain: "I'm only accessible if you know the password"
|
332
346
|
# end
|
333
347
|
#
|
334
348
|
# private
|
335
349
|
# def authenticate
|
336
350
|
# authenticate_or_request_with_http_token do |token, options|
|
337
|
-
#
|
351
|
+
# # Compare the tokens in a time-constant manner, to mitigate
|
352
|
+
# # timing attacks.
|
353
|
+
# ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
|
338
354
|
# end
|
339
355
|
# end
|
340
356
|
# end
|
@@ -344,16 +360,16 @@ module ActionController
|
|
344
360
|
# the regular HTML interface is protected by a session approach:
|
345
361
|
#
|
346
362
|
# class ApplicationController < ActionController::Base
|
347
|
-
#
|
363
|
+
# before_action :set_account, :authenticate
|
348
364
|
#
|
349
|
-
#
|
365
|
+
# private
|
350
366
|
# def set_account
|
351
|
-
# @account = Account.
|
367
|
+
# @account = Account.find_by(url_name: request.subdomains.first)
|
352
368
|
# end
|
353
369
|
#
|
354
370
|
# def authenticate
|
355
371
|
# case request.format
|
356
|
-
# when Mime
|
372
|
+
# when Mime[:xml], Mime[:atom]
|
357
373
|
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
|
358
374
|
# @current_user = user
|
359
375
|
# else
|
@@ -375,7 +391,7 @@ module ActionController
|
|
375
391
|
# def test_access_granted_from_xml
|
376
392
|
# get(
|
377
393
|
# "/notes/1.xml", nil,
|
378
|
-
#
|
394
|
+
# 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
|
379
395
|
# )
|
380
396
|
#
|
381
397
|
# assert_equal 200, status
|
@@ -388,32 +404,39 @@ module ActionController
|
|
388
404
|
#
|
389
405
|
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
|
390
406
|
module Token
|
407
|
+
TOKEN_KEY = "token="
|
408
|
+
TOKEN_REGEX = /^(Token|Bearer)\s+/
|
409
|
+
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
|
391
410
|
extend self
|
392
411
|
|
393
412
|
module ControllerMethods
|
394
|
-
def authenticate_or_request_with_http_token(realm = "Application", &login_procedure)
|
395
|
-
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)
|
396
415
|
end
|
397
416
|
|
398
417
|
def authenticate_with_http_token(&login_procedure)
|
399
418
|
Token.authenticate(self, &login_procedure)
|
400
419
|
end
|
401
420
|
|
402
|
-
def request_http_token_authentication(realm = "Application")
|
403
|
-
Token.authentication_request(self, realm)
|
421
|
+
def request_http_token_authentication(realm = "Application", message = nil)
|
422
|
+
Token.authentication_request(self, realm, message)
|
404
423
|
end
|
405
424
|
end
|
406
425
|
|
407
|
-
# If token Authorization header is present, call the login
|
408
|
-
# the present token and options.
|
426
|
+
# If token Authorization header is present, call the login
|
427
|
+
# procedure with the present token and options.
|
428
|
+
#
|
429
|
+
# [controller]
|
430
|
+
# ActionController::Base instance for the current request.
|
431
|
+
#
|
432
|
+
# [login_procedure]
|
433
|
+
# Proc to call if a token is present. The Proc should take two arguments:
|
409
434
|
#
|
410
|
-
#
|
411
|
-
# login_procedure - Proc to call if a token is present. The Proc should
|
412
|
-
# take 2 arguments:
|
413
|
-
# authenticate(controller) { |token, options| ... }
|
435
|
+
# authenticate(controller) { |token, options| ... }
|
414
436
|
#
|
415
|
-
# Returns the return value of
|
416
|
-
# Returns nil if no token is found.
|
437
|
+
# Returns the return value of <tt>login_procedure</tt> if a
|
438
|
+
# token is found. Returns <tt>nil</tt> if no token is found.
|
439
|
+
|
417
440
|
def authenticate(controller, &login_procedure)
|
418
441
|
token, options = token_and_options(controller.request)
|
419
442
|
unless token.blank?
|
@@ -421,28 +444,52 @@ module ActionController
|
|
421
444
|
end
|
422
445
|
end
|
423
446
|
|
424
|
-
# Parses the token and options out of the token
|
425
|
-
# 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:
|
426
450
|
# Authorization: Token token="abc", nonce="def"
|
427
|
-
# Then the returned token is "abc"
|
451
|
+
# Then the returned token is <tt>"abc"</tt>, and the options are
|
452
|
+
# <tt>{nonce: "def"}</tt>
|
428
453
|
#
|
429
454
|
# request - ActionDispatch::Request instance with the current headers.
|
430
455
|
#
|
431
|
-
# Returns an Array of [String, Hash] if a token is present.
|
432
|
-
# 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.
|
433
458
|
def token_and_options(request)
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
value.chomp!('"') # chomp trailing " in value
|
439
|
-
value.gsub!(/\\\"/, '"') # unescape remaining quotes
|
440
|
-
[key, value]
|
441
|
-
end]
|
442
|
-
[values.delete("token"), values.with_indifferent_access]
|
459
|
+
authorization_request = request.authorization.to_s
|
460
|
+
if authorization_request[TOKEN_REGEX]
|
461
|
+
params = token_params_from authorization_request
|
462
|
+
[params.shift[1], Hash[params].with_indifferent_access]
|
443
463
|
end
|
444
464
|
end
|
445
465
|
|
466
|
+
def token_params_from(auth)
|
467
|
+
rewrite_param_values params_array_from raw_params auth
|
468
|
+
end
|
469
|
+
|
470
|
+
# Takes raw_params and turns it into an array of parameters
|
471
|
+
def params_array_from(raw_params)
|
472
|
+
raw_params.map { |param| param.split %r/=(.+)?/ }
|
473
|
+
end
|
474
|
+
|
475
|
+
# This removes the <tt>"</tt> characters wrapping the value.
|
476
|
+
def rewrite_param_values(array_params)
|
477
|
+
array_params.each { |param| (param[1] || "".dup).gsub! %r/^"|"$/, "" }
|
478
|
+
end
|
479
|
+
|
480
|
+
# This method takes an authorization body and splits up the key-value
|
481
|
+
# pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
|
482
|
+
# delimiters defined in +AUTHN_PAIR_DELIMITERS+.
|
483
|
+
def raw_params(auth)
|
484
|
+
_raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
|
485
|
+
|
486
|
+
if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
|
487
|
+
_raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
|
488
|
+
end
|
489
|
+
|
490
|
+
_raw_params
|
491
|
+
end
|
492
|
+
|
446
493
|
# Encodes the given token and options into an Authorization header value.
|
447
494
|
#
|
448
495
|
# token - String token.
|
@@ -450,21 +497,22 @@ module ActionController
|
|
450
497
|
#
|
451
498
|
# Returns String.
|
452
499
|
def encode_credentials(token, options = {})
|
453
|
-
values = ["
|
500
|
+
values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
|
454
501
|
"#{key}=#{value.to_s.inspect}"
|
455
502
|
end
|
456
503
|
"Token #{values * ", "}"
|
457
504
|
end
|
458
505
|
|
459
|
-
# 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.
|
460
507
|
#
|
461
508
|
# controller - ActionController::Base instance for the outgoing response.
|
462
509
|
# realm - String realm to use in the header.
|
463
510
|
#
|
464
511
|
# Returns nothing.
|
465
|
-
def authentication_request(controller, realm)
|
466
|
-
|
467
|
-
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
|
468
516
|
end
|
469
517
|
end
|
470
518
|
end
|
@@ -1,19 +1,73 @@
|
|
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>ActionView::UnknownFormat</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 response_body
|
6
|
-
ret
|
7
|
-
end
|
30
|
+
# :stopdoc:
|
31
|
+
include BasicImplicitRender
|
8
32
|
|
9
33
|
def default_render(*args)
|
10
|
-
|
34
|
+
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
|
35
|
+
render(*args)
|
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 " \
|
45
|
+
"for this request format and variant.\n\n" \
|
46
|
+
"request.formats: #{request.formats.map(&:to_s).inspect}\n" \
|
47
|
+
"request.variant: #{request.variant.inspect}\n\n" \
|
48
|
+
"NOTE! For XHR/Ajax or API requests, this action would normally " \
|
49
|
+
"respond with 204 No Content: an empty white screen. Since you're " \
|
50
|
+
"loading it in a web browser, we assume that you expected to " \
|
51
|
+
"actually render a template, not nothing, so we're showing an " \
|
52
|
+
"error to be extra-clear. If you expect 204 No Content, carry on. " \
|
53
|
+
"That's what you'll get from an XHR or API request. Give it a shot."
|
54
|
+
|
55
|
+
raise ActionController::UnknownFormat, message
|
56
|
+
else
|
57
|
+
logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
|
58
|
+
super
|
59
|
+
end
|
11
60
|
end
|
12
61
|
|
13
62
|
def method_for_action(action_name)
|
14
63
|
super || if template_exists?(action_name.to_s, _prefixes)
|
15
|
-
|
16
|
-
|
64
|
+
"default_render"
|
65
|
+
end
|
17
66
|
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def interactive_browser_request?
|
70
|
+
request.get? && request.format == Mime[:html] && !request.xhr?
|
71
|
+
end
|
18
72
|
end
|
19
73
|
end
|