actionpack 7.2.1.1 → 8.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +108 -100
- data/lib/abstract_controller/helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +0 -1
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/form_builder.rb +3 -3
- data/lib/action_controller/metal/allow_browser.rb +12 -2
- data/lib/action_controller/metal/conditional_get.rb +6 -3
- data/lib/action_controller/metal/http_authentication.rb +6 -3
- data/lib/action_controller/metal/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +19 -8
- data/lib/action_controller/metal/rate_limiting.rb +13 -4
- 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 +274 -88
- data/lib/action_controller/railtie.rb +1 -7
- data/lib/action_controller/test_case.rb +6 -5
- data/lib/action_dispatch/http/cache.rb +27 -10
- data/lib/action_dispatch/http/content_security_policy.rb +5 -8
- data/lib/action_dispatch/http/filter_parameters.rb +4 -9
- data/lib/action_dispatch/http/filter_redirect.rb +2 -9
- data/lib/action_dispatch/http/param_builder.rb +163 -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 +31 -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 +40 -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/remote_ip.rb +5 -6
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/ssl.rb +14 -4
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
- data/lib/action_dispatch/railtie.rb +2 -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 +91 -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.rb +6 -0
- 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 shoudn'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,12 @@ 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.
|
84
97
|
#
|
85
98
|
# params = ActionController::Parameters.new({
|
86
99
|
# person: {
|
@@ -90,14 +103,14 @@ module ActionController
|
|
90
103
|
# }
|
91
104
|
# })
|
92
105
|
#
|
93
|
-
# permitted = params.
|
94
|
-
# permitted
|
95
|
-
# permitted.permitted? # => true
|
106
|
+
# permitted = params.expect(person: [:name, :age])
|
107
|
+
# permitted # => #<ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
|
96
108
|
#
|
97
109
|
# Person.first.update!(permitted)
|
98
110
|
# # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
|
99
111
|
#
|
100
|
-
#
|
112
|
+
# Paramaters provides two options that control the top-level behavior of new
|
113
|
+
# instances:
|
101
114
|
#
|
102
115
|
# * `permit_all_parameters` - If it's `true`, all the parameters will be
|
103
116
|
# permitted by default. The default is `false`.
|
@@ -112,8 +125,6 @@ module ActionController
|
|
112
125
|
# * `:raise` to raise an ActionController::UnpermittedParameters
|
113
126
|
# exception.
|
114
127
|
#
|
115
|
-
#
|
116
|
-
#
|
117
128
|
# Examples:
|
118
129
|
#
|
119
130
|
# params = ActionController::Parameters.new
|
@@ -250,20 +261,6 @@ module ActionController
|
|
250
261
|
cattr_accessor :always_permitted_parameters, default: %w( controller action )
|
251
262
|
|
252
263
|
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
264
|
def nested_attribute?(key, value) # :nodoc:
|
268
265
|
/\A-?\d+\z/.match?(key) && (value.is_a?(Hash) || value.is_a?(Parameters))
|
269
266
|
end
|
@@ -481,16 +478,16 @@ module ActionController
|
|
481
478
|
# Otherwise raises ActionController::ParameterMissing:
|
482
479
|
#
|
483
480
|
# ActionController::Parameters.new.require(:person)
|
484
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
481
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
|
485
482
|
#
|
486
483
|
# ActionController::Parameters.new(person: nil).require(:person)
|
487
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
484
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
|
488
485
|
#
|
489
486
|
# ActionController::Parameters.new(person: "\t").require(:person)
|
490
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
487
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
|
491
488
|
#
|
492
489
|
# ActionController::Parameters.new(person: {}).require(:person)
|
493
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: person
|
490
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
|
494
491
|
#
|
495
492
|
# When given an array of keys, the method tries to require each one of them in
|
496
493
|
# order. If it succeeds, an array with the respective return values is returned:
|
@@ -502,23 +499,21 @@ module ActionController
|
|
502
499
|
#
|
503
500
|
# params = ActionController::Parameters.new(user: {}, profile: {})
|
504
501
|
# user_params, profile_params = params.require([:user, :profile])
|
505
|
-
# # ActionController::ParameterMissing: param is missing or the value is empty: user
|
502
|
+
# # ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
|
506
503
|
#
|
507
|
-
#
|
504
|
+
# This method is not recommended for fetching terminal values because it does
|
505
|
+
# not permit the values. For example, this can cause problems:
|
508
506
|
#
|
509
507
|
# # CAREFUL
|
510
508
|
# params = ActionController::Parameters.new(person: { name: "Finn" })
|
511
509
|
# name = params.require(:person).require(:name) # CAREFUL
|
512
510
|
#
|
513
|
-
#
|
511
|
+
# It is recommended to use `expect` instead:
|
514
512
|
#
|
515
513
|
# def person_params
|
516
|
-
# params.
|
517
|
-
# person_params.require(:name) # SAFER
|
518
|
-
# end
|
514
|
+
# # params.expect(person: :name).require(:name)
|
519
515
|
# end
|
520
516
|
#
|
521
|
-
# for example.
|
522
517
|
def require(key)
|
523
518
|
return key.map { |k| require(k) } if key.is_a?(Array)
|
524
519
|
value = self[key]
|
@@ -536,8 +531,8 @@ module ActionController
|
|
536
531
|
# This is useful for limiting which attributes should be allowed for mass
|
537
532
|
# updating.
|
538
533
|
#
|
539
|
-
# params = ActionController::Parameters.new(
|
540
|
-
# permitted = params.
|
534
|
+
# params = ActionController::Parameters.new(name: "Francesco", age: 22, role: "admin")
|
535
|
+
# permitted = params.permit(:name, :age)
|
541
536
|
# permitted.permitted? # => true
|
542
537
|
# permitted.has_key?(:name) # => true
|
543
538
|
# permitted.has_key?(:age) # => true
|
@@ -567,7 +562,7 @@ module ActionController
|
|
567
562
|
# `permit` ensures values in the returned structure are permitted scalars and
|
568
563
|
# filters out anything else.
|
569
564
|
#
|
570
|
-
# You can also use `permit` on nested parameters
|
565
|
+
# You can also use `permit` on nested parameters:
|
571
566
|
#
|
572
567
|
# params = ActionController::Parameters.new({
|
573
568
|
# person: {
|
@@ -587,6 +582,29 @@ module ActionController
|
|
587
582
|
# permitted[:person][:pets][0][:name] # => "Purplish"
|
588
583
|
# permitted[:person][:pets][0][:category] # => nil
|
589
584
|
#
|
585
|
+
# This has the added benefit of rejecting user-modified inputs that send a
|
586
|
+
# string when a hash is expected.
|
587
|
+
#
|
588
|
+
# When followed by `require`, you can both filter and require parameters
|
589
|
+
# following the typical pattern of a Rails form. The `expect` method was
|
590
|
+
# made specifically for this use case and is the recommended way to require
|
591
|
+
# and permit parameters.
|
592
|
+
#
|
593
|
+
# permitted = params.expect(person: [:name, :age])
|
594
|
+
#
|
595
|
+
# When using `permit` and `require` separately, pay careful attention to the
|
596
|
+
# order of the method calls.
|
597
|
+
#
|
598
|
+
# params = ActionController::Parameters.new(person: { name: "Martin", age: 40, role: "admin" })
|
599
|
+
# permitted = params.permit(person: [:name, :age]).require(:person) # correct
|
600
|
+
#
|
601
|
+
# When require is used first, it is possible for users of your application to
|
602
|
+
# trigger a NoMethodError when the user, for example, sends a string for :person.
|
603
|
+
#
|
604
|
+
# params = ActionController::Parameters.new(person: "tampered")
|
605
|
+
# permitted = params.require(:person).permit(:name, :age) # not recommended
|
606
|
+
# # => NoMethodError: undefined method `permit' for an instance of String
|
607
|
+
#
|
590
608
|
# Note that if you use `permit` in a key that points to a hash, it won't allow
|
591
609
|
# all the hash. You also need to specify which attributes inside the hash should
|
592
610
|
# be permitted.
|
@@ -600,13 +618,13 @@ module ActionController
|
|
600
618
|
# }
|
601
619
|
# })
|
602
620
|
#
|
603
|
-
# params.
|
621
|
+
# params.permit(person: :contact).require(:person)
|
604
622
|
# # => #<ActionController::Parameters {} permitted: true>
|
605
623
|
#
|
606
|
-
# params.
|
624
|
+
# params.permit(person: { contact: :phone }).require(:person)
|
607
625
|
# # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
|
608
626
|
#
|
609
|
-
# params.
|
627
|
+
# params.permit(person: { contact: [ :email, :phone ] }).require(:person)
|
610
628
|
# # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
|
611
629
|
#
|
612
630
|
# If your parameters specify multiple parameters indexed by a number, you can
|
@@ -646,20 +664,127 @@ module ActionController
|
|
646
664
|
# params.permit(person: { '0': [:email], '1': [:phone]}).to_h
|
647
665
|
# # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"phone"=>"555-6789"}}}
|
648
666
|
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
|
667
|
+
permit_filters(filters, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
|
668
|
+
end
|
659
669
|
|
660
|
-
|
670
|
+
# `expect` is the preferred way to require and permit parameters.
|
671
|
+
# It is safer than the previous recommendation to call `permit` and `require`
|
672
|
+
# in sequence, which could allow user triggered 500 errors.
|
673
|
+
#
|
674
|
+
# `expect` is more strict with types to avoid a number of potential pitfalls
|
675
|
+
# that may be encountered with the `.require.permit` pattern.
|
676
|
+
#
|
677
|
+
# For example:
|
678
|
+
#
|
679
|
+
# params = ActionController::Parameters.new(comment: { text: "hello" })
|
680
|
+
# params.expect(comment: [:text])
|
681
|
+
# # => #<ActionController::Parameters { text: "hello" } permitted: true>
|
682
|
+
#
|
683
|
+
# params = ActionController::Parameters.new(comment: [{ text: "hello" }, { text: "world" }])
|
684
|
+
# params.expect(comment: [:text])
|
685
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comment
|
686
|
+
#
|
687
|
+
# In order to permit an array of parameters, the array must be defined
|
688
|
+
# explicitly. Use double array brackets, an array inside an array, to
|
689
|
+
# declare that an array of parameters is expected.
|
690
|
+
#
|
691
|
+
# params = ActionController::Parameters.new(comments: [{ text: "hello" }, { text: "world" }])
|
692
|
+
# params.expect(comments: [[:text]])
|
693
|
+
# # => [#<ActionController::Parameters { "text" => "hello" } permitted: true>,
|
694
|
+
# # #<ActionController::Parameters { "text" => "world" } permitted: true>]
|
695
|
+
#
|
696
|
+
# params = ActionController::Parameters.new(comments: { text: "hello" })
|
697
|
+
# params.expect(comments: [[:text]])
|
698
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comments
|
699
|
+
#
|
700
|
+
# `expect` is intended to protect against array tampering.
|
701
|
+
#
|
702
|
+
# params = ActionController::Parameters.new(user: "hack")
|
703
|
+
# # The previous way of requiring and permitting parameters will error
|
704
|
+
# params.require(:user).permit(:name, pets: [:name]) # wrong
|
705
|
+
# # => NoMethodError: undefined method `permit' for an instance of String
|
706
|
+
#
|
707
|
+
# # similarly with nested parameters
|
708
|
+
# params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
|
709
|
+
# user_params = params.require(:user).permit(:name, pets: [:name]) # wrong
|
710
|
+
# # user_params[:pets] is expected to be an array but is a hash
|
711
|
+
#
|
712
|
+
# `expect` solves this by being more strict with types.
|
713
|
+
#
|
714
|
+
# params = ActionController::Parameters.new(user: "hack")
|
715
|
+
# params.expect(user: [ :name, pets: [[:name]] ])
|
716
|
+
# # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
|
717
|
+
#
|
718
|
+
# # with nested parameters
|
719
|
+
# params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
|
720
|
+
# user_params = params.expect(user: [:name, pets: [[:name]] ])
|
721
|
+
# user_params[:pets] # => nil
|
722
|
+
#
|
723
|
+
# As the examples show, `expect` requires the `:user` key, and any root keys
|
724
|
+
# similar to the `.require.permit` pattern. If multiple root keys are
|
725
|
+
# expected, they will all be required.
|
726
|
+
#
|
727
|
+
# params = ActionController::Parameters.new(name: "Martin", pies: [{ type: "dessert", flavor: "pumpkin"}])
|
728
|
+
# name, pies = params.expect(:name, pies: [[:type, :flavor]])
|
729
|
+
# name # => "Martin"
|
730
|
+
# pies # => [#<ActionController::Parameters {"type"=>"dessert", "flavor"=>"pumpkin"} permitted: true>]
|
731
|
+
#
|
732
|
+
# When called with a hash with multiple keys, `expect` will permit the
|
733
|
+
# parameters and require the keys in the order they are given in the hash,
|
734
|
+
# returning an array of the permitted parameters.
|
735
|
+
#
|
736
|
+
# params = ActionController::Parameters.new(subject: { name: "Martin" }, object: { pie: "pumpkin" })
|
737
|
+
# subject, object = params.expect(subject: [:name], object: [:pie])
|
738
|
+
# subject # => #<ActionController::Parameters {"name"=>"Martin"} permitted: true>
|
739
|
+
# object # => #<ActionController::Parameters {"pie"=>"pumpkin"} permitted: true>
|
740
|
+
#
|
741
|
+
# Besides being more strict about array vs hash params, `expect` uses permit
|
742
|
+
# internally, so it will behave similarly.
|
743
|
+
#
|
744
|
+
# params = ActionController::Parameters.new({
|
745
|
+
# person: {
|
746
|
+
# name: "Francesco",
|
747
|
+
# age: 22,
|
748
|
+
# pets: [{
|
749
|
+
# name: "Purplish",
|
750
|
+
# category: "dogs"
|
751
|
+
# }]
|
752
|
+
# }
|
753
|
+
# })
|
754
|
+
#
|
755
|
+
# permitted = params.expect(person: [ :name, { pets: [[:name]] } ])
|
756
|
+
# permitted.permitted? # => true
|
757
|
+
# permitted[:name] # => "Francesco"
|
758
|
+
# permitted[:age] # => nil
|
759
|
+
# permitted[:pets][0][:name] # => "Purplish"
|
760
|
+
# permitted[:pets][0][:category] # => nil
|
761
|
+
#
|
762
|
+
# An array of permitted scalars may be expected with the following:
|
763
|
+
#
|
764
|
+
# params = ActionController::Parameters.new(tags: ["rails", "parameters"])
|
765
|
+
# permitted = params.expect(tags: [])
|
766
|
+
# permitted.permitted? # => true
|
767
|
+
# permitted.is_a?(Array) # => true
|
768
|
+
# permitted.size # => 2
|
769
|
+
#
|
770
|
+
def expect(*filters)
|
771
|
+
params = permit_filters(filters)
|
772
|
+
keys = filters.flatten.flat_map { |f| f.is_a?(Hash) ? f.keys : f }
|
773
|
+
values = params.require(keys)
|
774
|
+
values.size == 1 ? values.first : values
|
775
|
+
end
|
661
776
|
|
662
|
-
|
777
|
+
# Same as `expect`, but raises an `ActionController::ExpectedParameterMissing`
|
778
|
+
# instead of `ActionController::ParameterMissing`. Unlike `expect` which
|
779
|
+
# will render a 400 response, `expect!` will raise an exception that is
|
780
|
+
# not handled. This is intended for debugging invalid params for an
|
781
|
+
# internal API where incorrectly formatted params would indicate a bug
|
782
|
+
# in a client library that should be fixed.
|
783
|
+
#
|
784
|
+
def expect!(*filters)
|
785
|
+
expect(*filters)
|
786
|
+
rescue ParameterMissing => e
|
787
|
+
raise ExpectedParameterMissing.new(e.param, e.keys)
|
663
788
|
end
|
664
789
|
|
665
790
|
# Returns a parameter for the given `key`. If not found, returns `nil`.
|
@@ -686,7 +811,7 @@ module ActionController
|
|
686
811
|
#
|
687
812
|
# params = ActionController::Parameters.new(person: { name: "Francesco" })
|
688
813
|
# params.fetch(:person) # => #<ActionController::Parameters {"name"=>"Francesco"} permitted: false>
|
689
|
-
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
|
814
|
+
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: none
|
690
815
|
# params.fetch(:none, {}) # => #<ActionController::Parameters {} permitted: false>
|
691
816
|
# params.fetch(:none, "Francesco") # => "Francesco"
|
692
817
|
# params.fetch(:none) { "Francesco" } # => "Francesco"
|
@@ -999,6 +1124,26 @@ module ActionController
|
|
999
1124
|
hash
|
1000
1125
|
end
|
1001
1126
|
|
1127
|
+
# Filters self and optionally checks for unpermitted keys
|
1128
|
+
def permit_filters(filters, on_unpermitted: nil, explicit_arrays: true)
|
1129
|
+
params = self.class.new
|
1130
|
+
|
1131
|
+
filters.flatten.each do |filter|
|
1132
|
+
case filter
|
1133
|
+
when Symbol, String
|
1134
|
+
# Declaration [:name, "age"]
|
1135
|
+
permitted_scalar_filter(params, filter)
|
1136
|
+
when Hash
|
1137
|
+
# Declaration [{ person: ... }]
|
1138
|
+
hash_filter(params, filter, on_unpermitted:, explicit_arrays:)
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
unpermitted_parameters!(params, on_unpermitted:)
|
1143
|
+
|
1144
|
+
params.permit!
|
1145
|
+
end
|
1146
|
+
|
1002
1147
|
private
|
1003
1148
|
def new_instance_with_inherited_permitted_status(hash)
|
1004
1149
|
self.class.new(hash, @logging_context).tap do |new_instance|
|
@@ -1088,23 +1233,45 @@ module ActionController
|
|
1088
1233
|
end
|
1089
1234
|
end
|
1090
1235
|
|
1091
|
-
|
1236
|
+
# When an array is expected, you must specify an array explicitly
|
1237
|
+
# using the following format:
|
1238
|
+
#
|
1239
|
+
# params.expect(comments: [[:flavor]])
|
1240
|
+
#
|
1241
|
+
# Which will match only the following array formats:
|
1242
|
+
#
|
1243
|
+
# { pies: [{ flavor: "rhubarb" }, { flavor: "apple" }] }
|
1244
|
+
# { pies: { "0" => { flavor: "key lime" }, "1" => { flavor: "mince" } } }
|
1245
|
+
#
|
1246
|
+
# When using `permit`, arrays are specified the same way as hashes:
|
1247
|
+
#
|
1248
|
+
# params.expect(pies: [:flavor])
|
1249
|
+
#
|
1250
|
+
# In this case, `permit` would also allow matching with a hash (or vice versa):
|
1251
|
+
#
|
1252
|
+
# { pies: { flavor: "cherry" } }
|
1253
|
+
#
|
1254
|
+
def array_filter?(filter)
|
1255
|
+
filter.is_a?(Array) && filter.size == 1 && filter.first.is_a?(Array)
|
1256
|
+
end
|
1257
|
+
|
1258
|
+
# Called when an explicit array filter is encountered.
|
1259
|
+
def each_array_element(object, filter, &block)
|
1092
1260
|
case object
|
1093
1261
|
when Array
|
1094
1262
|
object.grep(Parameters).filter_map(&block)
|
1095
1263
|
when Parameters
|
1096
1264
|
if object.nested_attributes? && !specify_numeric_keys?(filter)
|
1097
1265
|
object.each_nested_attribute(&block)
|
1098
|
-
else
|
1099
|
-
yield object
|
1100
1266
|
end
|
1101
1267
|
end
|
1102
1268
|
end
|
1103
1269
|
|
1104
|
-
def unpermitted_parameters!(params)
|
1270
|
+
def unpermitted_parameters!(params, on_unpermitted: self.class.action_on_unpermitted_parameters)
|
1271
|
+
return unless on_unpermitted
|
1105
1272
|
unpermitted_keys = unpermitted_keys(params)
|
1106
1273
|
if unpermitted_keys.any?
|
1107
|
-
case
|
1274
|
+
case on_unpermitted
|
1108
1275
|
when :log
|
1109
1276
|
name = "unpermitted_parameters.action_controller"
|
1110
1277
|
ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys, context: @logging_context)
|
@@ -1174,45 +1341,63 @@ module ActionController
|
|
1174
1341
|
end
|
1175
1342
|
end
|
1176
1343
|
|
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
1344
|
def non_scalar?(value)
|
1184
1345
|
value.is_a?(Array) || value.is_a?(Parameters)
|
1185
1346
|
end
|
1186
1347
|
|
1187
1348
|
EMPTY_ARRAY = [] # :nodoc:
|
1188
1349
|
EMPTY_HASH = {} # :nodoc:
|
1189
|
-
def hash_filter(params, filter)
|
1350
|
+
def hash_filter(params, filter, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
|
1190
1351
|
filter = filter.with_indifferent_access
|
1191
1352
|
|
1192
1353
|
# Slicing filters out non-declared keys.
|
1193
1354
|
slice(*filter.keys).each do |key, value|
|
1194
1355
|
next unless value
|
1195
1356
|
next unless has_key? key
|
1357
|
+
result = permit_value(value, filter[key], on_unpermitted:, explicit_arrays:)
|
1358
|
+
params[key] = result unless result.nil?
|
1359
|
+
end
|
1360
|
+
end
|
1196
1361
|
|
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
|
1362
|
+
def permit_value(value, filter, on_unpermitted:, explicit_arrays:)
|
1363
|
+
if filter == EMPTY_ARRAY # Declaration { comment_ids: [] }.
|
1364
|
+
permit_array_of_scalars(value)
|
1365
|
+
elsif filter == EMPTY_HASH # Declaration { preferences: {} }.
|
1366
|
+
permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
1367
|
+
elsif array_filter?(filter) # Declaration { comments: [[:text]] }
|
1368
|
+
permit_array_of_hashes(value, filter.first, on_unpermitted:, explicit_arrays:)
|
1369
|
+
elsif explicit_arrays # Declaration { user: { address: ... } } or { user: [:name, ...] } (only allows hash value)
|
1370
|
+
permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
1371
|
+
elsif non_scalar?(value) # Declaration { user: { address: ... } } or { user: [:name, ...] }
|
1372
|
+
permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
|
1213
1373
|
end
|
1214
1374
|
end
|
1215
1375
|
|
1376
|
+
def permit_array_of_scalars(value)
|
1377
|
+
value if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
def permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:)
|
1381
|
+
each_array_element(value, filter) do |element|
|
1382
|
+
element.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
|
1383
|
+
end
|
1384
|
+
end
|
1385
|
+
|
1386
|
+
def permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
1387
|
+
return unless value.is_a?(Parameters)
|
1388
|
+
|
1389
|
+
if filter == EMPTY_HASH
|
1390
|
+
permit_any_in_parameters(value)
|
1391
|
+
else
|
1392
|
+
value.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
|
1393
|
+
end
|
1394
|
+
end
|
1395
|
+
|
1396
|
+
def permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
|
1397
|
+
permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:) ||
|
1398
|
+
permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
1399
|
+
end
|
1400
|
+
|
1216
1401
|
def permit_any_in_parameters(params)
|
1217
1402
|
self.class.new.tap do |sanitized|
|
1218
1403
|
params.each do |key, value|
|
@@ -1287,7 +1472,7 @@ module ActionController
|
|
1287
1472
|
# # list between create and update. Also, you can specialize this method
|
1288
1473
|
# # with per-user checking of permissible attributes.
|
1289
1474
|
# def person_params
|
1290
|
-
# params.
|
1475
|
+
# params.expect(person: [:name, :age])
|
1291
1476
|
# end
|
1292
1477
|
# end
|
1293
1478
|
#
|
@@ -1314,11 +1499,12 @@ module ActionController
|
|
1314
1499
|
# # It's mandatory to specify the nested attributes that should be permitted.
|
1315
1500
|
# # If you use `permit` with just the key that points to the nested attributes hash,
|
1316
1501
|
# # it will return an empty hash.
|
1317
|
-
# params.
|
1502
|
+
# params.expect(person: [ :name, :age, pets_attributes: [ :id, :name, :category ] ])
|
1318
1503
|
# end
|
1319
1504
|
# end
|
1320
1505
|
#
|
1321
|
-
# See ActionController::Parameters.
|
1506
|
+
# See ActionController::Parameters.expect,
|
1507
|
+
# See ActionController::Parameters.require, and
|
1322
1508
|
# ActionController::Parameters.permit for more information.
|
1323
1509
|
module StrongParameters
|
1324
1510
|
# Returns a new ActionController::Parameters object that has been instantiated
|