actionpack 7.2.1.1 → 8.0.0.rc1
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.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- 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
|