actionpack 7.1.3 → 7.2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -501
- 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 +15 -7
- 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 +198 -126
- 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 +123 -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 +210 -205
- 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 +525 -480
- 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 +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 +10 -3
- 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 +44 -38
- data/lib/action_dispatch/http/filter_parameters.rb +18 -9
- 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 +30 -41
- data/lib/action_dispatch/http/mime_type.rb +31 -24
- 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 +20 -44
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +94 -75
- data/lib/action_dispatch/http/response.rb +73 -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 +8 -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 +31 -21
- 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/rescues/missing_exact_template.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
- data/lib/action_dispatch/railtie.rb +2 -4
- 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 +5 -3
- data/lib/action_dispatch/routing/mapper.rb +671 -636
- 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 +59 -45
- 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 +10 -3
- data/lib/action_dispatch/system_testing/driver.rb +3 -1
- 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 -28
- 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 +39 -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"
|