actionpack 6.1.7 → 7.0.4.1
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 +269 -406
- data/MIT-LICENSE +1 -0
- data/README.rdoc +2 -3
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +13 -26
- data/lib/abstract_controller/caching/fragments.rb +2 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +21 -7
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +4 -3
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/translation.rb +3 -2
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/action_controller/api.rb +6 -6
- data/lib/action_controller/base.rb +5 -4
- data/lib/action_controller/form_builder.rb +2 -2
- data/lib/action_controller/log_subscriber.rb +4 -3
- data/lib/action_controller/metal/conditional_get.rb +39 -2
- data/lib/action_controller/metal/content_security_policy.rb +36 -2
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +5 -13
- data/lib/action_controller/metal/exceptions.rb +19 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- data/lib/action_controller/metal/helpers.rb +2 -2
- data/lib/action_controller/metal/http_authentication.rb +66 -39
- data/lib/action_controller/metal/instrumentation.rb +57 -52
- data/lib/action_controller/metal/live.rb +43 -2
- data/lib/action_controller/metal/mime_responds.rb +3 -3
- data/lib/action_controller/metal/params_wrapper.rb +20 -11
- data/lib/action_controller/metal/permissions_policy.rb +19 -28
- data/lib/action_controller/metal/redirecting.rb +93 -18
- data/lib/action_controller/metal/renderers.rb +10 -11
- data/lib/action_controller/metal/rendering.rb +8 -8
- data/lib/action_controller/metal/request_forgery_protection.rb +78 -29
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +6 -8
- data/lib/action_controller/metal/strong_parameters.rb +100 -54
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +3 -3
- data/lib/action_controller/metal.rb +10 -13
- data/lib/action_controller/railtie.rb +49 -6
- data/lib/action_controller/renderer.rb +1 -1
- data/lib/action_controller/test_case.rb +28 -7
- data/lib/action_controller.rb +2 -5
- data/lib/action_dispatch/http/cache.rb +14 -7
- data/lib/action_dispatch/http/content_security_policy.rb +108 -35
- data/lib/action_dispatch/http/filter_parameters.rb +5 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +15 -5
- data/lib/action_dispatch/http/mime_type.rb +9 -11
- data/lib/action_dispatch/http/parameters.rb +5 -5
- data/lib/action_dispatch/http/permissions_policy.rb +17 -1
- data/lib/action_dispatch/http/request.rb +12 -21
- data/lib/action_dispatch/http/response.rb +3 -16
- data/lib/action_dispatch/http/url.rb +11 -19
- data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
- data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
- data/lib/action_dispatch/journey/nodes/node.rb +70 -5
- data/lib/action_dispatch/journey/path/pattern.rb +22 -13
- data/lib/action_dispatch/journey/route.rb +6 -13
- data/lib/action_dispatch/journey/router/utils.rb +2 -2
- data/lib/action_dispatch/journey/router.rb +1 -1
- data/lib/action_dispatch/journey/routes.rb +3 -3
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
- data/lib/action_dispatch/middleware/cookies.rb +42 -27
- data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
- data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
- data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
- data/lib/action_dispatch/middleware/executor.rb +3 -0
- data/lib/action_dispatch/middleware/flash.rb +17 -18
- data/lib/action_dispatch/middleware/host_authorization.rb +1 -12
- data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- data/lib/action_dispatch/middleware/server_timing.rb +76 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
- data/lib/action_dispatch/middleware/session/cookie_store.rb +9 -9
- data/lib/action_dispatch/middleware/show_exceptions.rb +7 -9
- data/lib/action_dispatch/middleware/stack.rb +27 -9
- data/lib/action_dispatch/middleware/static.rb +2 -6
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +3 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +2 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +43 -13
- data/lib/action_dispatch/routing/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +59 -83
- data/lib/action_dispatch/routing/redirection.rb +5 -2
- data/lib/action_dispatch/routing/route_set.rb +17 -7
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +4 -5
- data/lib/action_dispatch/routing.rb +5 -6
- data/lib/action_dispatch/system_test_case.rb +5 -5
- data/lib/action_dispatch/system_testing/browser.rb +2 -12
- data/lib/action_dispatch/system_testing/driver.rb +35 -11
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +11 -7
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
- data/lib/action_dispatch/testing/assertions/routing.rb +3 -2
- data/lib/action_dispatch/testing/assertions.rb +2 -5
- data/lib/action_dispatch/testing/integration.rb +6 -8
- data/lib/action_dispatch/testing/test_process.rb +3 -29
- data/lib/action_dispatch/testing/test_response.rb +20 -2
- data/lib/action_dispatch.rb +1 -0
- data/lib/action_pack/gem_version.rb +5 -5
- data/lib/action_pack/version.rb +1 -1
- metadata +16 -15
@@ -9,11 +9,14 @@ module ActionController
|
|
9
9
|
# Wraps the parameters hash into a nested hash. This will allow clients to
|
10
10
|
# submit requests without having to specify any root elements.
|
11
11
|
#
|
12
|
-
# This functionality is enabled
|
13
|
-
#
|
12
|
+
# This functionality is enabled by default for JSON, and can be customized by
|
13
|
+
# setting the format array:
|
14
14
|
#
|
15
|
-
#
|
16
|
-
#
|
15
|
+
# class ApplicationController < ActionController::Base
|
16
|
+
# wrap_parameters format: [:json, :xml]
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# You could also turn it on per controller:
|
17
20
|
#
|
18
21
|
# class UsersController < ApplicationController
|
19
22
|
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
|
@@ -68,6 +71,12 @@ module ActionController
|
|
68
71
|
# will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
|
69
72
|
# determine the wrapper key respectively. If both models don't exist,
|
70
73
|
# it will then fallback to use +user+ as the key.
|
74
|
+
#
|
75
|
+
# To disable this functionality for a controller:
|
76
|
+
#
|
77
|
+
# class UsersController < ApplicationController
|
78
|
+
# wrap_parameters false
|
79
|
+
# end
|
71
80
|
module ParamsWrapper
|
72
81
|
extend ActiveSupport::Concern
|
73
82
|
|
@@ -242,14 +251,14 @@ module ActionController
|
|
242
251
|
end
|
243
252
|
end
|
244
253
|
|
245
|
-
# Performs parameters wrapping upon the request. Called automatically
|
246
|
-
# by the metal call stack.
|
247
|
-
def process_action(*)
|
248
|
-
_perform_parameter_wrapping if _wrapper_enabled?
|
249
|
-
super
|
250
|
-
end
|
251
|
-
|
252
254
|
private
|
255
|
+
# Performs parameters wrapping upon the request. Called automatically
|
256
|
+
# by the metal call stack.
|
257
|
+
def process_action(*)
|
258
|
+
_perform_parameter_wrapping if _wrapper_enabled?
|
259
|
+
super
|
260
|
+
end
|
261
|
+
|
253
262
|
# Returns the wrapper key which will be used to store wrapped parameters.
|
254
263
|
def _wrapper_key
|
255
264
|
_wrapper_options.name
|
@@ -1,37 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module ActionController
|
4
|
-
# HTTP Permissions Policy is a web standard for defining a mechanism to
|
5
|
-
# allow and deny the use of browser permissions in its own context, and
|
6
|
-
# in content within any <iframe> elements in the document.
|
7
|
-
#
|
8
|
-
# Full details of HTTP Permissions Policy specification and guidelines can
|
9
|
-
# be found at MDN:
|
10
|
-
#
|
11
|
-
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy
|
12
|
-
#
|
13
|
-
# Examples of usage:
|
14
|
-
#
|
15
|
-
# # Global policy
|
16
|
-
# Rails.application.config.permissions_policy do |f|
|
17
|
-
# f.camera :none
|
18
|
-
# f.gyroscope :none
|
19
|
-
# f.microphone :none
|
20
|
-
# f.usb :none
|
21
|
-
# f.fullscreen :self
|
22
|
-
# f.payment :self, "https://secure.example.com"
|
23
|
-
# end
|
24
|
-
#
|
25
|
-
# # Controller level policy
|
26
|
-
# class PagesController < ApplicationController
|
27
|
-
# permissions_policy do |p|
|
28
|
-
# p.geolocation "https://example.com"
|
29
|
-
# end
|
30
|
-
# end
|
3
|
+
module ActionController # :nodoc:
|
31
4
|
module PermissionsPolicy
|
32
5
|
extend ActiveSupport::Concern
|
33
6
|
|
34
7
|
module ClassMethods
|
8
|
+
# Overrides parts of the globally configured Feature-Policy
|
9
|
+
# header:
|
10
|
+
#
|
11
|
+
# class PagesController < ApplicationController
|
12
|
+
# permissions_policy do |policy|
|
13
|
+
# policy.geolocation "https://example.com"
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Options can be passed similar to +before_action+. For example, pass
|
18
|
+
# <tt>only: :index</tt> to override the header on the index action only:
|
19
|
+
#
|
20
|
+
# class PagesController < ApplicationController
|
21
|
+
# permissions_policy(only: :index) do |policy|
|
22
|
+
# policy.camera :self
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
35
26
|
def permissions_policy(**options, &block)
|
36
27
|
before_action(options) do
|
37
28
|
if block_given?
|
@@ -7,6 +7,12 @@ module ActionController
|
|
7
7
|
include AbstractController::Logger
|
8
8
|
include ActionController::UrlFor
|
9
9
|
|
10
|
+
class UnsafeRedirectError < StandardError; end
|
11
|
+
|
12
|
+
included do
|
13
|
+
mattr_accessor :raise_on_open_redirects, default: false
|
14
|
+
end
|
15
|
+
|
10
16
|
# Redirects the browser to the target specified in +options+. This parameter can be any one of:
|
11
17
|
#
|
12
18
|
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
@@ -54,16 +60,42 @@ module ActionController
|
|
54
60
|
#
|
55
61
|
# Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
|
56
62
|
# To terminate the execution of the function immediately after the +redirect_to+, use return.
|
63
|
+
#
|
57
64
|
# redirect_to post_url(@post) and return
|
65
|
+
#
|
66
|
+
# === Open Redirect protection
|
67
|
+
#
|
68
|
+
# By default, Rails protects against redirecting to external hosts for your app's safety, so called open redirects.
|
69
|
+
# Note: this was a new default in Rails 7.0, after upgrading opt-in by uncommenting the line with +raise_on_open_redirects+ in <tt>config/initializers/new_framework_defaults_7_0.rb</tt>
|
70
|
+
#
|
71
|
+
# Here #redirect_to automatically validates the potentially-unsafe URL:
|
72
|
+
#
|
73
|
+
# redirect_to params[:redirect_url]
|
74
|
+
#
|
75
|
+
# Raises UnsafeRedirectError in the case of an unsafe redirect.
|
76
|
+
#
|
77
|
+
# To allow any external redirects pass <tt>allow_other_host: true</tt>, though using a user-provided param in that case is unsafe.
|
78
|
+
#
|
79
|
+
# redirect_to "https://rubyonrails.org", allow_other_host: true
|
80
|
+
#
|
81
|
+
# See #url_from for more information on what an internal and safe URL is, or how to fall back to an alternate redirect URL in the unsafe case.
|
58
82
|
def redirect_to(options = {}, response_options = {})
|
59
83
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
60
84
|
raise AbstractController::DoubleRenderError if response_body
|
61
85
|
|
86
|
+
allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
|
87
|
+
|
62
88
|
self.status = _extract_redirect_to_status(options, response_options)
|
63
|
-
self.location = _compute_redirect_to_location(request, options)
|
89
|
+
self.location = _enforce_open_redirect_protection(_compute_redirect_to_location(request, options), allow_other_host: allow_other_host)
|
64
90
|
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
|
65
91
|
end
|
66
92
|
|
93
|
+
# Soft deprecated alias for #redirect_back_or_to where the +fallback_location+ location is supplied as a keyword argument instead
|
94
|
+
# of the first positional argument.
|
95
|
+
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
|
96
|
+
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
|
97
|
+
end
|
98
|
+
|
67
99
|
# Redirects the browser to the page that issued the request (the referrer)
|
68
100
|
# if possible, otherwise redirects to the provided default fallback
|
69
101
|
# location.
|
@@ -73,35 +105,37 @@ module ActionController
|
|
73
105
|
# subject to browser security settings and user preferences. If the request
|
74
106
|
# is missing this header, the <tt>fallback_location</tt> will be used.
|
75
107
|
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
108
|
+
# redirect_back_or_to({ action: "show", id: 5 })
|
109
|
+
# redirect_back_or_to @post
|
110
|
+
# redirect_back_or_to "http://www.rubyonrails.org"
|
111
|
+
# redirect_back_or_to "/images/screenshot.jpg"
|
112
|
+
# redirect_back_or_to posts_url
|
113
|
+
# redirect_back_or_to proc { edit_post_url(@post) }
|
114
|
+
# redirect_back_or_to '/', allow_other_host: false
|
83
115
|
#
|
84
116
|
# ==== Options
|
85
|
-
# * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
|
86
117
|
# * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
|
87
118
|
#
|
88
119
|
# All other options that can be passed to #redirect_to are accepted as
|
89
|
-
# options and the behavior is identical.
|
90
|
-
def
|
91
|
-
referer
|
92
|
-
|
93
|
-
|
120
|
+
# options, and the behavior is identical.
|
121
|
+
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
|
122
|
+
if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
|
123
|
+
redirect_to request.referer, allow_other_host: allow_other_host, **options
|
124
|
+
else
|
125
|
+
# The method level `allow_other_host` doesn't apply in the fallback case, omit and let the `redirect_to` handling take over.
|
126
|
+
redirect_to fallback_location, **options
|
127
|
+
end
|
94
128
|
end
|
95
129
|
|
96
|
-
def _compute_redirect_to_location(request, options)
|
130
|
+
def _compute_redirect_to_location(request, options) # :nodoc:
|
97
131
|
case options
|
98
132
|
# The scheme name consist of a letter followed by any combination of
|
99
133
|
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
|
100
134
|
# characters; and is terminated by a colon (":").
|
101
135
|
# See https://tools.ietf.org/html/rfc3986#section-3.1
|
102
136
|
# The protocol relative scheme starts with a double slash "//".
|
103
|
-
when /\A([a-z][a-z\d
|
104
|
-
options
|
137
|
+
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
|
138
|
+
options.to_str
|
105
139
|
when String
|
106
140
|
request.protocol + request.host_with_port + options
|
107
141
|
when Proc
|
@@ -113,7 +147,35 @@ module ActionController
|
|
113
147
|
module_function :_compute_redirect_to_location
|
114
148
|
public :_compute_redirect_to_location
|
115
149
|
|
150
|
+
# Verifies the passed +location+ is an internal URL that's safe to redirect to and returns it, or nil if not.
|
151
|
+
# Useful to wrap a params provided redirect URL and fallback to an alternate URL to redirect to:
|
152
|
+
#
|
153
|
+
# redirect_to url_from(params[:redirect_url]) || root_url
|
154
|
+
#
|
155
|
+
# The +location+ is considered internal, and safe, if it's on the same host as <tt>request.host</tt>:
|
156
|
+
#
|
157
|
+
# # If request.host is example.com:
|
158
|
+
# url_from("https://example.com/profile") # => "https://example.com/profile"
|
159
|
+
# url_from("http://example.com/profile") # => "http://example.com/profile"
|
160
|
+
# url_from("http://evil.com/profile") # => nil
|
161
|
+
#
|
162
|
+
# Subdomains are considered part of the host:
|
163
|
+
#
|
164
|
+
# # If request.host is on https://example.com or https://app.example.com, you'd get:
|
165
|
+
# url_from("https://dev.example.com/profile") # => nil
|
166
|
+
#
|
167
|
+
# NOTE: there's a similarity with {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor#url_for], which generates an internal URL from various options from within the app, e.g. <tt>url_for(@post)</tt>.
|
168
|
+
# However, #url_from is meant to take an external parameter to verify as in <tt>url_from(params[:redirect_url])</tt>.
|
169
|
+
def url_from(location)
|
170
|
+
location = location.presence
|
171
|
+
location if location && _url_host_allowed?(location)
|
172
|
+
end
|
173
|
+
|
116
174
|
private
|
175
|
+
def _allow_other_host
|
176
|
+
!raise_on_open_redirects
|
177
|
+
end
|
178
|
+
|
117
179
|
def _extract_redirect_to_status(options, response_options)
|
118
180
|
if options.is_a?(Hash) && options.key?(:status)
|
119
181
|
Rack::Utils.status_code(options.delete(:status))
|
@@ -124,8 +186,21 @@ module ActionController
|
|
124
186
|
end
|
125
187
|
end
|
126
188
|
|
189
|
+
def _enforce_open_redirect_protection(location, allow_other_host:)
|
190
|
+
if allow_other_host || _url_host_allowed?(location)
|
191
|
+
location
|
192
|
+
else
|
193
|
+
raise UnsafeRedirectError, "Unsafe redirect to #{location.truncate(100).inspect}, pass allow_other_host: true to redirect anyway."
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
127
197
|
def _url_host_allowed?(url)
|
128
|
-
URI(url.to_s).host
|
198
|
+
host = URI(url.to_s).host
|
199
|
+
|
200
|
+
return true if host == request.host
|
201
|
+
return false unless host.nil?
|
202
|
+
return false unless url.to_s.start_with?("/")
|
203
|
+
return !url.to_s.start_with?("//")
|
129
204
|
rescue ArgumentError, URI::Error
|
130
205
|
false
|
131
206
|
end
|
@@ -31,8 +31,7 @@ module ActionController
|
|
31
31
|
class_attribute :_renderers, default: Set.new.freeze
|
32
32
|
end
|
33
33
|
|
34
|
-
# Used in
|
35
|
-
# and <tt>ActionController::API</tt> to include all
|
34
|
+
# Used in ActionController::Base and ActionController::API to include all
|
36
35
|
# renderers by default.
|
37
36
|
module All
|
38
37
|
extend ActiveSupport::Concern
|
@@ -45,7 +44,7 @@ module ActionController
|
|
45
44
|
|
46
45
|
# Adds a new renderer to call within controller actions.
|
47
46
|
# A renderer is invoked by passing its name as an option to
|
48
|
-
#
|
47
|
+
# AbstractController::Rendering#render. To create a renderer
|
49
48
|
# pass it a name and a block. The block takes two arguments, the first
|
50
49
|
# is the value paired with its key and the second is the remaining
|
51
50
|
# hash of options passed to +render+.
|
@@ -96,18 +95,18 @@ module ActionController
|
|
96
95
|
# Adds, by name, a renderer or renderers to the +_renderers+ available
|
97
96
|
# to call within controller actions.
|
98
97
|
#
|
99
|
-
# It is useful when rendering from an
|
98
|
+
# It is useful when rendering from an ActionController::Metal controller or
|
100
99
|
# otherwise to add an available renderer proc to a specific controller.
|
101
100
|
#
|
102
|
-
# Both
|
103
|
-
# include
|
101
|
+
# Both ActionController::Base and ActionController::API
|
102
|
+
# include ActionController::Renderers::All, making all renderers
|
104
103
|
# available in the controller. See <tt>Renderers::RENDERERS</tt> and <tt>Renderers.add</tt>.
|
105
104
|
#
|
106
|
-
# Since
|
107
|
-
# must include
|
108
|
-
# and
|
105
|
+
# Since ActionController::Metal controllers cannot render, the controller
|
106
|
+
# must include AbstractController::Rendering, ActionController::Rendering,
|
107
|
+
# and ActionController::Renderers, and have at least one renderer.
|
109
108
|
#
|
110
|
-
# Rather than including
|
109
|
+
# Rather than including ActionController::Renderers::All and including all renderers,
|
111
110
|
# you may specify which renderers to include by passing the renderer name or names to
|
112
111
|
# +use_renderers+. For example, a controller that includes only the <tt>:json</tt> renderer
|
113
112
|
# (+_render_with_renderer_json+) might look like:
|
@@ -133,7 +132,7 @@ module ActionController
|
|
133
132
|
alias use_renderer use_renderers
|
134
133
|
end
|
135
134
|
|
136
|
-
# Called by +render+ in
|
135
|
+
# Called by +render+ in AbstractController::Rendering
|
137
136
|
# which sets the return value as the +response_body+.
|
138
137
|
#
|
139
138
|
# If no renderer is found, +super+ returns control to
|
@@ -24,19 +24,13 @@ module ActionController
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
# Before processing, set the request formats in current controller formats.
|
28
|
-
def process_action(*) #:nodoc:
|
29
|
-
self.formats = request.formats.map(&:ref).compact
|
30
|
-
super
|
31
|
-
end
|
32
|
-
|
33
27
|
# Check for double render errors and set the content_type after rendering.
|
34
|
-
def render(*args)
|
28
|
+
def render(*args) # :nodoc:
|
35
29
|
raise ::AbstractController::DoubleRenderError if response_body
|
36
30
|
super
|
37
31
|
end
|
38
32
|
|
39
|
-
#
|
33
|
+
# Override render_to_string because body can now be set to a Rack body.
|
40
34
|
def render_to_string(*)
|
41
35
|
result = super
|
42
36
|
if result.respond_to?(:each)
|
@@ -53,6 +47,12 @@ module ActionController
|
|
53
47
|
end
|
54
48
|
|
55
49
|
private
|
50
|
+
# Before processing, set the request formats in current controller formats.
|
51
|
+
def process_action(*) # :nodoc:
|
52
|
+
self.formats = request.formats.filter_map(&:ref)
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
56
|
def _process_variant(options)
|
57
57
|
if defined?(request) && !request.nil? && request.variant.present?
|
58
58
|
options[:variant] = request.variant
|
@@ -4,11 +4,11 @@ require "rack/session/abstract/id"
|
|
4
4
|
require "action_controller/metal/exceptions"
|
5
5
|
require "active_support/security_utils"
|
6
6
|
|
7
|
-
module ActionController
|
8
|
-
class InvalidAuthenticityToken < ActionControllerError
|
7
|
+
module ActionController # :nodoc:
|
8
|
+
class InvalidAuthenticityToken < ActionControllerError # :nodoc:
|
9
9
|
end
|
10
10
|
|
11
|
-
class InvalidCrossOriginRequest < ActionControllerError
|
11
|
+
class InvalidCrossOriginRequest < ActionControllerError # :nodoc:
|
12
12
|
end
|
13
13
|
|
14
14
|
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
|
@@ -32,7 +32,7 @@ module ActionController #:nodoc:
|
|
32
32
|
# response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
|
33
33
|
# Ajax) requests are allowed to make requests for JavaScript responses.
|
34
34
|
#
|
35
|
-
# Subclasses of
|
35
|
+
# Subclasses of ActionController::Base are protected by default with the
|
36
36
|
# <tt>:exception</tt> strategy, which raises an
|
37
37
|
# <tt>ActionController::InvalidAuthenticityToken</tt> error on unverified requests.
|
38
38
|
#
|
@@ -92,7 +92,16 @@ module ActionController #:nodoc:
|
|
92
92
|
|
93
93
|
# Controls whether URL-safe CSRF tokens are generated.
|
94
94
|
config_accessor :urlsafe_csrf_tokens, instance_writer: false
|
95
|
-
self.urlsafe_csrf_tokens =
|
95
|
+
self.urlsafe_csrf_tokens = true
|
96
|
+
|
97
|
+
singleton_class.redefine_method(:urlsafe_csrf_tokens=) do |urlsafe_csrf_tokens|
|
98
|
+
if urlsafe_csrf_tokens
|
99
|
+
ActiveSupport::Deprecation.warn("URL-safe CSRF tokens are now the default. Use 6.1 defaults or above.")
|
100
|
+
else
|
101
|
+
ActiveSupport::Deprecation.warn("Non-URL-safe CSRF tokens are deprecated. Use 6.1 defaults or above.")
|
102
|
+
end
|
103
|
+
config.urlsafe_csrf_tokens = urlsafe_csrf_tokens
|
104
|
+
end
|
96
105
|
|
97
106
|
helper_method :form_authenticity_token
|
98
107
|
helper_method :protect_against_forgery?
|
@@ -115,8 +124,8 @@ module ActionController #:nodoc:
|
|
115
124
|
#
|
116
125
|
# Valid Options:
|
117
126
|
#
|
118
|
-
# * <tt>:only
|
119
|
-
# * <tt>:if
|
127
|
+
# * <tt>:only</tt> / <tt>:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
|
128
|
+
# * <tt>:if</tt> / <tt>:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
|
120
129
|
# * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
|
121
130
|
# protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
|
122
131
|
# when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
|
@@ -124,10 +133,26 @@ module ActionController #:nodoc:
|
|
124
133
|
# If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
|
125
134
|
# * <tt>:with</tt> - Set the method to handle unverified request.
|
126
135
|
#
|
127
|
-
#
|
136
|
+
# Built-in unverified request handling methods are:
|
128
137
|
# * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
|
129
138
|
# * <tt>:reset_session</tt> - Resets the session.
|
130
139
|
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
|
140
|
+
#
|
141
|
+
# You can also implement custom strategy classes for unverified request handling:
|
142
|
+
#
|
143
|
+
# class CustomStrategy
|
144
|
+
# def initialize(controller)
|
145
|
+
# @controller = controller
|
146
|
+
# end
|
147
|
+
#
|
148
|
+
# def handle_unverified_request
|
149
|
+
# # Custom behaviour for unverfied request
|
150
|
+
# end
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# class ApplicationController < ActionController:x:Base
|
154
|
+
# protect_from_forgery with: CustomStrategy
|
155
|
+
# end
|
131
156
|
def protect_from_forgery(options = {})
|
132
157
|
options = options.reverse_merge(prepend: false)
|
133
158
|
|
@@ -143,14 +168,23 @@ module ActionController #:nodoc:
|
|
143
168
|
#
|
144
169
|
# See +skip_before_action+ for allowed options.
|
145
170
|
def skip_forgery_protection(options = {})
|
146
|
-
skip_before_action :verify_authenticity_token, options
|
171
|
+
skip_before_action :verify_authenticity_token, options.reverse_merge(raise: false)
|
147
172
|
end
|
148
173
|
|
149
174
|
private
|
150
175
|
def protection_method_class(name)
|
151
|
-
|
152
|
-
|
153
|
-
|
176
|
+
case name
|
177
|
+
when :null_session
|
178
|
+
ProtectionMethods::NullSession
|
179
|
+
when :reset_session
|
180
|
+
ProtectionMethods::ResetSession
|
181
|
+
when :exception
|
182
|
+
ProtectionMethods::Exception
|
183
|
+
when Class
|
184
|
+
name
|
185
|
+
else
|
186
|
+
raise ArgumentError, "Invalid request forgery protection method, use :null_session, :exception, :reset_session, or a custom forgery protection class."
|
187
|
+
end
|
154
188
|
end
|
155
189
|
end
|
156
190
|
|
@@ -170,7 +204,7 @@ module ActionController #:nodoc:
|
|
170
204
|
end
|
171
205
|
|
172
206
|
private
|
173
|
-
class NullSessionHash < Rack::Session::Abstract::SessionHash
|
207
|
+
class NullSessionHash < Rack::Session::Abstract::SessionHash # :nodoc:
|
174
208
|
def initialize(req)
|
175
209
|
super(nil, req)
|
176
210
|
@data = {}
|
@@ -183,9 +217,13 @@ module ActionController #:nodoc:
|
|
183
217
|
def exists?
|
184
218
|
true
|
185
219
|
end
|
220
|
+
|
221
|
+
def enabled?
|
222
|
+
false
|
223
|
+
end
|
186
224
|
end
|
187
225
|
|
188
|
-
class NullCookieJar < ActionDispatch::Cookies::CookieJar
|
226
|
+
class NullCookieJar < ActionDispatch::Cookies::CookieJar # :nodoc:
|
189
227
|
def write(*)
|
190
228
|
# nothing
|
191
229
|
end
|
@@ -203,12 +241,14 @@ module ActionController #:nodoc:
|
|
203
241
|
end
|
204
242
|
|
205
243
|
class Exception
|
244
|
+
attr_accessor :warning_message
|
245
|
+
|
206
246
|
def initialize(controller)
|
207
247
|
@controller = controller
|
208
248
|
end
|
209
249
|
|
210
250
|
def handle_unverified_request
|
211
|
-
raise ActionController::InvalidAuthenticityToken
|
251
|
+
raise ActionController::InvalidAuthenticityToken, warning_message
|
212
252
|
end
|
213
253
|
end
|
214
254
|
end
|
@@ -228,22 +268,31 @@ module ActionController #:nodoc:
|
|
228
268
|
mark_for_same_origin_verification!
|
229
269
|
|
230
270
|
if !verified_request?
|
231
|
-
if logger && log_warning_on_csrf_failure
|
232
|
-
|
233
|
-
logger.warn "Can't verify CSRF token authenticity."
|
234
|
-
else
|
235
|
-
logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
|
236
|
-
end
|
237
|
-
end
|
271
|
+
logger.warn unverified_request_warning_message if logger && log_warning_on_csrf_failure
|
272
|
+
|
238
273
|
handle_unverified_request
|
239
274
|
end
|
240
275
|
end
|
241
276
|
|
242
277
|
def handle_unverified_request # :doc:
|
243
|
-
forgery_protection_strategy.new(self)
|
278
|
+
protection_strategy = forgery_protection_strategy.new(self)
|
279
|
+
|
280
|
+
if protection_strategy.respond_to?(:warning_message)
|
281
|
+
protection_strategy.warning_message = unverified_request_warning_message
|
282
|
+
end
|
283
|
+
|
284
|
+
protection_strategy.handle_unverified_request
|
285
|
+
end
|
286
|
+
|
287
|
+
def unverified_request_warning_message # :nodoc:
|
288
|
+
if valid_request_origin?
|
289
|
+
"Can't verify CSRF token authenticity."
|
290
|
+
else
|
291
|
+
"HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
|
292
|
+
end
|
244
293
|
end
|
245
294
|
|
246
|
-
|
295
|
+
# :nodoc:
|
247
296
|
CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
|
248
297
|
"<script> tag on another site requested protected JavaScript. " \
|
249
298
|
"If you know what you're doing, go ahead and disable forgery " \
|
@@ -303,15 +352,15 @@ module ActionController #:nodoc:
|
|
303
352
|
[form_authenticity_param, request.x_csrf_token]
|
304
353
|
end
|
305
354
|
|
306
|
-
#
|
307
|
-
def form_authenticity_token(form_options: {})
|
355
|
+
# Creates the authenticity token for the current request.
|
356
|
+
def form_authenticity_token(form_options: {}) # :doc:
|
308
357
|
masked_authenticity_token(session, form_options: form_options)
|
309
358
|
end
|
310
359
|
|
311
360
|
# Creates a masked version of the authenticity token that varies
|
312
361
|
# on each request. The masking is used to mitigate SSL attacks
|
313
362
|
# like BREACH.
|
314
|
-
def masked_authenticity_token(session, form_options: {})
|
363
|
+
def masked_authenticity_token(session, form_options: {})
|
315
364
|
action, method = form_options.values_at(:action, :method)
|
316
365
|
|
317
366
|
raw_token = if per_form_csrf_tokens && action && method
|
@@ -438,7 +487,7 @@ module ActionController #:nodoc:
|
|
438
487
|
|
439
488
|
# Checks if the controller allows forgery protection.
|
440
489
|
def protect_against_forgery? # :doc:
|
441
|
-
allow_forgery_protection
|
490
|
+
allow_forgery_protection && (!session.respond_to?(:enabled?) || session.enabled?)
|
442
491
|
end
|
443
492
|
|
444
493
|
NULL_ORIGIN_MESSAGE = <<~MSG
|
@@ -469,7 +518,7 @@ module ActionController #:nodoc:
|
|
469
518
|
|
470
519
|
def generate_csrf_token # :nodoc:
|
471
520
|
if urlsafe_csrf_tokens
|
472
|
-
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH
|
521
|
+
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH)
|
473
522
|
else
|
474
523
|
SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
|
475
524
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "rack/chunked"
|
4
4
|
|
5
|
-
module ActionController
|
5
|
+
module ActionController # :nodoc:
|
6
6
|
# Allows views to be streamed back to the client as they are rendered.
|
7
7
|
#
|
8
8
|
# By default, Rails renders views by first rendering the template
|
@@ -24,7 +24,7 @@ module ActionController #:nodoc:
|
|
24
24
|
# Ruby implementation).
|
25
25
|
#
|
26
26
|
# Streaming can be added to a given template easily, all you need to do is
|
27
|
-
# to pass the
|
27
|
+
# to pass the +:stream+ option.
|
28
28
|
#
|
29
29
|
# class PostsController
|
30
30
|
# def index
|
@@ -59,8 +59,8 @@ module ActionController #:nodoc:
|
|
59
59
|
# render stream: true
|
60
60
|
# end
|
61
61
|
#
|
62
|
-
# Notice that
|
63
|
-
# or
|
62
|
+
# Notice that +:stream+ only works with templates. Rendering +:json+
|
63
|
+
# or +:xml+ with +:stream+ won't work.
|
64
64
|
#
|
65
65
|
# == Communication between layout and template
|
66
66
|
#
|
@@ -72,7 +72,7 @@ module ActionController #:nodoc:
|
|
72
72
|
# variables set in the template to be used in the layout, they won't
|
73
73
|
# work once you move to streaming. The proper way to communicate
|
74
74
|
# between layout and template, regardless of whether you use streaming
|
75
|
-
# or not, is by using +content_for+, +provide
|
75
|
+
# or not, is by using +content_for+, +provide+, and +yield+.
|
76
76
|
#
|
77
77
|
# Take a simple example where the layout expects the template to tell
|
78
78
|
# which title to use:
|
@@ -132,7 +132,7 @@ module ActionController #:nodoc:
|
|
132
132
|
# That said, when streaming, you need to properly check your templates
|
133
133
|
# and choose when to use +provide+ and +content_for+.
|
134
134
|
#
|
135
|
-
# == Headers, cookies, session and flash
|
135
|
+
# == Headers, cookies, session, and flash
|
136
136
|
#
|
137
137
|
# When streaming, the HTTP headers are sent to the client right before
|
138
138
|
# it renders the first line. This means that, modifying headers, cookies,
|
@@ -193,8 +193,6 @@ module ActionController #:nodoc:
|
|
193
193
|
# To be described.
|
194
194
|
#
|
195
195
|
module Streaming
|
196
|
-
extend ActiveSupport::Concern
|
197
|
-
|
198
196
|
private
|
199
197
|
# Set proper cache control and transfer encoding when streaming
|
200
198
|
def _process_options(options)
|