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.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +308 -523
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/asset_paths.rb +6 -2
  5. data/lib/abstract_controller/base.rb +104 -105
  6. data/lib/abstract_controller/caching/fragments.rb +50 -53
  7. data/lib/abstract_controller/caching.rb +8 -3
  8. data/lib/abstract_controller/callbacks.rb +70 -62
  9. data/lib/abstract_controller/collector.rb +7 -7
  10. data/lib/abstract_controller/deprecator.rb +2 -0
  11. data/lib/abstract_controller/error.rb +2 -0
  12. data/lib/abstract_controller/helpers.rb +71 -84
  13. data/lib/abstract_controller/logger.rb +4 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/rendering.rb +13 -13
  16. data/lib/abstract_controller/translation.rb +12 -13
  17. data/lib/abstract_controller/url_for.rb +8 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api/api_rendering.rb +2 -0
  20. data/lib/action_controller/api.rb +76 -72
  21. data/lib/action_controller/base.rb +199 -126
  22. data/lib/action_controller/caching.rb +16 -14
  23. data/lib/action_controller/deprecator.rb +2 -0
  24. data/lib/action_controller/form_builder.rb +21 -18
  25. data/lib/action_controller/log_subscriber.rb +23 -2
  26. data/lib/action_controller/metal/allow_browser.rb +133 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  28. data/lib/action_controller/metal/conditional_get.rb +217 -175
  29. data/lib/action_controller/metal/content_security_policy.rb +25 -24
  30. data/lib/action_controller/metal/cookies.rb +4 -2
  31. data/lib/action_controller/metal/data_streaming.rb +72 -63
  32. data/lib/action_controller/metal/default_headers.rb +5 -3
  33. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  34. data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
  35. data/lib/action_controller/metal/exceptions.rb +16 -9
  36. data/lib/action_controller/metal/flash.rb +13 -14
  37. data/lib/action_controller/metal/head.rb +15 -11
  38. data/lib/action_controller/metal/helpers.rb +63 -55
  39. data/lib/action_controller/metal/http_authentication.rb +209 -201
  40. data/lib/action_controller/metal/implicit_render.rb +17 -15
  41. data/lib/action_controller/metal/instrumentation.rb +16 -14
  42. data/lib/action_controller/metal/live.rb +177 -128
  43. data/lib/action_controller/metal/logging.rb +6 -4
  44. data/lib/action_controller/metal/mime_responds.rb +151 -142
  45. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  46. data/lib/action_controller/metal/params_wrapper.rb +57 -59
  47. data/lib/action_controller/metal/permissions_policy.rb +22 -12
  48. data/lib/action_controller/metal/rate_limiting.rb +92 -0
  49. data/lib/action_controller/metal/redirecting.rb +213 -94
  50. data/lib/action_controller/metal/renderers.rb +78 -57
  51. data/lib/action_controller/metal/rendering.rb +111 -77
  52. data/lib/action_controller/metal/request_forgery_protection.rb +182 -143
  53. data/lib/action_controller/metal/rescue.rb +20 -9
  54. data/lib/action_controller/metal/streaming.rb +118 -195
  55. data/lib/action_controller/metal/strong_parameters.rb +720 -530
  56. data/lib/action_controller/metal/testing.rb +2 -0
  57. data/lib/action_controller/metal/url_for.rb +17 -15
  58. data/lib/action_controller/metal.rb +86 -60
  59. data/lib/action_controller/railtie.rb +36 -15
  60. data/lib/action_controller/railties/helpers.rb +2 -0
  61. data/lib/action_controller/renderer.rb +41 -36
  62. data/lib/action_controller/structured_event_subscriber.rb +116 -0
  63. data/lib/action_controller/template_assertions.rb +4 -2
  64. data/lib/action_controller/test_case.rb +160 -131
  65. data/lib/action_controller.rb +5 -1
  66. data/lib/action_dispatch/constants.rb +8 -0
  67. data/lib/action_dispatch/deprecator.rb +2 -0
  68. data/lib/action_dispatch/http/cache.rb +163 -35
  69. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +54 -39
  71. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  72. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  73. data/lib/action_dispatch/http/headers.rb +22 -22
  74. data/lib/action_dispatch/http/mime_negotiation.rb +89 -41
  75. data/lib/action_dispatch/http/mime_type.rb +25 -21
  76. data/lib/action_dispatch/http/mime_types.rb +3 -0
  77. data/lib/action_dispatch/http/param_builder.rb +187 -0
  78. data/lib/action_dispatch/http/param_error.rb +26 -0
  79. data/lib/action_dispatch/http/parameters.rb +14 -12
  80. data/lib/action_dispatch/http/permissions_policy.rb +25 -36
  81. data/lib/action_dispatch/http/query_parser.rb +55 -0
  82. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  83. data/lib/action_dispatch/http/request.rb +141 -92
  84. data/lib/action_dispatch/http/response.rb +137 -77
  85. data/lib/action_dispatch/http/upload.rb +18 -16
  86. data/lib/action_dispatch/http/url.rb +187 -89
  87. data/lib/action_dispatch/journey/formatter.rb +21 -9
  88. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  89. data/lib/action_dispatch/journey/gtg/simulator.rb +34 -11
  90. data/lib/action_dispatch/journey/gtg/transition_table.rb +47 -53
  91. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  92. data/lib/action_dispatch/journey/nodes/node.rb +8 -6
  93. data/lib/action_dispatch/journey/parser.rb +99 -195
  94. data/lib/action_dispatch/journey/path/pattern.rb +4 -1
  95. data/lib/action_dispatch/journey/route.rb +54 -38
  96. data/lib/action_dispatch/journey/router/utils.rb +22 -27
  97. data/lib/action_dispatch/journey/router.rb +63 -83
  98. data/lib/action_dispatch/journey/routes.rb +11 -2
  99. data/lib/action_dispatch/journey/scanner.rb +46 -42
  100. data/lib/action_dispatch/journey/visitors.rb +57 -23
  101. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  102. data/lib/action_dispatch/journey.rb +2 -0
  103. data/lib/action_dispatch/log_subscriber.rb +7 -1
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
  106. data/lib/action_dispatch/middleware/callbacks.rb +3 -1
  107. data/lib/action_dispatch/middleware/cookies.rb +125 -106
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +37 -8
  109. data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
  110. data/lib/action_dispatch/middleware/debug_view.rb +13 -5
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +18 -23
  112. data/lib/action_dispatch/middleware/executor.rb +19 -4
  113. data/lib/action_dispatch/middleware/flash.rb +63 -51
  114. data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +14 -12
  116. data/lib/action_dispatch/middleware/reloader.rb +5 -3
  117. data/lib/action_dispatch/middleware/remote_ip.rb +87 -77
  118. data/lib/action_dispatch/middleware/request_id.rb +16 -10
  119. data/lib/action_dispatch/middleware/server_timing.rb +4 -2
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +30 -8
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +16 -16
  125. data/lib/action_dispatch/middleware/ssl.rb +53 -40
  126. data/lib/action_dispatch/middleware/stack.rb +11 -10
  127. data/lib/action_dispatch/middleware/static.rb +33 -31
  128. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
  130. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  141. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
  142. data/lib/action_dispatch/railtie.rb +23 -3
  143. data/lib/action_dispatch/request/session.rb +24 -21
  144. data/lib/action_dispatch/request/utils.rb +11 -3
  145. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  146. data/lib/action_dispatch/routing/inspector.rb +85 -60
  147. data/lib/action_dispatch/routing/mapper.rb +1031 -851
  148. data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
  149. data/lib/action_dispatch/routing/redirection.rb +47 -39
  150. data/lib/action_dispatch/routing/route_set.rb +79 -56
  151. data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
  152. data/lib/action_dispatch/routing/url_for.rb +130 -125
  153. data/lib/action_dispatch/routing.rb +150 -148
  154. data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
  155. data/lib/action_dispatch/system_test_case.rb +91 -81
  156. data/lib/action_dispatch/system_testing/browser.rb +16 -23
  157. data/lib/action_dispatch/system_testing/driver.rb +2 -0
  158. data/lib/action_dispatch/system_testing/server.rb +2 -0
  159. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +34 -23
  160. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  161. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  162. data/lib/action_dispatch/testing/assertions/response.rb +52 -25
  163. data/lib/action_dispatch/testing/assertions/routing.rb +168 -87
  164. data/lib/action_dispatch/testing/assertions.rb +2 -0
  165. data/lib/action_dispatch/testing/integration.rb +233 -223
  166. data/lib/action_dispatch/testing/request_encoder.rb +11 -9
  167. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  168. data/lib/action_dispatch/testing/test_process.rb +11 -8
  169. data/lib/action_dispatch/testing/test_request.rb +3 -1
  170. data/lib/action_dispatch/testing/test_response.rb +27 -26
  171. data/lib/action_dispatch.rb +36 -32
  172. data/lib/action_pack/gem_version.rb +6 -4
  173. data/lib/action_pack/version.rb +3 -1
  174. data/lib/action_pack.rb +17 -16
  175. metadata +36 -32
  176. data/lib/action_dispatch/journey/parser.y +0 -50
  177. 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
