actionpack 7.2.2.1 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +123 -109
- data/lib/abstract_controller/rendering.rb +0 -1
- data/lib/action_controller/base.rb +1 -1
- data/lib/action_controller/form_builder.rb +3 -3
- data/lib/action_controller/metal/allow_browser.rb +11 -1
- data/lib/action_controller/metal/conditional_get.rb +5 -1
- data/lib/action_controller/metal/data_streaming.rb +4 -2
- data/lib/action_controller/metal/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +13 -4
- data/lib/action_controller/metal/rate_limiting.rb +13 -4
- data/lib/action_controller/metal/redirecting.rb +2 -1
- data/lib/action_controller/metal/renderers.rb +2 -3
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +277 -89
- data/lib/action_controller/railtie.rb +1 -7
- data/lib/action_controller/test_case.rb +4 -2
- data/lib/action_dispatch/http/cache.rb +27 -10
- data/lib/action_dispatch/http/content_security_policy.rb +1 -0
- data/lib/action_dispatch/http/param_builder.rb +186 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/permissions_policy.rb +2 -0
- data/lib/action_dispatch/http/query_parser.rb +53 -0
- data/lib/action_dispatch/http/request.rb +60 -16
- data/lib/action_dispatch/journey/parser.rb +99 -196
- data/lib/action_dispatch/journey/scanner.rb +44 -42
- data/lib/action_dispatch/middleware/cookies.rb +4 -2
- data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -3
- data/lib/action_dispatch/middleware/debug_view.rb +0 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +0 -6
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/ssl.rb +13 -3
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
- data/lib/action_dispatch/railtie.rb +8 -0
- data/lib/action_dispatch/request/session.rb +1 -0
- data/lib/action_dispatch/request/utils.rb +9 -3
- data/lib/action_dispatch/routing/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +90 -62
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
- data/lib/action_dispatch/routing/route_set.rb +20 -8
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- data/lib/action_dispatch/testing/assertions/response.rb +12 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +4 -4
- data/lib/action_dispatch/testing/integration.rb +11 -1
- data/lib/action_dispatch/testing/test_process.rb +1 -2
- data/lib/action_dispatch.rb +6 -4
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +15 -34
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -165,95 +165,16 @@ module ActionController # :nodoc:
|
|
165
165
|
#
|
166
166
|
# ## Web server support
|
167
167
|
#
|
168
|
-
#
|
169
|
-
# instructions for each of them.
|
170
|
-
#
|
171
|
-
# #### Unicorn
|
172
|
-
#
|
173
|
-
# Unicorn supports streaming but it needs to be configured. For this, you need
|
174
|
-
# to create a config file as follow:
|
175
|
-
#
|
176
|
-
# # unicorn.config.rb
|
177
|
-
# listen 3000, tcp_nopush: false
|
178
|
-
#
|
179
|
-
# And use it on initialization:
|
180
|
-
#
|
181
|
-
# unicorn_rails --config-file unicorn.config.rb
|
182
|
-
#
|
183
|
-
# You may also want to configure other parameters like `:tcp_nodelay`.
|
184
|
-
#
|
185
|
-
# For more information, please check the
|
186
|
-
# [documentation](https://bogomips.org/unicorn/Unicorn/Configurator.html#method-
|
187
|
-
# i-listen).
|
188
|
-
#
|
189
|
-
# If you are using Unicorn with NGINX, you may need to tweak NGINX. Streaming
|
190
|
-
# should work out of the box on Rainbows.
|
191
|
-
#
|
192
|
-
# #### Passenger
|
193
|
-
#
|
194
|
-
# Phusion Passenger with NGINX, offers two streaming mechanisms out of the box.
|
195
|
-
#
|
196
|
-
# 1. NGINX response buffering mechanism which is dependent on the value of
|
197
|
-
# `passenger_buffer_response` option (default is "off").
|
198
|
-
# 2. Passenger buffering system which is always 'on' irrespective of the value
|
199
|
-
# of `passenger_buffer_response`.
|
200
|
-
#
|
201
|
-
#
|
202
|
-
# When `passenger_buffer_response` is turned "on", then streaming would be done
|
203
|
-
# at the NGINX level which waits until the application is done sending the
|
204
|
-
# response back to the client.
|
205
|
-
#
|
206
|
-
# For more information, please check the [documentation]
|
207
|
-
# (https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response).
|
168
|
+
# Rack 3+ compatible servers all support streaming.
|
208
169
|
module Streaming
|
209
|
-
class Body # :nodoc:
|
210
|
-
TERM = "\r\n"
|
211
|
-
TAIL = "0#{TERM}"
|
212
|
-
|
213
|
-
# Store the response body to be chunked.
|
214
|
-
def initialize(body)
|
215
|
-
@body = body
|
216
|
-
end
|
217
|
-
|
218
|
-
# For each element yielded by the response body, yield the element in chunked
|
219
|
-
# encoding.
|
220
|
-
def each(&block)
|
221
|
-
term = TERM
|
222
|
-
@body.each do |chunk|
|
223
|
-
size = chunk.bytesize
|
224
|
-
next if size == 0
|
225
|
-
|
226
|
-
yield [size.to_s(16), term, chunk.b, term].join
|
227
|
-
end
|
228
|
-
yield TAIL
|
229
|
-
yield term
|
230
|
-
end
|
231
|
-
|
232
|
-
# Close the response body if the response body supports it.
|
233
|
-
def close
|
234
|
-
@body.close if @body.respond_to?(:close)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
170
|
private
|
239
|
-
# Set proper cache control and transfer encoding when streaming
|
240
|
-
def _process_options(options)
|
241
|
-
super
|
242
|
-
if options[:stream]
|
243
|
-
if request.version == "HTTP/1.0"
|
244
|
-
options.delete(:stream)
|
245
|
-
else
|
246
|
-
headers["Cache-Control"] ||= "no-cache"
|
247
|
-
headers["Transfer-Encoding"] = "chunked"
|
248
|
-
headers.delete("Content-Length")
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
171
|
# Call render_body if we are streaming instead of usual `render`.
|
254
172
|
def _render_template(options)
|
255
173
|
if options.delete(:stream)
|
256
|
-
|
174
|
+
# It shouldn't be necessary to set this.
|
175
|
+
headers["cache-control"] ||= "no-cache"
|
176
|
+
|
177
|
+
view_renderer.render_body(view_context, options)
|
257
178
|
else
|
258
179
|
super
|
259
180
|
end
|
@@ -10,7 +10,6 @@ require "active_support/deep_mergeable"
|
|
10
10
|
require "action_dispatch/http/upload"
|
11
11
|
require "rack/test"
|
12
12
|
require "stringio"
|
13
|
-
require "set"
|
14
13
|
require "yaml"
|
15
14
|
|
16
15
|
module ActionController
|
@@ -18,16 +17,18 @@ module ActionController
|
|
18
17
|
#
|
19
18
|
# params = ActionController::Parameters.new(a: {})
|
20
19
|
# params.fetch(:b)
|
21
|
-
# # => ActionController::ParameterMissing: param is missing or the value is empty: b
|
20
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: b
|
22
21
|
# params.require(:a)
|
23
|
-
# # => ActionController::ParameterMissing: param is missing or the value is empty: a
|
22
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a
|
23
|
+
# params.expect(a: [])
|
24
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a
|
24
25
|
class ParameterMissing < KeyError
|
25
26
|
attr_reader :param, :keys # :nodoc:
|
26
27
|
|
27
28
|
def initialize(param, keys = nil) # :nodoc:
|
28
29
|
@param = param
|
29
30
|
@keys = keys
|
30
|
-
super("param is missing or the value is empty: #{param}")
|
31
|
+
super("param is missing or the value is empty or invalid: #{param}")
|
31
32
|
end
|
32
33
|
|
33
34
|
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
|
@@ -39,6 +40,15 @@ module ActionController
|
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
43
|
+
# Raised from `expect!` when an expected parameter is missing or is of an
|
44
|
+
# incompatible type.
|
45
|
+
#
|
46
|
+
# params = ActionController::Parameters.new(a: {})
|
47
|
+
# params.expect!(:a)
|
48
|
+
# # => ActionController::ExpectedParameterMissing: param is missing or the value is empty or invalid: a
|
49
|
+
class ExpectedParameterMissing < ParameterMissing
|
50
|
+
end
|
51
|
+
|
42
52
|
# Raised when a supplied parameter is not expected and
|
43
53
|
# ActionController::Parameters.action_on_unpermitted_parameters is set to
|
44
54
|
# `:raise`.
|
@@ -78,9 +88,14 @@ module ActionController
|
|
78
88
|
#
|
79
89
|
# Allows you to choose which attributes should be permitted for mass updating
|
80
90
|
# and thus prevent accidentally exposing that which shouldn't be exposed.
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
91
|
+
#
|
92
|
+
# Provides methods for filtering and requiring params:
|
93
|
+
#
|
94
|
+
# * `expect` to safely permit and require parameters in one step.
|
95
|
+
# * `permit` to filter params for mass assignment.
|
96
|
+
# * `require` to require a parameter or raise an error.
|
97
|
+
#
|
98
|
+
# Examples:
|
84
99
|
#
|
85
100
|
# params = ActionController::Parameters.new({
|
86
101
|
# person: {
|
@@ -90,14 +105,14 @@ module ActionController
|
|
90
105
|
# }
|
91
106
|
# })
|
92
107
|
#
|
93
|
-
# permitted = params.
|
94
|
-
# permitted
|
95
|
-
# permitted.permitted? # => true
|
108
|
+
# permitted = params.expect(person: [:name, :age])
|
109
|
+
# permitted # => #<ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
|
96
110
|
#
|
97
111
|
# Person.first.update!(permitted)
|
98
112
|
# # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
|
99
113
|
#
|
100
|
-
#
|
114
|
+
# Parameters provides two options that control the top-level behavior of new
|
115
|
+
# instances:
|
101
116
|
#
|
102
117
|
# * `permit_all_parameters` - If it's `true`, all the parameters will be
|
103
118
|
# permitted by default. The default is `false`.
|
@@ -112,8 +127,6 @@ module ActionController
|
|
112
127
|
# * `:raise` to raise an ActionController::UnpermittedParameters
|
113
128
|
# exception.
|
114
129
|
#
|
115
|
-
#
|
116
|
-
#
|
117
130
|
# Examples:
|
118
131
|
#
|
119
132
|
# params = ActionController::Parameters.new
|
@@ -250,20 +263,6 @@ module ActionController
|
|
250
263
|
cattr_accessor :always_permitted_parameters, default: %w( controller action )
|
251
264
|
|
252
265
|
class << self
|
253
|
-
def allow_deprecated_parameters_hash_equality
|
254
|
-
ActionController.deprecator.warn <<-WARNING.squish
|
255
|
-
`Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality` is
|
256
|
-
deprecated and will be removed in Rails 8.0.
|
257
|
-
WARNING
|
258
|
-
end
|
259
|
-
|
260
|
-
def allow_deprecated_parameters_hash_equality=(value)
|
261
|
-
ActionController.deprecator.warn <<-WARNING.squish
|
262
|
-
`Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`
|
263
|
-
is deprecated and will be removed in Rails 8.0.
|
264
|
-
WARNING
|
265
|
-
end
|
266
|
-
|
267
266
|
def nested_attribute?(key, value) # :nodoc:
|
268
267
|
/\A-?\d+\z/.match?(key) && (value.is_a?(Hash) || value.is_a?(Parameters))
|
269
268
|
end
|
@@ -481,16 +480,16 @@ module ActionController
|
|
481
480
|
# Otherwise raises ActionController::ParameterMissing:
|
482
481
|
#
|
483
482
|
# ActionController::Parameters.new.require(:person)
|
484
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
483
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
|
485
484
|
#
|
486
485
|
# ActionController::Parameters.new(person: nil).require(:person)
|
487
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
486
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
|
488
487
|
#
|
489
488
|
# ActionController::Parameters.new(person: "\t").require(:person)
|
490
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
489
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
|
491
490
|
#
|
492
491
|
# ActionController::Parameters.new(person: {}).require(:person)
|
493
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
492
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
|
494
493
|
#
|
495
494
|
# When given an array of keys, the method tries to require each one of them in
|
496
495
|
# order. If it succeeds, an array with the respective return values is returned:
|
@@ -502,23 +501,21 @@ module ActionController
|
|
502
501
|
#
|
503
502
|
# params = ActionController::Parameters.new(user: {}, profile: {})
|
504
503
|
# user_params, profile_params = params.require([:user, :profile])
|
505
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: user
|
504
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
|
506
505
|
#
|
507
|
-
#
|
506
|
+
# This method is not recommended for fetching terminal values because it does
|
507
|
+
# not permit the values. For example, this can cause problems:
|
508
508
|
#
|
509
509
|
# # CAREFUL
|
510
510
|
# params = ActionController::Parameters.new(person: { name: "Finn" })
|
511
511
|
# name = params.require(:person).require(:name) # CAREFUL
|
512
512
|
#
|
513
|
-
#
|
513
|
+
# It is recommended to use `expect` instead:
|
514
514
|
#
|
515
515
|
# def person_params
|
516
|
-
# params.
|
517
|
-
# person_params.require(:name) # SAFER
|
518
|
-
# end
|
516
|
+
# params.expect(person: :name).require(:name)
|
519
517
|
# end
|
520
518
|
#
|
521
|
-
# for example.
|
522
519
|
def require(key)
|
523
520
|
return key.map { |k| require(k) } if key.is_a?(Array)
|
524
521
|
value = self[key]
|
@@ -536,8 +533,8 @@ module ActionController
|
|
536
533
|
# This is useful for limiting which attributes should be allowed for mass
|
537
534
|
# updating.
|
538
535
|
#
|
539
|
-
# params = ActionController::Parameters.new(
|
540
|
-
# permitted = params.
|
536
|
+
# params = ActionController::Parameters.new(name: "Francesco", age: 22, role: "admin")
|
537
|
+
# permitted = params.permit(:name, :age)
|
541
538
|
# permitted.permitted? # => true
|
542
539
|
# permitted.has_key?(:name) # => true
|
543
540
|
# permitted.has_key?(:age) # => true
|
@@ -567,7 +564,7 @@ module ActionController
|
|
567
564
|
# `permit` ensures values in the returned structure are permitted scalars and
|
568
565
|
# filters out anything else.
|
569
566
|
#
|
570
|
-
# You can also use `permit` on nested parameters
|
567
|
+
# You can also use `permit` on nested parameters:
|
571
568
|
#
|
572
569
|
# params = ActionController::Parameters.new({
|
573
570
|
# person: {
|
@@ -587,6 +584,29 @@ module ActionController
|
|
587
584
|
# permitted[:person][:pets][0][:name] # => "Purplish"
|
588
585
|
# permitted[:person][:pets][0][:category] # => nil
|
589
586
|
#
|
587
|
+
# This has the added benefit of rejecting user-modified inputs that send a
|
588
|
+
# string when a hash is expected.
|
589
|
+
#
|
590
|
+
# When followed by `require`, you can both filter and require parameters
|
591
|
+
# following the typical pattern of a Rails form. The `expect` method was
|
592
|
+
# made specifically for this use case and is the recommended way to require
|
593
|
+
# and permit parameters.
|
594
|
+
#
|
595
|
+
# permitted = params.expect(person: [:name, :age])
|
596
|
+
#
|
597
|
+
# When using `permit` and `require` separately, pay careful attention to the
|
598
|
+
# order of the method calls.
|
599
|
+
#
|
600
|
+
# params = ActionController::Parameters.new(person: { name: "Martin", age: 40, role: "admin" })
|
601
|
+
# permitted = params.permit(person: [:name, :age]).require(:person) # correct
|
602
|
+
#
|
603
|
+
# When require is used first, it is possible for users of your application to
|
604
|
+
# trigger a NoMethodError when the user, for example, sends a string for :person.
|
605
|
+
#
|
606
|
+
# params = ActionController::Parameters.new(person: "tampered")
|
607
|
+
# permitted = params.require(:person).permit(:name, :age) # not recommended
|
608
|
+
# # => NoMethodError: undefined method `permit' for an instance of String
|
609
|
+
#
|
590
610
|
# Note that if you use `permit` in a key that points to a hash, it won't allow
|
591
611
|
# all the hash. You also need to specify which attributes inside the hash should
|
592
612
|
# be permitted.
|
@@ -600,13 +620,13 @@ module ActionController
|
|
600
620
|
# }
|
601
621
|
# })
|
602
622
|
#
|
603
|
-
# params.
|
604
|
-
# # =>
|
623
|
+
# params.permit(person: :contact).require(:person)
|
624
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
|
605
625
|
#
|
606
|
-
# params.
|
626
|
+
# params.permit(person: { contact: :phone }).require(:person)
|
607
627
|
# # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
|
608
628
|
#
|
609
|
-
# params.
|
629
|
+
# params.permit(person: { contact: [ :email, :phone ] }).require(:person)
|
610
630
|
# # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
|
611
631
|
#
|
612
632
|
# If your parameters specify multiple parameters indexed by a number, you can
|
@@ -646,20 +666,127 @@ module ActionController
|
|
646
666
|
# params.permit(person: { '0': [:email], '1': [:phone]}).to_h
|
647
667
|
# # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"phone"=>"555-6789"}}}
|
648
668
|
def permit(*filters)
|
649
|
-
|
650
|
-
|
651
|
-
filters.flatten.each do |filter|
|
652
|
-
case filter
|
653
|
-
when Symbol, String
|
654
|
-
permitted_scalar_filter(params, filter)
|
655
|
-
when Hash
|
656
|
-
hash_filter(params, filter)
|
657
|
-
end
|
658
|
-
end
|
669
|
+
permit_filters(filters, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
|
670
|
+
end
|
659
671
|
|
660
|
-
|
672
|
+
# `expect` is the preferred way to require and permit parameters.
|
673
|
+
# It is safer than the previous recommendation to call `permit` and `require`
|
674
|
+
# in sequence, which could allow user triggered 500 errors.
|
675
|
+
#
|
676
|
+
# `expect` is more strict with types to avoid a number of potential pitfalls
|
677
|
+
# that may be encountered with the `.require.permit` pattern.
|
678
|
+
#
|
679
|
+
# For example:
|
680
|
+
#
|
681
|
+
# params = ActionController::Parameters.new(comment: { text: "hello" })
|
682
|
+
# params.expect(comment: [:text])
|
683
|
+
# # => #<ActionController::Parameters { text: "hello" } permitted: true>
|
684
|
+
#
|
685
|
+
# params = ActionController::Parameters.new(comment: [{ text: "hello" }, { text: "world" }])
|
686
|
+
# params.expect(comment: [:text])
|
687
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comment
|
688
|
+
#
|
689
|
+
# In order to permit an array of parameters, the array must be defined
|
690
|
+
# explicitly. Use double array brackets, an array inside an array, to
|
691
|
+
# declare that an array of parameters is expected.
|
692
|
+
#
|
693
|
+
# params = ActionController::Parameters.new(comments: [{ text: "hello" }, { text: "world" }])
|
694
|
+
# params.expect(comments: [[:text]])
|
695
|
+
# # => [#<ActionController::Parameters { "text" => "hello" } permitted: true>,
|
696
|
+
# # #<ActionController::Parameters { "text" => "world" } permitted: true>]
|
697
|
+
#
|
698
|
+
# params = ActionController::Parameters.new(comments: { text: "hello" })
|
699
|
+
# params.expect(comments: [[:text]])
|
700
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comments
|
701
|
+
#
|
702
|
+
# `expect` is intended to protect against array tampering.
|
703
|
+
#
|
704
|
+
# params = ActionController::Parameters.new(user: "hack")
|
705
|
+
# # The previous way of requiring and permitting parameters will error
|
706
|
+
# params.require(:user).permit(:name, pets: [:name]) # wrong
|
707
|
+
# # => NoMethodError: undefined method `permit' for an instance of String
|
708
|
+
#
|
709
|
+
# # similarly with nested parameters
|
710
|
+
# params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
|
711
|
+
# user_params = params.require(:user).permit(:name, pets: [:name]) # wrong
|
712
|
+
# # user_params[:pets] is expected to be an array but is a hash
|
713
|
+
#
|
714
|
+
# `expect` solves this by being more strict with types.
|
715
|
+
#
|
716
|
+
# params = ActionController::Parameters.new(user: "hack")
|
717
|
+
# params.expect(user: [ :name, pets: [[:name]] ])
|
718
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
|
719
|
+
#
|
720
|
+
# # with nested parameters
|
721
|
+
# params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
|
722
|
+
# user_params = params.expect(user: [:name, pets: [[:name]] ])
|
723
|
+
# user_params[:pets] # => nil
|
724
|
+
#
|
725
|
+
# As the examples show, `expect` requires the `:user` key, and any root keys
|
726
|
+
# similar to the `.require.permit` pattern. If multiple root keys are
|
727
|
+
# expected, they will all be required.
|
728
|
+
#
|
729
|
+
# params = ActionController::Parameters.new(name: "Martin", pies: [{ type: "dessert", flavor: "pumpkin"}])
|
730
|
+
# name, pies = params.expect(:name, pies: [[:type, :flavor]])
|
731
|
+
# name # => "Martin"
|
732
|
+
# pies # => [#<ActionController::Parameters {"type"=>"dessert", "flavor"=>"pumpkin"} permitted: true>]
|
733
|
+
#
|
734
|
+
# When called with a hash with multiple keys, `expect` will permit the
|
735
|
+
# parameters and require the keys in the order they are given in the hash,
|
736
|
+
# returning an array of the permitted parameters.
|
737
|
+
#
|
738
|
+
# params = ActionController::Parameters.new(subject: { name: "Martin" }, object: { pie: "pumpkin" })
|
739
|
+
# subject, object = params.expect(subject: [:name], object: [:pie])
|
740
|
+
# subject # => #<ActionController::Parameters {"name"=>"Martin"} permitted: true>
|
741
|
+
# object # => #<ActionController::Parameters {"pie"=>"pumpkin"} permitted: true>
|
742
|
+
#
|
743
|
+
# Besides being more strict about array vs hash params, `expect` uses permit
|
744
|
+
# internally, so it will behave similarly.
|
745
|
+
#
|
746
|
+
# params = ActionController::Parameters.new({
|
747
|
+
# person: {
|
748
|
+
# name: "Francesco",
|
749
|
+
# age: 22,
|
750
|
+
# pets: [{
|
751
|
+
# name: "Purplish",
|
752
|
+
# category: "dogs"
|
753
|
+
# }]
|
754
|
+
# }
|
755
|
+
# })
|
756
|
+
#
|
757
|
+
# permitted = params.expect(person: [ :name, { pets: [[:name]] } ])
|
758
|
+
# permitted.permitted? # => true
|
759
|
+
# permitted[:name] # => "Francesco"
|
760
|
+
# permitted[:age] # => nil
|
761
|
+
# permitted[:pets][0][:name] # => "Purplish"
|
762
|
+
# permitted[:pets][0][:category] # => nil
|
763
|
+
#
|
764
|
+
# An array of permitted scalars may be expected with the following:
|
765
|
+
#
|
766
|
+
# params = ActionController::Parameters.new(tags: ["rails", "parameters"])
|
767
|
+
# permitted = params.expect(tags: [])
|
768
|
+
# permitted.permitted? # => true
|
769
|
+
# permitted.is_a?(Array) # => true
|
770
|
+
# permitted.size # => 2
|
771
|
+
#
|
772
|
+
def expect(*filters)
|
773
|
+
params = permit_filters(filters)
|
774
|
+
keys = filters.flatten.flat_map { |f| f.is_a?(Hash) ? f.keys : f }
|
775
|
+
values = params.require(keys)
|
776
|
+
values.size == 1 ? values.first : values
|
777
|
+
end
|
661
778
|
|
662
|
-
|
779
|
+
# Same as `expect`, but raises an `ActionController::ExpectedParameterMissing`
|
780
|
+
# instead of `ActionController::ParameterMissing`. Unlike `expect` which
|
781
|
+
# will render a 400 response, `expect!` will raise an exception that is
|
782
|
+
# not handled. This is intended for debugging invalid params for an
|
783
|
+
# internal API where incorrectly formatted params would indicate a bug
|
784
|
+
# in a client library that should be fixed.
|
785
|
+
#
|
786
|
+
def expect!(*filters)
|
787
|
+
expect(*filters)
|
788
|
+
rescue ParameterMissing => e
|
789
|
+
raise ExpectedParameterMissing.new(e.param, e.keys)
|
663
790
|
end
|
664
791
|
|
665
792
|
# Returns a parameter for the given `key`. If not found, returns `nil`.
|
@@ -686,7 +813,7 @@ module ActionController
|
|
686
813
|
#
|
687
814
|
# params = ActionController::Parameters.new(person: { name: "Francesco" })
|
688
815
|
# params.fetch(:person) # => #<ActionController::Parameters {"name"=>"Francesco"} permitted: false>
|
689
|
-
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
|
816
|
+
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: none
|
690
817
|
# params.fetch(:none, {}) # => #<ActionController::Parameters {} permitted: false>
|
691
818
|
# params.fetch(:none, "Francesco") # => "Francesco"
|
692
819
|
# params.fetch(:none) { "Francesco" } # => "Francesco"
|
@@ -999,6 +1126,26 @@ module ActionController
|
|
999
1126
|
hash
|
1000
1127
|
end
|
1001
1128
|
|
1129
|
+
# Filters self and optionally checks for unpermitted keys
|
1130
|
+
def permit_filters(filters, on_unpermitted: nil, explicit_arrays: true)
|
1131
|
+
params = self.class.new
|
1132
|
+
|
1133
|
+
filters.flatten.each do |filter|
|
1134
|
+
case filter
|
1135
|
+
when Symbol, String
|
1136
|
+
# Declaration [:name, "age"]
|
1137
|
+
permitted_scalar_filter(params, filter)
|
1138
|
+
when Hash
|
1139
|
+
# Declaration [{ person: ... }]
|
1140
|
+
hash_filter(params, filter, on_unpermitted:, explicit_arrays:)
|
1141
|
+
end
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
unpermitted_parameters!(params, on_unpermitted:)
|
1145
|
+
|
1146
|
+
params.permit!
|
1147
|
+
end
|
1148
|
+
|
1002
1149
|
private
|
1003
1150
|
def new_instance_with_inherited_permitted_status(hash)
|
1004
1151
|
self.class.new(hash, @logging_context).tap do |new_instance|
|
@@ -1088,23 +1235,45 @@ module ActionController
|
|
1088
1235
|
end
|
1089
1236
|
end
|
1090
1237
|
|
1091
|
-
|
1238
|
+
# When an array is expected, you must specify an array explicitly
|
1239
|
+
# using the following format:
|
1240
|
+
#
|
1241
|
+
# params.expect(comments: [[:flavor]])
|
1242
|
+
#
|
1243
|
+
# Which will match only the following array formats:
|
1244
|
+
#
|
1245
|
+
# { pies: [{ flavor: "rhubarb" }, { flavor: "apple" }] }
|
1246
|
+
# { pies: { "0" => { flavor: "key lime" }, "1" => { flavor: "mince" } } }
|
1247
|
+
#
|
1248
|
+
# When using `permit`, arrays are specified the same way as hashes:
|
1249
|
+
#
|
1250
|
+
# params.expect(pies: [:flavor])
|
1251
|
+
#
|
1252
|
+
# In this case, `permit` would also allow matching with a hash (or vice versa):
|
1253
|
+
#
|
1254
|
+
# { pies: { flavor: "cherry" } }
|
1255
|
+
#
|
1256
|
+
def array_filter?(filter)
|
1257
|
+
filter.is_a?(Array) && filter.size == 1 && filter.first.is_a?(Array)
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
# Called when an explicit array filter is encountered.
|
1261
|
+
def each_array_element(object, filter, &block)
|
1092
1262
|
case object
|
1093
1263
|
when Array
|
1094
1264
|
object.grep(Parameters).filter_map(&block)
|
1095
1265
|
when Parameters
|
1096
1266
|
if object.nested_attributes? && !specify_numeric_keys?(filter)
|
1097
1267
|
object.each_nested_attribute(&block)
|
1098
|
-
else
|
1099
|
-
yield object
|
1100
1268
|
end
|
1101
1269
|
end
|
1102
1270
|
end
|
1103
1271
|
|
1104
|
-
def unpermitted_parameters!(params)
|
1272
|
+
def unpermitted_parameters!(params, on_unpermitted: self.class.action_on_unpermitted_parameters)
|
1273
|
+
return unless on_unpermitted
|
1105
1274
|
unpermitted_keys = unpermitted_keys(params)
|
1106
1275
|
if unpermitted_keys.any?
|
1107
|
-
case
|
1276
|
+
case on_unpermitted
|
1108
1277
|
when :log
|
1109
1278
|
name = "unpermitted_parameters.action_controller"
|
1110
1279
|
ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys, context: @logging_context)
|
@@ -1174,45 +1343,63 @@ module ActionController
|
|
1174
1343
|
end
|
1175
1344
|
end
|
1176
1345
|
|
1177
|
-
def array_of_permitted_scalars?(value)
|
1178
|
-
if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
|
1179
|
-
yield value
|
1180
|
-
end
|
1181
|
-
end
|
1182
|
-
|
1183
1346
|
def non_scalar?(value)
|
1184
1347
|
value.is_a?(Array) || value.is_a?(Parameters)
|
1185
1348
|
end
|
1186
1349
|
|
1187
1350
|
EMPTY_ARRAY = [] # :nodoc:
|
1188
1351
|
EMPTY_HASH = {} # :nodoc:
|
1189
|
-
def hash_filter(params, filter)
|
1352
|
+
def hash_filter(params, filter, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
|
1190
1353
|
filter = filter.with_indifferent_access
|
1191
1354
|
|
1192
1355
|
# Slicing filters out non-declared keys.
|
1193
1356
|
slice(*filter.keys).each do |key, value|
|
1194
1357
|
next unless value
|
1195
1358
|
next unless has_key? key
|
1359
|
+
result = permit_value(value, filter[key], on_unpermitted:, explicit_arrays:)
|
1360
|
+
params[key] = result unless result.nil?
|
1361
|
+
end
|
1362
|
+
end
|
1196
1363
|
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
|
1209
|
-
params[key] = each_element(value, filter[key]) do |element|
|
1210
|
-
element.permit(*Array.wrap(filter[key]))
|
1211
|
-
end
|
1212
|
-
end
|
1364
|
+
def permit_value(value, filter, on_unpermitted:, explicit_arrays:)
|
1365
|
+
if filter == EMPTY_ARRAY # Declaration { comment_ids: [] }.
|
1366
|
+
permit_array_of_scalars(value)
|
1367
|
+
elsif filter == EMPTY_HASH # Declaration { preferences: {} }.
|
1368
|
+
permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
1369
|
+
elsif array_filter?(filter) # Declaration { comments: [[:text]] }
|
1370
|
+
permit_array_of_hashes(value, filter.first, on_unpermitted:, explicit_arrays:)
|
1371
|
+
elsif explicit_arrays # Declaration { user: { address: ... } } or { user: [:name, ...] } (only allows hash value)
|
1372
|
+
permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
1373
|
+
elsif non_scalar?(value) # Declaration { user: { address: ... } } or { user: [:name, ...] }
|
1374
|
+
permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
|
1213
1375
|
end
|
1214
1376
|
end
|
1215
1377
|
|
1378
|
+
def permit_array_of_scalars(value)
|
1379
|
+
value if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
|
1380
|
+
end
|
1381
|
+
|
1382
|
+
def permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:)
|
1383
|
+
each_array_element(value, filter) do |element|
|
1384
|
+
element.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
|
1385
|
+
end
|
1386
|
+
end
|
1387
|
+
|
1388
|
+
def permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
1389
|
+
return unless value.is_a?(Parameters)
|
1390
|
+
|
1391
|
+
if filter == EMPTY_HASH
|
1392
|
+
permit_any_in_parameters(value)
|
1393
|
+
else
|
1394
|
+
value.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
|
1395
|
+
end
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
def permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
|
1399
|
+
permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:) ||
|
1400
|
+
permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
1401
|
+
end
|
1402
|
+
|
1216
1403
|
def permit_any_in_parameters(params)
|
1217
1404
|
self.class.new.tap do |sanitized|
|
1218
1405
|
params.each do |key, value|
|
@@ -1287,7 +1474,7 @@ module ActionController
|
|
1287
1474
|
# # list between create and update. Also, you can specialize this method
|
1288
1475
|
# # with per-user checking of permissible attributes.
|
1289
1476
|
# def person_params
|
1290
|
-
# params.
|
1477
|
+
# params.expect(person: [:name, :age])
|
1291
1478
|
# end
|
1292
1479
|
# end
|
1293
1480
|
#
|
@@ -1314,11 +1501,12 @@ module ActionController
|
|
1314
1501
|
# # It's mandatory to specify the nested attributes that should be permitted.
|
1315
1502
|
# # If you use `permit` with just the key that points to the nested attributes hash,
|
1316
1503
|
# # it will return an empty hash.
|
1317
|
-
# params.
|
1504
|
+
# params.expect(person: [ :name, :age, pets_attributes: [ :id, :name, :category ] ])
|
1318
1505
|
# end
|
1319
1506
|
# end
|
1320
1507
|
#
|
1321
|
-
# See ActionController::Parameters.
|
1508
|
+
# See ActionController::Parameters.expect,
|
1509
|
+
# See ActionController::Parameters.require, and
|
1322
1510
|
# ActionController::Parameters.permit for more information.
|
1323
1511
|
module StrongParameters
|
1324
1512
|
# Returns a new ActionController::Parameters object that has been instantiated
|