actionpack 7.1.5.1 → 8.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +308 -523
- data/README.rdoc +1 -1
- data/lib/abstract_controller/asset_paths.rb +6 -2
- data/lib/abstract_controller/base.rb +104 -105
- data/lib/abstract_controller/caching/fragments.rb +50 -53
- data/lib/abstract_controller/caching.rb +8 -3
- data/lib/abstract_controller/callbacks.rb +70 -62
- data/lib/abstract_controller/collector.rb +7 -7
- data/lib/abstract_controller/deprecator.rb +2 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +71 -84
- data/lib/abstract_controller/logger.rb +4 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +13 -13
- data/lib/abstract_controller/translation.rb +12 -13
- data/lib/abstract_controller/url_for.rb +8 -6
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api/api_rendering.rb +2 -0
- data/lib/action_controller/api.rb +76 -72
- data/lib/action_controller/base.rb +199 -126
- data/lib/action_controller/caching.rb +16 -14
- data/lib/action_controller/deprecator.rb +2 -0
- data/lib/action_controller/form_builder.rb +21 -18
- data/lib/action_controller/log_subscriber.rb +23 -2
- data/lib/action_controller/metal/allow_browser.rb +133 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
- data/lib/action_controller/metal/conditional_get.rb +217 -175
- data/lib/action_controller/metal/content_security_policy.rb +25 -24
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +72 -63
- data/lib/action_controller/metal/default_headers.rb +5 -3
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
- data/lib/action_controller/metal/exceptions.rb +16 -9
- data/lib/action_controller/metal/flash.rb +13 -14
- data/lib/action_controller/metal/head.rb +15 -11
- data/lib/action_controller/metal/helpers.rb +63 -55
- data/lib/action_controller/metal/http_authentication.rb +209 -201
- data/lib/action_controller/metal/implicit_render.rb +17 -15
- data/lib/action_controller/metal/instrumentation.rb +16 -14
- data/lib/action_controller/metal/live.rb +177 -128
- data/lib/action_controller/metal/logging.rb +6 -4
- data/lib/action_controller/metal/mime_responds.rb +151 -142
- data/lib/action_controller/metal/parameter_encoding.rb +34 -32
- data/lib/action_controller/metal/params_wrapper.rb +57 -59
- data/lib/action_controller/metal/permissions_policy.rb +22 -12
- data/lib/action_controller/metal/rate_limiting.rb +92 -0
- data/lib/action_controller/metal/redirecting.rb +213 -94
- data/lib/action_controller/metal/renderers.rb +78 -57
- data/lib/action_controller/metal/rendering.rb +111 -77
- data/lib/action_controller/metal/request_forgery_protection.rb +182 -143
- data/lib/action_controller/metal/rescue.rb +20 -9
- data/lib/action_controller/metal/streaming.rb +118 -195
- data/lib/action_controller/metal/strong_parameters.rb +720 -530
- data/lib/action_controller/metal/testing.rb +2 -0
- data/lib/action_controller/metal/url_for.rb +17 -15
- data/lib/action_controller/metal.rb +86 -60
- data/lib/action_controller/railtie.rb +36 -15
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +41 -36
- data/lib/action_controller/structured_event_subscriber.rb +116 -0
- data/lib/action_controller/template_assertions.rb +4 -2
- data/lib/action_controller/test_case.rb +160 -131
- data/lib/action_controller.rb +5 -1
- data/lib/action_dispatch/constants.rb +8 -0
- data/lib/action_dispatch/deprecator.rb +2 -0
- data/lib/action_dispatch/http/cache.rb +163 -35
- data/lib/action_dispatch/http/content_disposition.rb +2 -0
- data/lib/action_dispatch/http/content_security_policy.rb +54 -39
- data/lib/action_dispatch/http/filter_parameters.rb +14 -8
- data/lib/action_dispatch/http/filter_redirect.rb +22 -1
- data/lib/action_dispatch/http/headers.rb +22 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +89 -41
- data/lib/action_dispatch/http/mime_type.rb +25 -21
- data/lib/action_dispatch/http/mime_types.rb +3 -0
- data/lib/action_dispatch/http/param_builder.rb +187 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/parameters.rb +14 -12
- data/lib/action_dispatch/http/permissions_policy.rb +25 -36
- data/lib/action_dispatch/http/query_parser.rb +55 -0
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +141 -92
- data/lib/action_dispatch/http/response.rb +137 -77
- data/lib/action_dispatch/http/upload.rb +18 -16
- data/lib/action_dispatch/http/url.rb +187 -89
- data/lib/action_dispatch/journey/formatter.rb +21 -9
- data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +34 -11
- data/lib/action_dispatch/journey/gtg/transition_table.rb +47 -53
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
- data/lib/action_dispatch/journey/nodes/node.rb +8 -6
- data/lib/action_dispatch/journey/parser.rb +99 -195
- data/lib/action_dispatch/journey/path/pattern.rb +4 -1
- data/lib/action_dispatch/journey/route.rb +54 -38
- data/lib/action_dispatch/journey/router/utils.rb +22 -27
- data/lib/action_dispatch/journey/router.rb +63 -83
- data/lib/action_dispatch/journey/routes.rb +11 -2
- data/lib/action_dispatch/journey/scanner.rb +46 -42
- data/lib/action_dispatch/journey/visitors.rb +57 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/journey.rb +2 -0
- data/lib/action_dispatch/log_subscriber.rb +7 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
- data/lib/action_dispatch/middleware/callbacks.rb +3 -1
- data/lib/action_dispatch/middleware/cookies.rb +125 -106
- data/lib/action_dispatch/middleware/debug_exceptions.rb +37 -8
- data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
- data/lib/action_dispatch/middleware/debug_view.rb +13 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +18 -23
- data/lib/action_dispatch/middleware/executor.rb +19 -4
- data/lib/action_dispatch/middleware/flash.rb +63 -51
- data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
- data/lib/action_dispatch/middleware/public_exceptions.rb +14 -12
- data/lib/action_dispatch/middleware/reloader.rb +5 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +87 -77
- data/lib/action_dispatch/middleware/request_id.rb +16 -10
- data/lib/action_dispatch/middleware/server_timing.rb +4 -2
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +30 -8
- data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +16 -16
- data/lib/action_dispatch/middleware/ssl.rb +53 -40
- data/lib/action_dispatch/middleware/stack.rb +11 -10
- data/lib/action_dispatch/middleware/static.rb +33 -31
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
- data/lib/action_dispatch/railtie.rb +23 -3
- data/lib/action_dispatch/request/session.rb +24 -21
- data/lib/action_dispatch/request/utils.rb +11 -3
- data/lib/action_dispatch/routing/endpoint.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +85 -60
- data/lib/action_dispatch/routing/mapper.rb +1031 -851
- data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
- data/lib/action_dispatch/routing/redirection.rb +47 -39
- data/lib/action_dispatch/routing/route_set.rb +79 -56
- data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
- data/lib/action_dispatch/routing/url_for.rb +130 -125
- data/lib/action_dispatch/routing.rb +150 -148
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_test_case.rb +91 -81
- data/lib/action_dispatch/system_testing/browser.rb +16 -23
- data/lib/action_dispatch/system_testing/driver.rb +2 -0
- data/lib/action_dispatch/system_testing/server.rb +2 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +34 -23
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
- data/lib/action_dispatch/testing/assertion_response.rb +9 -7
- data/lib/action_dispatch/testing/assertions/response.rb +52 -25
- data/lib/action_dispatch/testing/assertions/routing.rb +168 -87
- data/lib/action_dispatch/testing/assertions.rb +2 -0
- data/lib/action_dispatch/testing/integration.rb +233 -223
- data/lib/action_dispatch/testing/request_encoder.rb +11 -9
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +11 -8
- data/lib/action_dispatch/testing/test_request.rb +3 -1
- data/lib/action_dispatch/testing/test_response.rb +27 -26
- data/lib/action_dispatch.rb +36 -32
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +17 -16
- metadata +36 -32
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -31
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
require "active_support/core_ext/hash/slice"
|
|
4
6
|
require "active_support/core_ext/hash/except"
|
|
5
7
|
require "active_support/core_ext/module/anonymous"
|
|
6
8
|
require "action_dispatch/http/mime_type"
|
|
7
9
|
|
|
8
10
|
module ActionController
|
|
9
|
-
#
|
|
11
|
+
# # Action Controller Params Wrapper
|
|
10
12
|
#
|
|
11
13
|
# Wraps the parameters hash into a nested hash. This will allow clients to
|
|
12
14
|
# submit requests without having to specify any root elements.
|
|
@@ -24,8 +26,8 @@ module ActionController
|
|
|
24
26
|
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
|
|
25
27
|
# end
|
|
26
28
|
#
|
|
27
|
-
# If you enable
|
|
28
|
-
#
|
|
29
|
+
# If you enable `ParamsWrapper` for `:json` format, instead of having to send
|
|
30
|
+
# JSON parameters like this:
|
|
29
31
|
#
|
|
30
32
|
# {"user": {"name": "Konata"}}
|
|
31
33
|
#
|
|
@@ -34,45 +36,44 @@ module ActionController
|
|
|
34
36
|
# {"name": "Konata"}
|
|
35
37
|
#
|
|
36
38
|
# And it will be wrapped into a nested hash with the key name matching the
|
|
37
|
-
# controller's name. For example, if you're posting to
|
|
38
|
-
#
|
|
39
|
+
# controller's name. For example, if you're posting to `UsersController`, your
|
|
40
|
+
# new `params` hash will look like this:
|
|
39
41
|
#
|
|
40
42
|
# {"name" => "Konata", "user" => {"name" => "Konata"}}
|
|
41
43
|
#
|
|
42
|
-
# You can also specify the key in which the parameters should be wrapped to,
|
|
43
|
-
#
|
|
44
|
-
#
|
|
44
|
+
# You can also specify the key in which the parameters should be wrapped to, and
|
|
45
|
+
# also the list of attributes it should wrap by using either `:include` or
|
|
46
|
+
# `:exclude` options like this:
|
|
45
47
|
#
|
|
46
48
|
# class UsersController < ApplicationController
|
|
47
49
|
# wrap_parameters :person, include: [:username, :password]
|
|
48
50
|
# end
|
|
49
51
|
#
|
|
50
|
-
# On Active Record models with no
|
|
51
|
-
#
|
|
52
|
-
# <tt>attribute_names</tt>.
|
|
52
|
+
# On Active Record models with no `:include` or `:exclude` option set, it will
|
|
53
|
+
# only wrap the parameters returned by the class method `attribute_names`.
|
|
53
54
|
#
|
|
54
|
-
# If you're going to pass the parameters to an
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
55
|
+
# If you're going to pass the parameters to an `ActiveModel` object (such as
|
|
56
|
+
# `User.new(params[:user])`), you might consider passing the model class to the
|
|
57
|
+
# method instead. The `ParamsWrapper` will actually try to determine the list of
|
|
58
|
+
# attribute names from the model and only wrap those attributes:
|
|
58
59
|
#
|
|
59
60
|
# class UsersController < ApplicationController
|
|
60
61
|
# wrap_parameters Person
|
|
61
62
|
# end
|
|
62
63
|
#
|
|
63
|
-
# You still could pass
|
|
64
|
+
# You still could pass `:include` and `:exclude` to set the list of attributes
|
|
64
65
|
# you want to wrap.
|
|
65
66
|
#
|
|
66
67
|
# By default, if you don't specify the key in which the parameters would be
|
|
67
|
-
# wrapped to,
|
|
68
|
-
#
|
|
68
|
+
# wrapped to, `ParamsWrapper` will actually try to determine if there's a model
|
|
69
|
+
# related to it or not. This controller, for example:
|
|
69
70
|
#
|
|
70
71
|
# class Admin::UsersController < ApplicationController
|
|
71
72
|
# end
|
|
72
73
|
#
|
|
73
|
-
# will try to check if
|
|
74
|
-
# determine the wrapper key respectively. If both models don't exist,
|
|
75
|
-
#
|
|
74
|
+
# will try to check if `Admin::User` or `User` model exists, and use it to
|
|
75
|
+
# determine the wrapper key respectively. If both models don't exist, it will
|
|
76
|
+
# then fall back to use `user` as the key.
|
|
76
77
|
#
|
|
77
78
|
# To disable this functionality for a controller:
|
|
78
79
|
#
|
|
@@ -84,11 +85,7 @@ module ActionController
|
|
|
84
85
|
|
|
85
86
|
EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
|
|
86
87
|
|
|
87
|
-
require "mutex_m"
|
|
88
|
-
|
|
89
88
|
class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
|
|
90
|
-
include Mutex_m
|
|
91
|
-
|
|
92
89
|
def self.from_hash(hash)
|
|
93
90
|
name = hash[:name]
|
|
94
91
|
format = Array(hash[:format])
|
|
@@ -99,6 +96,7 @@ module ActionController
|
|
|
99
96
|
|
|
100
97
|
def initialize(name, format, include, exclude, klass, model) # :nodoc:
|
|
101
98
|
super
|
|
99
|
+
@mutex = Mutex.new
|
|
102
100
|
@include_set = include
|
|
103
101
|
@name_set = name
|
|
104
102
|
end
|
|
@@ -111,7 +109,7 @@ module ActionController
|
|
|
111
109
|
return super if @include_set
|
|
112
110
|
|
|
113
111
|
m = model
|
|
114
|
-
synchronize do
|
|
112
|
+
@mutex.synchronize do
|
|
115
113
|
return super if @include_set
|
|
116
114
|
|
|
117
115
|
@include_set = true
|
|
@@ -144,7 +142,7 @@ module ActionController
|
|
|
144
142
|
return super if @name_set
|
|
145
143
|
|
|
146
144
|
m = model
|
|
147
|
-
synchronize do
|
|
145
|
+
@mutex.synchronize do
|
|
148
146
|
return super if @name_set
|
|
149
147
|
|
|
150
148
|
@name_set = true
|
|
@@ -157,13 +155,13 @@ module ActionController
|
|
|
157
155
|
end
|
|
158
156
|
|
|
159
157
|
private
|
|
160
|
-
# Determine the wrapper model from the controller's name. By convention,
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
158
|
+
# Determine the wrapper model from the controller's name. By convention, this
|
|
159
|
+
# could be done by trying to find the defined model that has the same singular
|
|
160
|
+
# name as the controller. For example, `UsersController` will try to find if the
|
|
161
|
+
# `User` model exists.
|
|
164
162
|
#
|
|
165
|
-
# This method also does namespace lookup. Foo::Bar::UsersController will
|
|
166
|
-
#
|
|
163
|
+
# This method also does namespace lookup. Foo::Bar::UsersController will try to
|
|
164
|
+
# find Foo::Bar::User, Foo::User and finally User.
|
|
167
165
|
def _default_wrap_model
|
|
168
166
|
return nil if klass.anonymous?
|
|
169
167
|
model_name = klass.name.delete_suffix("Controller").classify
|
|
@@ -192,33 +190,34 @@ module ActionController
|
|
|
192
190
|
self._wrapper_options = Options.from_hash(options)
|
|
193
191
|
end
|
|
194
192
|
|
|
195
|
-
# Sets the name of the wrapper key, or the model which
|
|
196
|
-
#
|
|
193
|
+
# Sets the name of the wrapper key, or the model which `ParamsWrapper` would use
|
|
194
|
+
# to determine the attribute names from.
|
|
195
|
+
#
|
|
196
|
+
# #### Examples
|
|
197
|
+
# wrap_parameters format: :xml
|
|
198
|
+
# # enables the parameter wrapper for XML format
|
|
197
199
|
#
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
# # enables the parameter wrapper for XML format
|
|
200
|
+
# wrap_parameters :person
|
|
201
|
+
# # wraps parameters into params[:person] hash
|
|
201
202
|
#
|
|
202
|
-
#
|
|
203
|
-
#
|
|
203
|
+
# wrap_parameters Person
|
|
204
|
+
# # wraps parameters by determining the wrapper key from Person class
|
|
205
|
+
# # (:person, in this case) and the list of attribute names
|
|
204
206
|
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
# # (+person+, in this case) and the list of attribute names
|
|
207
|
+
# wrap_parameters include: [:username, :title]
|
|
208
|
+
# # wraps only :username and :title attributes from parameters.
|
|
208
209
|
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
210
|
+
# wrap_parameters false
|
|
211
|
+
# # disables parameters wrapping for this controller altogether.
|
|
211
212
|
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
213
|
+
# #### Options
|
|
214
|
+
# * `:format` - The list of formats in which the parameters wrapper will be
|
|
215
|
+
# enabled.
|
|
216
|
+
# * `:include` - The list of attribute names which parameters wrapper will
|
|
217
|
+
# wrap into a nested hash.
|
|
218
|
+
# * `:exclude` - The list of attribute names which parameters wrapper will
|
|
219
|
+
# exclude from a nested hash.
|
|
214
220
|
#
|
|
215
|
-
# ==== Options
|
|
216
|
-
# * <tt>:format</tt> - The list of formats in which the parameters wrapper
|
|
217
|
-
# will be enabled.
|
|
218
|
-
# * <tt>:include</tt> - The list of attribute names which parameters wrapper
|
|
219
|
-
# will wrap into a nested hash.
|
|
220
|
-
# * <tt>:exclude</tt> - The list of attribute names which parameters wrapper
|
|
221
|
-
# will exclude from a nested hash.
|
|
222
221
|
def wrap_parameters(name_or_model_or_options, options = {})
|
|
223
222
|
model = nil
|
|
224
223
|
|
|
@@ -240,9 +239,8 @@ module ActionController
|
|
|
240
239
|
self._wrapper_options = opts
|
|
241
240
|
end
|
|
242
241
|
|
|
243
|
-
# Sets the default wrapper key or model which will be used to determine
|
|
244
|
-
#
|
|
245
|
-
# module is inherited.
|
|
242
|
+
# Sets the default wrapper key or model which will be used to determine wrapper
|
|
243
|
+
# key and attribute names. Called automatically when the module is inherited.
|
|
246
244
|
def inherited(klass)
|
|
247
245
|
if klass._wrapper_options.format.any?
|
|
248
246
|
params = klass._wrapper_options.dup
|
|
@@ -254,8 +252,8 @@ module ActionController
|
|
|
254
252
|
end
|
|
255
253
|
|
|
256
254
|
private
|
|
257
|
-
# Performs parameters wrapping upon the request. Called automatically
|
|
258
|
-
#
|
|
255
|
+
# Performs parameters wrapping upon the request. Called automatically by the
|
|
256
|
+
# metal call stack.
|
|
259
257
|
def process_action(*)
|
|
260
258
|
_perform_parameter_wrapping if _wrapper_enabled?
|
|
261
259
|
super
|
|
@@ -1,30 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
module ActionController # :nodoc:
|
|
4
6
|
module PermissionsPolicy
|
|
5
7
|
extend ActiveSupport::Concern
|
|
6
8
|
|
|
7
9
|
module ClassMethods
|
|
8
|
-
# Overrides parts of the globally configured
|
|
9
|
-
# header:
|
|
10
|
+
# Overrides parts of the globally configured `Feature-Policy` header:
|
|
10
11
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# class PagesController < ApplicationController
|
|
13
|
+
# permissions_policy do |policy|
|
|
14
|
+
# policy.geolocation "https://example.com"
|
|
15
|
+
# end
|
|
14
16
|
# end
|
|
15
|
-
# end
|
|
16
17
|
#
|
|
17
|
-
# Options can be passed similar to
|
|
18
|
-
#
|
|
18
|
+
# Options can be passed similar to `before_action`. For example, pass `only:
|
|
19
|
+
# :index` to override the header on the index action only:
|
|
19
20
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
21
|
+
# class PagesController < ApplicationController
|
|
22
|
+
# permissions_policy(only: :index) do |policy|
|
|
23
|
+
# policy.camera :self
|
|
24
|
+
# end
|
|
23
25
|
# end
|
|
24
|
-
# end
|
|
25
26
|
#
|
|
27
|
+
# Requires a global policy defined in an initializer, which can be
|
|
28
|
+
# empty:
|
|
29
|
+
#
|
|
30
|
+
# Rails.application.config.permissions_policy do |policy|
|
|
31
|
+
# # policy.gyroscope :none
|
|
32
|
+
# end
|
|
26
33
|
def permissions_policy(**options, &block)
|
|
27
34
|
before_action(options) do
|
|
35
|
+
unless request.respond_to?(:permissions_policy)
|
|
36
|
+
raise "Cannot override permissions_policy if no global permissions_policy configured."
|
|
37
|
+
end
|
|
28
38
|
if block_given?
|
|
29
39
|
policy = request.permissions_policy.clone
|
|
30
40
|
instance_exec(policy, &block)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
module ActionController # :nodoc:
|
|
6
|
+
module RateLimiting
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# Applies a rate limit to all actions or those specified by the normal
|
|
11
|
+
# `before_action` filters with `only:` and `except:`.
|
|
12
|
+
#
|
|
13
|
+
# The maximum number of requests allowed is specified `to:` and constrained to
|
|
14
|
+
# the window of time given by `within:`.
|
|
15
|
+
#
|
|
16
|
+
# Rate limits are by default unique to the ip address making the request, but
|
|
17
|
+
# you can provide your own identity function by passing a callable in the `by:`
|
|
18
|
+
# parameter. It's evaluated within the context of the controller processing the
|
|
19
|
+
# request.
|
|
20
|
+
#
|
|
21
|
+
# By default, rate limits are scoped to the controller's path. If you want to
|
|
22
|
+
# share rate limits across multiple controllers, you can provide your own scope,
|
|
23
|
+
# by passing value in the `scope:` parameter.
|
|
24
|
+
#
|
|
25
|
+
# Requests that exceed the rate limit will raise an `ActionController::TooManyRequests`
|
|
26
|
+
# error. By default, Action Dispatch will rescue from the error and refuse the request
|
|
27
|
+
# with a `429 Too Many Requests` response. You can specialize this by passing a callable in the `with:`
|
|
28
|
+
# parameter. It's evaluated within the context of the controller processing the
|
|
29
|
+
# request.
|
|
30
|
+
#
|
|
31
|
+
# Rate limiting relies on a backing `ActiveSupport::Cache` store and defaults to
|
|
32
|
+
# `config.action_controller.cache_store`, which itself defaults to the global
|
|
33
|
+
# `config.cache_store`. If you don't want to store rate limits in the same
|
|
34
|
+
# datastore as your general caches, you can pass a custom store in the `store`
|
|
35
|
+
# parameter.
|
|
36
|
+
#
|
|
37
|
+
# If you want to use multiple rate limits per controller, you need to give each of
|
|
38
|
+
# them an explicit name via the `name:` option.
|
|
39
|
+
#
|
|
40
|
+
# Examples:
|
|
41
|
+
#
|
|
42
|
+
# class SessionsController < ApplicationController
|
|
43
|
+
# rate_limit to: 10, within: 3.minutes, only: :create
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# class SignupsController < ApplicationController
|
|
47
|
+
# rate_limit to: 1000, within: 10.seconds,
|
|
48
|
+
# by: -> { request.domain }, with: :redirect_to_busy, only: :new
|
|
49
|
+
#
|
|
50
|
+
# private
|
|
51
|
+
# def redirect_to_busy
|
|
52
|
+
# redirect_to busy_controller_url, alert: "Too many signups on domain!"
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# class APIController < ApplicationController
|
|
57
|
+
# RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
|
|
58
|
+
# rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE
|
|
59
|
+
# rate_limit to: 100, within: 5.minutes, scope: :api_global
|
|
60
|
+
# end
|
|
61
|
+
#
|
|
62
|
+
# class SessionsController < ApplicationController
|
|
63
|
+
# rate_limit to: 3, within: 2.seconds, name: "short-term"
|
|
64
|
+
# rate_limit to: 10, within: 5.minutes, name: "long-term"
|
|
65
|
+
# end
|
|
66
|
+
def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { raise TooManyRequests }, store: cache_store, name: nil, scope: nil, **options)
|
|
67
|
+
before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store, name: name, scope: scope || controller_path) }, **options
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
def rate_limiting(to:, within:, by:, with:, store:, name:, scope:)
|
|
73
|
+
by = by.is_a?(Symbol) ? send(by) : instance_exec(&by)
|
|
74
|
+
|
|
75
|
+
cache_key = ["rate-limit", scope, name, by].compact.join(":")
|
|
76
|
+
count = store.increment(cache_key, 1, expires_in: within)
|
|
77
|
+
if count && count > to
|
|
78
|
+
ActiveSupport::Notifications.instrument("rate_limit.action_controller",
|
|
79
|
+
request: request,
|
|
80
|
+
count: count,
|
|
81
|
+
to: to,
|
|
82
|
+
within: within,
|
|
83
|
+
by: by,
|
|
84
|
+
name: name,
|
|
85
|
+
scope: scope,
|
|
86
|
+
cache_key: cache_key) do
|
|
87
|
+
with.is_a?(Symbol) ? send(with) : instance_exec(&with)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|