- # = Action Controller Params Wrapper
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 +ParamsWrapper+ for +:json+ format, instead of having to
28
- # send JSON parameters like this:
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 +UsersController+,
38
- # your new +params+ hash will look like this:
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
- # and also the list of attributes it should wrap by using either +:include+ or
44
- # +:exclude+ options like this:
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 +:include+ or +:exclude+ option set,
51
- # it will only wrap the parameters returned by the class method
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 +ActiveModel+ object (such as
55
- # <tt>User.new(params[:user])</tt>), you might consider passing the model class to
56
- # the method instead. The +ParamsWrapper+ will actually try to determine the
57
- # list of attribute names from the model and only wrap those attributes:
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 +:include+ and +:exclude+ to set the list of attributes
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, +ParamsWrapper+ will actually try to determine if there's
68
- # a model related to it or not. This controller, for example:
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 +Admin::User+ or +User+ model exists, and use it to
74
- # determine the wrapper key respectively. If both models don't exist,
75
- # it will then fall back to use +user+ as the key.
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
- # this could be done by trying to find the defined model that has the
162
- # same singular name as the controller. For example, +UsersController+
163
- # will try to find if the +User+ model exists.
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
- # try to find Foo::Bar::User, Foo::User and finally User.
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 +ParamsWrapper+
196
- # would use to determine the attribute names from.
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
- # ==== Examples
199
- # wrap_parameters format: :xml
200
- # # enables the parameter wrapper for XML format
200
+ # wrap_parameters :person
201
+ # # wraps parameters into params[:person] hash
201
202
  #
202
- # wrap_parameters :person
203
- # # wraps parameters into +params[:person]+ hash
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
- # wrap_parameters Person
206
- # # wraps parameters by determining the wrapper key from Person class
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
- # wrap_parameters include: [:username, :title]
210
- # # wraps only +:username+ and +:title+ attributes from parameters.
210
+ # wrap_parameters false
211
+ # # disables parameters wrapping for this controller altogether.
211
212
  #
212
- # wrap_parameters false
213
- # # disables parameters wrapping for this controller altogether.
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
- # wrapper key and attribute names. Called automatically when the
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
- # by the metal call stack.
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 +Feature-Policy+
9
- # header:
10
+ # Overrides parts of the globally configured `Feature-Policy` header:
10
11
  #
11
- # class PagesController < ApplicationController
12
- # permissions_policy do |policy|
13
- # policy.geolocation "https://example.com"
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 +before_action+. For example, pass
18
- # <tt>only: :index</tt> to override the header on the index action only:
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
- # class PagesController < ApplicationController
21
- # permissions_policy(only: :index) do |policy|
22
- # policy.camera :self
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