actionpack 7.1.5.1 → 7.2.0.beta1
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 +61 -642
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +102 -98
- data/lib/abstract_controller/caching/fragments.rb +50 -53
- data/lib/abstract_controller/caching.rb +2 -0
- data/lib/abstract_controller/callbacks.rb +66 -64
- data/lib/abstract_controller/collector.rb +6 -6
- data/lib/abstract_controller/deprecator.rb +2 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +70 -85
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +13 -12
- 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 +74 -72
- data/lib/action_controller/base.rb +155 -117
- data/lib/action_controller/caching.rb +15 -12
- data/lib/action_controller/deprecator.rb +2 -0
- data/lib/action_controller/form_builder.rb +20 -17
- data/lib/action_controller/log_subscriber.rb +3 -1
- data/lib/action_controller/metal/allow_browser.rb +119 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
- data/lib/action_controller/metal/conditional_get.rb +188 -174
- 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 +64 -55
- 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 +11 -9
- data/lib/action_controller/metal/flash.rb +12 -10
- data/lib/action_controller/metal/head.rb +12 -10
- data/lib/action_controller/metal/helpers.rb +63 -55
- data/lib/action_controller/metal/http_authentication.rb +214 -203
- data/lib/action_controller/metal/implicit_render.rb +17 -15
- data/lib/action_controller/metal/instrumentation.rb +15 -12
- data/lib/action_controller/metal/live.rb +113 -107
- 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 +13 -12
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +108 -82
- data/lib/action_controller/metal/renderers.rb +50 -49
- data/lib/action_controller/metal/rendering.rb +103 -75
- data/lib/action_controller/metal/request_forgery_protection.rb +162 -133
- data/lib/action_controller/metal/rescue.rb +11 -9
- data/lib/action_controller/metal/streaming.rb +138 -136
- data/lib/action_controller/metal/strong_parameters.rb +483 -478
- 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 +58 -57
- data/lib/action_controller/railtie.rb +3 -0
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +42 -36
- data/lib/action_controller/template_assertions.rb +4 -2
- data/lib/action_controller/test_case.rb +146 -126
- data/lib/action_controller.rb +5 -1
- data/lib/action_dispatch/constants.rb +2 -0
- data/lib/action_dispatch/deprecator.rb +2 -0
- data/lib/action_dispatch/http/cache.rb +27 -26
- data/lib/action_dispatch/http/content_disposition.rb +2 -0
- data/lib/action_dispatch/http/content_security_policy.rb +48 -59
- data/lib/action_dispatch/http/filter_parameters.rb +13 -14
- data/lib/action_dispatch/http/filter_redirect.rb +15 -1
- data/lib/action_dispatch/http/headers.rb +22 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +30 -41
- data/lib/action_dispatch/http/mime_type.rb +25 -21
- data/lib/action_dispatch/http/mime_types.rb +2 -0
- data/lib/action_dispatch/http/parameters.rb +11 -9
- data/lib/action_dispatch/http/permissions_policy.rb +26 -36
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +75 -95
- data/lib/action_dispatch/http/response.rb +61 -61
- data/lib/action_dispatch/http/upload.rb +18 -16
- data/lib/action_dispatch/http/url.rb +75 -73
- data/lib/action_dispatch/journey/formatter.rb +13 -6
- data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
- data/lib/action_dispatch/journey/nodes/node.rb +6 -5
- data/lib/action_dispatch/journey/parser.rb +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +2 -0
- data/lib/action_dispatch/journey/path/pattern.rb +4 -1
- data/lib/action_dispatch/journey/route.rb +9 -7
- data/lib/action_dispatch/journey/router/utils.rb +16 -15
- data/lib/action_dispatch/journey/router.rb +4 -2
- data/lib/action_dispatch/journey/routes.rb +4 -2
- data/lib/action_dispatch/journey/scanner.rb +4 -2
- data/lib/action_dispatch/journey/visitors.rb +2 -0
- data/lib/action_dispatch/journey.rb +2 -0
- data/lib/action_dispatch/log_subscriber.rb +2 -0
- 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 +119 -104
- data/lib/action_dispatch/middleware/debug_exceptions.rb +13 -5
- data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
- data/lib/action_dispatch/middleware/debug_view.rb +2 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +6 -11
- data/lib/action_dispatch/middleware/executor.rb +2 -0
- 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 +8 -6
- data/lib/action_dispatch/middleware/reloader.rb +5 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +77 -72
- data/lib/action_dispatch/middleware/request_id.rb +14 -9
- 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 +13 -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 +43 -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/_source.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
- data/lib/action_dispatch/railtie.rb +2 -3
- data/lib/action_dispatch/request/session.rb +23 -21
- data/lib/action_dispatch/request/utils.rb +2 -0
- data/lib/action_dispatch/routing/endpoint.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +6 -4
- data/lib/action_dispatch/routing/mapper.rb +623 -625
- data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
- data/lib/action_dispatch/routing/redirection.rb +37 -32
- data/lib/action_dispatch/routing/route_set.rb +60 -46
- data/lib/action_dispatch/routing/routes_proxy.rb +6 -4
- data/lib/action_dispatch/routing/url_for.rb +130 -125
- data/lib/action_dispatch/routing.rb +150 -148
- data/lib/action_dispatch/system_test_case.rb +91 -81
- data/lib/action_dispatch/system_testing/browser.rb +4 -2
- 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 +32 -21
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
- data/lib/action_dispatch/testing/assertion_response.rb +8 -6
- data/lib/action_dispatch/testing/assertions/response.rb +26 -23
- data/lib/action_dispatch/testing/assertions/routing.rb +153 -84
- data/lib/action_dispatch/testing/assertions.rb +2 -0
- data/lib/action_dispatch/testing/integration.rb +223 -222
- data/lib/action_dispatch/testing/request_encoder.rb +2 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +12 -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 +22 -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 +33 -16
@@ -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,27 +1,28 @@
|
|
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
|
#
|
26
27
|
def permissions_policy(**options, &block)
|
27
28
|
before_action(options) do
|
@@ -0,0 +1,62 @@
|
|
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
|
+
# Requests that exceed the rate limit are refused with a `429 Too Many Requests`
|
22
|
+
# response. You can specialize this by passing a callable in the `with:`
|
23
|
+
# parameter. It's evaluated within the context of the controller processing the
|
24
|
+
# request.
|
25
|
+
#
|
26
|
+
# Rate limiting relies on a backing `ActiveSupport::Cache` store and defaults to
|
27
|
+
# `config.action_controller.cache_store`, which itself defaults to the global
|
28
|
+
# `config.cache_store`. If you don't want to store rate limits in the same
|
29
|
+
# datastore as your general caches, you can pass a custom store in the `store`
|
30
|
+
# parameter.
|
31
|
+
#
|
32
|
+
# Examples:
|
33
|
+
#
|
34
|
+
# class SessionsController < ApplicationController
|
35
|
+
# rate_limit to: 10, within: 3.minutes, only: :create
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# class SignupsController < ApplicationController
|
39
|
+
# rate_limit to: 1000, within: 10.seconds,
|
40
|
+
# by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups on domain!" }, only: :new
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# class APIController < ApplicationController
|
44
|
+
# RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
|
45
|
+
# rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE
|
46
|
+
# end
|
47
|
+
def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, **options)
|
48
|
+
before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store) }, **options
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def rate_limiting(to:, within:, by:, with:, store:)
|
54
|
+
count = store.increment("rate-limit:#{controller_path}:#{instance_exec(&by)}", 1, expires_in: within)
|
55
|
+
if count && count > to
|
56
|
+
ActiveSupport::Notifications.instrument("rate_limit.action_controller", request: request) do
|
57
|
+
instance_exec(&with)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionController
|
4
6
|
module Redirecting
|
5
7
|
extend ActiveSupport::Concern
|
@@ -9,78 +11,95 @@ module ActionController
|
|
9
11
|
|
10
12
|
class UnsafeRedirectError < StandardError; end
|
11
13
|
|
12
|
-
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]
|
14
|
+
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/
|
13
15
|
|
14
16
|
included do
|
15
17
|
mattr_accessor :raise_on_open_redirects, default: false
|
16
18
|
end
|
17
19
|
|
18
|
-
# Redirects the browser to the target specified in
|
20
|
+
# Redirects the browser to the target specified in `options`. This parameter can
|
21
|
+
# be any one of:
|
22
|
+
#
|
23
|
+
# * `Hash` - The URL will be generated by calling url_for with the `options`.
|
24
|
+
# * `Record` - The URL will be generated by calling url_for with the
|
25
|
+
# `options`, which will reference a named URL for that record.
|
26
|
+
# * `String` starting with `protocol://` (like `http://`) or a protocol
|
27
|
+
# relative reference (like `//`) - Is passed straight through as the target
|
28
|
+
# for redirection.
|
29
|
+
# * `String` not containing a protocol - The current protocol and host is
|
30
|
+
# prepended to the string.
|
31
|
+
# * `Proc` - A block that will be executed in the controller's context. Should
|
32
|
+
# return any option accepted by `redirect_to`.
|
19
33
|
#
|
20
|
-
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
21
|
-
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
|
22
|
-
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
|
23
|
-
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
|
24
|
-
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
|
25
34
|
#
|
26
|
-
#
|
35
|
+
# ### Examples
|
27
36
|
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
37
|
+
# redirect_to action: "show", id: 5
|
38
|
+
# redirect_to @post
|
39
|
+
# redirect_to "http://www.rubyonrails.org"
|
40
|
+
# redirect_to "/images/screenshot.jpg"
|
41
|
+
# redirect_to posts_url
|
42
|
+
# redirect_to proc { edit_post_url(@post) }
|
34
43
|
#
|
35
|
-
# The redirection happens as a
|
44
|
+
# The redirection happens as a `302 Found` header unless otherwise specified
|
45
|
+
# using the `:status` option:
|
36
46
|
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
47
|
+
# redirect_to post_url(@post), status: :found
|
48
|
+
# redirect_to action: 'atom', status: :moved_permanently
|
49
|
+
# redirect_to post_url(@post), status: 301
|
50
|
+
# redirect_to action: 'atom', status: 302
|
41
51
|
#
|
42
|
-
# The status code can either be a standard
|
43
|
-
# integer, or a
|
44
|
-
#
|
52
|
+
# The status code can either be a standard [HTTP Status
|
53
|
+
# code](https://www.iana.org/assignments/http-status-codes) as an integer, or a
|
54
|
+
# symbol representing the downcased, underscored and symbolized description.
|
55
|
+
# Note that the status code must be a 3xx HTTP code, or redirection will not
|
56
|
+
# occur.
|
45
57
|
#
|
46
58
|
# If you are using XHR requests other than GET or POST and redirecting after the
|
47
59
|
# request then some browsers will follow the redirect using the original request
|
48
60
|
# method. This may lead to undesirable behavior such as a double DELETE. To work
|
49
|
-
# around this you can return a
|
61
|
+
# around this you can return a `303 See Other` status code which will be
|
50
62
|
# followed using a GET request.
|
51
63
|
#
|
52
|
-
#
|
53
|
-
#
|
64
|
+
# redirect_to posts_url, status: :see_other
|
65
|
+
# redirect_to action: 'index', status: 303
|
54
66
|
#
|
55
|
-
# It is also possible to assign a flash message as part of the redirection.
|
56
|
-
#
|
67
|
+
# It is also possible to assign a flash message as part of the redirection.
|
68
|
+
# There are two special accessors for the commonly used flash names `alert` and
|
69
|
+
# `notice` as well as a general purpose `flash` bucket.
|
57
70
|
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
71
|
+
# redirect_to post_url(@post), alert: "Watch it, mister!"
|
72
|
+
# redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
|
73
|
+
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
|
74
|
+
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
|
62
75
|
#
|
63
|
-
# Statements after
|
64
|
-
#
|
76
|
+
# Statements after `redirect_to` in our controller get executed, so
|
77
|
+
# `redirect_to` doesn't stop the execution of the function. To terminate the
|
78
|
+
# execution of the function immediately after the `redirect_to`, use return.
|
65
79
|
#
|
66
|
-
#
|
80
|
+
# redirect_to post_url(@post) and return
|
67
81
|
#
|
68
|
-
#
|
82
|
+
# ### Open Redirect protection
|
69
83
|
#
|
70
|
-
# By default,
|
71
|
-
# Note: this was a new default in
|
84
|
+
# By default, Rails protects against redirecting to external hosts for your
|
85
|
+
# app's safety, so called open redirects. Note: this was a new default in Rails
|
86
|
+
# 7.0, after upgrading opt-in by uncommenting the line with
|
87
|
+
# `raise_on_open_redirects` in
|
88
|
+
# `config/initializers/new_framework_defaults_7_0.rb`
|
72
89
|
#
|
73
90
|
# Here #redirect_to automatically validates the potentially-unsafe URL:
|
74
91
|
#
|
75
|
-
#
|
92
|
+
# redirect_to params[:redirect_url]
|
76
93
|
#
|
77
94
|
# Raises UnsafeRedirectError in the case of an unsafe redirect.
|
78
95
|
#
|
79
|
-
# To allow any external redirects pass
|
96
|
+
# To allow any external redirects pass `allow_other_host: true`, though using a
|
97
|
+
# user-provided param in that case is unsafe.
|
80
98
|
#
|
81
|
-
#
|
99
|
+
# redirect_to "https://rubyonrails.org", allow_other_host: true
|
82
100
|
#
|
83
|
-
# See #url_from for more information on what an internal and safe URL is, or how
|
101
|
+
# See #url_from for more information on what an internal and safe URL is, or how
|
102
|
+
# to fall back to an alternate redirect URL in the unsafe case.
|
84
103
|
def redirect_to(options = {}, response_options = {})
|
85
104
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
86
105
|
raise AbstractController::DoubleRenderError if response_body
|
@@ -96,50 +115,53 @@ module ActionController
|
|
96
115
|
self.response_body = ""
|
97
116
|
end
|
98
117
|
|
99
|
-
# Soft deprecated alias for #redirect_back_or_to where the
|
100
|
-
# of the first positional
|
118
|
+
# Soft deprecated alias for #redirect_back_or_to where the `fallback_location`
|
119
|
+
# location is supplied as a keyword argument instead of the first positional
|
120
|
+
# argument.
|
101
121
|
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
|
102
122
|
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
|
103
123
|
end
|
104
124
|
|
105
|
-
# Redirects the browser to the page that issued the request (the referrer)
|
106
|
-
#
|
107
|
-
#
|
125
|
+
# Redirects the browser to the page that issued the request (the referrer) if
|
126
|
+
# possible, otherwise redirects to the provided default fallback location.
|
127
|
+
#
|
128
|
+
# The referrer information is pulled from the HTTP `Referer` (sic) header on the
|
129
|
+
# request. This is an optional header and its presence on the request is subject
|
130
|
+
# to browser security settings and user preferences. If the request is missing
|
131
|
+
# this header, the `fallback_location` will be used.
|
108
132
|
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
133
|
+
# redirect_back_or_to({ action: "show", id: 5 })
|
134
|
+
# redirect_back_or_to @post
|
135
|
+
# redirect_back_or_to "http://www.rubyonrails.org"
|
136
|
+
# redirect_back_or_to "/images/screenshot.jpg"
|
137
|
+
# redirect_back_or_to posts_url
|
138
|
+
# redirect_back_or_to proc { edit_post_url(@post) }
|
139
|
+
# redirect_back_or_to '/', allow_other_host: false
|
113
140
|
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
# redirect_back_or_to "/images/screenshot.jpg"
|
118
|
-
# redirect_back_or_to posts_url
|
119
|
-
# redirect_back_or_to proc { edit_post_url(@post) }
|
120
|
-
# redirect_back_or_to '/', allow_other_host: false
|
141
|
+
# #### Options
|
142
|
+
# * `:allow_other_host` - Allow or disallow redirection to the host that is
|
143
|
+
# different to the current host, defaults to true.
|
121
144
|
#
|
122
|
-
# ==== Options
|
123
|
-
# * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
|
124
145
|
#
|
125
|
-
# All other options that can be passed to #redirect_to are accepted as
|
126
|
-
#
|
146
|
+
# All other options that can be passed to #redirect_to are accepted as options,
|
147
|
+
# and the behavior is identical.
|
127
148
|
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
|
128
149
|
if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
|
129
150
|
redirect_to request.referer, allow_other_host: allow_other_host, **options
|
130
151
|
else
|
131
|
-
# The method level `allow_other_host` doesn't apply in the fallback case, omit
|
152
|
+
# The method level `allow_other_host` doesn't apply in the fallback case, omit
|
153
|
+
# and let the `redirect_to` handling take over.
|
132
154
|
redirect_to fallback_location, **options
|
133
155
|
end
|
134
156
|
end
|
135
157
|
|
136
158
|
def _compute_redirect_to_location(request, options) # :nodoc:
|
137
159
|
case options
|
138
|
-
# The scheme name consist of a letter followed by any combination of
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
160
|
+
# The scheme name consist of a letter followed by any combination of letters,
|
161
|
+
# digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is
|
162
|
+
# terminated by a colon (":"). See
|
163
|
+
# https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme
|
164
|
+
# starts with a double slash "//".
|
143
165
|
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
|
144
166
|
options.to_str
|
145
167
|
when String
|
@@ -153,25 +175,30 @@ module ActionController
|
|
153
175
|
module_function :_compute_redirect_to_location
|
154
176
|
public :_compute_redirect_to_location
|
155
177
|
|
156
|
-
# Verifies the passed
|
157
|
-
#
|
178
|
+
# Verifies the passed `location` is an internal URL that's safe to redirect to
|
179
|
+
# and returns it, or nil if not. Useful to wrap a params provided redirect URL
|
180
|
+
# and fall back to an alternate URL to redirect to:
|
158
181
|
#
|
159
|
-
#
|
182
|
+
# redirect_to url_from(params[:redirect_url]) || root_url
|
160
183
|
#
|
161
|
-
# The
|
184
|
+
# The `location` is considered internal, and safe, if it's on the same host as
|
185
|
+
# `request.host`:
|
162
186
|
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
187
|
+
# # If request.host is example.com:
|
188
|
+
# url_from("https://example.com/profile") # => "https://example.com/profile"
|
189
|
+
# url_from("http://example.com/profile") # => "http://example.com/profile"
|
190
|
+
# url_from("http://evil.com/profile") # => nil
|
167
191
|
#
|
168
192
|
# Subdomains are considered part of the host:
|
169
193
|
#
|
170
|
-
#
|
171
|
-
#
|
194
|
+
# # If request.host is on https://example.com or https://app.example.com, you'd get:
|
195
|
+
# url_from("https://dev.example.com/profile") # => nil
|
172
196
|
#
|
173
|
-
# NOTE: there's a similarity with
|
174
|
-
#
|
197
|
+
# NOTE: there's a similarity with
|
198
|
+
# [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor#url_for), which generates
|
199
|
+
# an internal URL from various options from within the app, e.g.
|
200
|
+
# `url_for(@post)`. However, #url_from is meant to take an external parameter to
|
201
|
+
# verify as in `url_from(params[:redirect_url])`.
|
175
202
|
def url_from(location)
|
176
203
|
location = location.presence
|
177
204
|
location if location && _url_host_allowed?(location)
|
@@ -212,9 +239,8 @@ module ActionController
|
|
212
239
|
end
|
213
240
|
|
214
241
|
def _ensure_url_is_http_header_safe(url)
|
215
|
-
# Attempt to comply with the set of valid token characters
|
216
|
-
#
|
217
|
-
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
242
|
+
# Attempt to comply with the set of valid token characters defined for an HTTP
|
243
|
+
# header value in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
218
244
|
if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
|
219
245
|
msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
|
220
246
|
"Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
|