actionpack 7.2.3 → 8.0.0.beta1

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.

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -187
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/base.rb +12 -1
  5. data/lib/abstract_controller/collector.rb +1 -1
  6. data/lib/abstract_controller/helpers.rb +1 -3
  7. data/lib/action_controller/metal/allow_browser.rb +1 -1
  8. data/lib/action_controller/metal/conditional_get.rb +5 -1
  9. data/lib/action_controller/metal/http_authentication.rb +4 -1
  10. data/lib/action_controller/metal/instrumentation.rb +1 -2
  11. data/lib/action_controller/metal/live.rb +11 -3
  12. data/lib/action_controller/metal/params_wrapper.rb +3 -3
  13. data/lib/action_controller/metal/rate_limiting.rb +13 -4
  14. data/lib/action_controller/metal/redirecting.rb +3 -4
  15. data/lib/action_controller/metal/renderers.rb +2 -1
  16. data/lib/action_controller/metal/rendering.rb +1 -1
  17. data/lib/action_controller/metal/request_forgery_protection.rb +1 -3
  18. data/lib/action_controller/metal/streaming.rb +5 -84
  19. data/lib/action_controller/metal/strong_parameters.rb +277 -73
  20. data/lib/action_controller/railtie.rb +1 -1
  21. data/lib/action_controller/renderer.rb +1 -0
  22. data/lib/action_controller/test_case.rb +2 -0
  23. data/lib/action_dispatch/constants.rb +0 -6
  24. data/lib/action_dispatch/http/cache.rb +27 -10
  25. data/lib/action_dispatch/http/content_security_policy.rb +13 -25
  26. data/lib/action_dispatch/http/filter_parameters.rb +4 -9
  27. data/lib/action_dispatch/http/filter_redirect.rb +2 -9
  28. data/lib/action_dispatch/http/mime_negotiation.rb +3 -8
  29. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  30. data/lib/action_dispatch/http/request.rb +7 -6
  31. data/lib/action_dispatch/http/response.rb +1 -15
  32. data/lib/action_dispatch/http/url.rb +2 -2
  33. data/lib/action_dispatch/journey/formatter.rb +3 -8
  34. data/lib/action_dispatch/journey/gtg/transition_table.rb +4 -4
  35. data/lib/action_dispatch/journey/parser.rb +99 -196
  36. data/lib/action_dispatch/journey/scanner.rb +40 -42
  37. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  38. data/lib/action_dispatch/middleware/debug_exceptions.rb +17 -6
  39. data/lib/action_dispatch/middleware/exception_wrapper.rb +3 -3
  40. data/lib/action_dispatch/middleware/executor.rb +2 -5
  41. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
  42. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  43. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  44. data/lib/action_dispatch/railtie.rb +2 -0
  45. data/lib/action_dispatch/routing/inspector.rb +1 -1
  46. data/lib/action_dispatch/routing/mapper.rb +30 -22
  47. data/lib/action_dispatch/routing/route_set.rb +18 -6
  48. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  49. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  50. data/lib/action_dispatch/testing/integration.rb +3 -2
  51. data/lib/action_dispatch/testing/request_encoder.rb +9 -9
  52. data/lib/action_dispatch/testing/test_process.rb +2 -1
  53. data/lib/action_dispatch.rb +0 -4
  54. data/lib/action_pack/gem_version.rb +4 -4
  55. metadata +16 -49
  56. data/lib/action_dispatch/journey/parser.y +0 -50
  57. 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
- # Not all web servers support streaming out-of-the-box. You need to check the
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
- Body.new view_renderer.render_body(view_context, options)
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
@@ -18,16 +18,18 @@ module ActionController
18
18
  #
19
19
  # params = ActionController::Parameters.new(a: {})
20
20
  # params.fetch(:b)
21
- # # => ActionController::ParameterMissing: param is missing or the value is empty: b
21
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: b
22
22
  # params.require(:a)
23
- # # => ActionController::ParameterMissing: param is missing or the value is empty: a
23
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a
24
+ # params.expect(a: [])
25
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a
24
26
  class ParameterMissing < KeyError
25
27
  attr_reader :param, :keys # :nodoc:
26
28
 
27
29
  def initialize(param, keys = nil) # :nodoc:
28
30
  @param = param
29
31
  @keys = keys
30
- super("param is missing or the value is empty: #{param}")
32
+ super("param is missing or the value is empty or invalid: #{param}")
31
33
  end
32
34
 
33
35
  if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
@@ -39,6 +41,15 @@ module ActionController
39
41
  end
40
42
  end
41
43
 
44
+ # Raised from `expect!` when an expected parameter is missing or is of an
45
+ # incompatible type.
46
+ #
47
+ # params = ActionController::Parameters.new(a: {})
48
+ # params.expect!(:a)
49
+ # # => ActionController::ExpectedParameterMissing: param is missing or the value is empty or invalid: a
50
+ class ExpectedParameterMissing < ParameterMissing
51
+ end
52
+
42
53
  # Raised when a supplied parameter is not expected and
43
54
  # ActionController::Parameters.action_on_unpermitted_parameters is set to
44
55
  # `:raise`.
@@ -78,9 +89,12 @@ module ActionController
78
89
  #
79
90
  # Allows you to choose which attributes should be permitted for mass updating
80
91
  # and thus prevent accidentally exposing that which shouldn't be exposed.
81
- # Provides two methods for this purpose: #require and #permit. The former is
82
- # used to mark parameters as required. The latter is used to set the parameter
83
- # as permitted and limit which attributes should be allowed for mass updating.
92
+ #
93
+ # Provides methods for filtering and requiring params:
94
+ #
95
+ # * `expect` to safely permit and require parameters in one step.
96
+ # * `permit` to filter params for mass assignment.
97
+ # * `require` to require a parameter or raise an error.
84
98
  #
85
99
  # params = ActionController::Parameters.new({
86
100
  # person: {
@@ -90,14 +104,14 @@ module ActionController
90
104
  # }
91
105
  # })
92
106
  #
93
- # permitted = params.require(:person).permit(:name, :age)
94
- # permitted # => #<ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
95
- # permitted.permitted? # => true
107
+ # permitted = params.expect(person: [:name, :age])
108
+ # permitted # => #<ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
96
109
  #
97
110
  # Person.first.update!(permitted)
98
111
  # # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
99
112
  #
100
- # It provides two options that controls the top-level behavior of new instances:
113
+ # Paramaters provides two options that control the top-level behavior of new
114
+ # instances:
101
115
  #
102
116
  # * `permit_all_parameters` - If it's `true`, all the parameters will be
103
117
  # permitted by default. The default is `false`.
@@ -112,8 +126,6 @@ module ActionController
112
126
  # * `:raise` to raise an ActionController::UnpermittedParameters
113
127
  # exception.
114
128
  #
115
- #
116
- #
117
129
  # Examples:
118
130
  #
119
131
  # params = ActionController::Parameters.new
@@ -481,16 +493,16 @@ module ActionController
481
493
  # Otherwise raises ActionController::ParameterMissing:
482
494
  #
483
495
  # ActionController::Parameters.new.require(:person)
484
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
496
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
485
497
  #
486
498
  # ActionController::Parameters.new(person: nil).require(:person)
487
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
499
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
488
500
  #
489
501
  # ActionController::Parameters.new(person: "\t").require(:person)
490
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
502
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
491
503
  #
492
504
  # ActionController::Parameters.new(person: {}).require(:person)
493
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
505
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
494
506
  #
495
507
  # When given an array of keys, the method tries to require each one of them in
496
508
  # order. If it succeeds, an array with the respective return values is returned:
@@ -502,23 +514,21 @@ module ActionController
502
514
  #
503
515
  # params = ActionController::Parameters.new(user: {}, profile: {})
504
516
  # user_params, profile_params = params.require([:user, :profile])
505
- # # ActionController::ParameterMissing: param is missing or the value is empty: user
517
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
506
518
  #
507
- # Technically this method can be used to fetch terminal values:
519
+ # This method is not recommended for fetching terminal values because it does
520
+ # not permit the values. For example, this can cause problems:
508
521
  #
509
522
  # # CAREFUL
510
523
  # params = ActionController::Parameters.new(person: { name: "Finn" })
511
524
  # name = params.require(:person).require(:name) # CAREFUL
512
525
  #
513
- # but take into account that at some point those ones have to be permitted:
526
+ # It is recommended to use `expect` instead:
514
527
  #
515
528
  # def person_params
516
- # params.require(:person).permit(:name).tap do |person_params|
517
- # person_params.require(:name) # SAFER
518
- # end
529
+ # # params.expect(person: :name).require(:name)
519
530
  # end
520
531
  #
521
- # for example.
522
532
  def require(key)
523
533
  return key.map { |k| require(k) } if key.is_a?(Array)
524
534
  value = self[key]
@@ -536,8 +546,8 @@ module ActionController
536
546
  # This is useful for limiting which attributes should be allowed for mass
537
547
  # updating.
538
548
  #
539
- # params = ActionController::Parameters.new(user: { name: "Francesco", age: 22, role: "admin" })
540
- # permitted = params.require(:user).permit(:name, :age)
549
+ # params = ActionController::Parameters.new(name: "Francesco", age: 22, role: "admin")
550
+ # permitted = params.permit(:name, :age)
541
551
  # permitted.permitted? # => true
542
552
  # permitted.has_key?(:name) # => true
543
553
  # permitted.has_key?(:age) # => true
@@ -567,7 +577,7 @@ module ActionController
567
577
  # `permit` ensures values in the returned structure are permitted scalars and
568
578
  # filters out anything else.
569
579
  #
570
- # You can also use `permit` on nested parameters, like:
580
+ # You can also use `permit` on nested parameters:
571
581
  #
572
582
  # params = ActionController::Parameters.new({
573
583
  # person: {
@@ -587,6 +597,29 @@ module ActionController
587
597
  # permitted[:person][:pets][0][:name] # => "Purplish"
588
598
  # permitted[:person][:pets][0][:category] # => nil
589
599
  #
600
+ # This has the added benefit of rejecting user-modified inputs that send a
601
+ # string when a hash is expected.
602
+ #
603
+ # When followed by `require`, you can both filter and require parameters
604
+ # following the typical pattern of a Rails form. The `expect` method was
605
+ # made specifically for this use case and is the recommended way to require
606
+ # and permit parameters.
607
+ #
608
+ # permitted = params.expect(person: [:name, :age])
609
+ #
610
+ # When using `permit` and `require` separately, pay careful attention to the
611
+ # order of the method calls.
612
+ #
613
+ # params = ActionController::Parameters.new(person: { name: "Martin", age: 40, role: "admin" })
614
+ # permitted = params.permit(person: [:name, :age]).require(:person) # correct
615
+ #
616
+ # When require is used first, it is possible for users of your application to
617
+ # trigger a NoMethodError when the user, for example, sends a string for :person.
618
+ #
619
+ # params = ActionController::Parameters.new(person: "tampered")
620
+ # permitted = params.require(:person).permit(:name, :age) # not recommended
621
+ # # => NoMethodError: undefined method `permit' for an instance of String
622
+ #
590
623
  # Note that if you use `permit` in a key that points to a hash, it won't allow
591
624
  # all the hash. You also need to specify which attributes inside the hash should
592
625
  # be permitted.
@@ -600,13 +633,13 @@ module ActionController
600
633
  # }
601
634
  # })
602
635
  #
603
- # params.require(:person).permit(:contact)
636
+ # params.permit(person: :contact).require(:person)
604
637
  # # => #<ActionController::Parameters {} permitted: true>
605
638
  #
606
- # params.require(:person).permit(contact: :phone)
639
+ # params.permit(person: { contact: :phone }).require(:person)
607
640
  # # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
608
641
  #
609
- # params.require(:person).permit(contact: [ :email, :phone ])
642
+ # params.permit(person: { contact: [ :email, :phone ] }).require(:person)
610
643
  # # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
611
644
  #
612
645
  # If your parameters specify multiple parameters indexed by a number, you can
@@ -646,20 +679,127 @@ module ActionController
646
679
  # params.permit(person: { '0': [:email], '1': [:phone]}).to_h
647
680
  # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"phone"=>"555-6789"}}}
648
681
  def permit(*filters)
649
- params = self.class.new
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
682
+ permit_filters(filters, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
683
+ end
659
684
 
660
- unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
685
+ # `expect` is the preferred way to require and permit parameters.
686
+ # It is safer than the previous recommendation to call `permit` and `require`
687
+ # in sequence, which could allow user triggered 500 errors.
688
+ #
689
+ # `expect` is more strict with types to avoid a number of potential pitfalls
690
+ # that may be encountered with the `.require.permit` pattern.
691
+ #
692
+ # For example:
693
+ #
694
+ # params = ActionController::Parameters.new(comment: { text: "hello" })
695
+ # params.expect(comment: [:text])
696
+ # # => #<ActionController::Parameters { text: "hello" } permitted: true>
697
+ #
698
+ # params = ActionController::Parameters.new(comment: [{ text: "hello" }, { text: "world" }])
699
+ # params.expect(comment: [:text])
700
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comment
701
+ #
702
+ # In order to permit an array of parameters, the array must be defined
703
+ # explicitly. Use double array brackets, an array inside an array, to
704
+ # declare that an array of parameters is expected.
705
+ #
706
+ # params = ActionController::Parameters.new(comments: [{ text: "hello" }, { text: "world" }])
707
+ # params.expect(comments: [[:text]])
708
+ # # => [#<ActionController::Parameters { "text" => "hello" } permitted: true>,
709
+ # # #<ActionController::Parameters { "text" => "world" } permitted: true>]
710
+ #
711
+ # params = ActionController::Parameters.new(comments: { text: "hello" })
712
+ # params.expect(comments: [[:text]])
713
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comments
714
+ #
715
+ # `expect` is intended to protect against array tampering.
716
+ #
717
+ # params = ActionController::Parameters.new(user: "hack")
718
+ # # The previous way of requiring and permitting parameters will error
719
+ # params.require(:user).permit(:name, pets: [:name]) # wrong
720
+ # # => NoMethodError: undefined method `permit' for an instance of String
721
+ #
722
+ # # similarly with nested parameters
723
+ # params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
724
+ # user_params = params.require(:user).permit(:name, pets: [:name]) # wrong
725
+ # # user_params[:pets] is expected to be an array but is a hash
726
+ #
727
+ # `expect` solves this by being more strict with types.
728
+ #
729
+ # params = ActionController::Parameters.new(user: "hack")
730
+ # params.expect(user: [ :name, pets: [[:name]] ])
731
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
732
+ #
733
+ # # with nested parameters
734
+ # params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
735
+ # user_params = params.expect(user: [:name, pets: [[:name]] ])
736
+ # user_params[:pets] # => nil
737
+ #
738
+ # As the examples show, `expect` requires the `:user` key, and any root keys
739
+ # similar to the `.require.permit` pattern. If multiple root keys are
740
+ # expected, they will all be required.
741
+ #
742
+ # params = ActionController::Parameters.new(name: "Martin", pies: [{ type: "dessert", flavor: "pumpkin"}])
743
+ # name, pies = params.expect(:name, pies: [[:type, :flavor]])
744
+ # name # => "Martin"
745
+ # pies # => [#<ActionController::Parameters {"type"=>"dessert", "flavor"=>"pumpkin"} permitted: true>]
746
+ #
747
+ # When called with a hash with multiple keys, `expect` will permit the
748
+ # parameters and require the keys in the order they are given in the hash,
749
+ # returning an array of the permitted parameters.
750
+ #
751
+ # params = ActionController::Parameters.new(subject: { name: "Martin" }, object: { pie: "pumpkin" })
752
+ # subject, object = params.expect(subject: [:name], object: [:pie])
753
+ # subject # => #<ActionController::Parameters {"name"=>"Martin"} permitted: true>
754
+ # object # => #<ActionController::Parameters {"pie"=>"pumpkin"} permitted: true>
755
+ #
756
+ # Besides being more strict about array vs hash params, `expect` uses permit
757
+ # internally, so it will behave similarly.
758
+ #
759
+ # params = ActionController::Parameters.new({
760
+ # person: {
761
+ # name: "Francesco",
762
+ # age: 22,
763
+ # pets: [{
764
+ # name: "Purplish",
765
+ # category: "dogs"
766
+ # }]
767
+ # }
768
+ # })
769
+ #
770
+ # permitted = params.expect(person: [ :name, { pets: [[:name]] } ])
771
+ # permitted.permitted? # => true
772
+ # permitted[:name] # => "Francesco"
773
+ # permitted[:age] # => nil
774
+ # permitted[:pets][0][:name] # => "Purplish"
775
+ # permitted[:pets][0][:category] # => nil
776
+ #
777
+ # An array of permitted scalars may be expected with the following:
778
+ #
779
+ # params = ActionController::Parameters.new(tags: ["rails", "parameters"])
780
+ # permitted = params.expect(tags: [])
781
+ # permitted.permitted? # => true
782
+ # permitted.is_a?(Array) # => true
783
+ # permitted.size # => 2
784
+ #
785
+ def expect(*filters)
786
+ params = permit_filters(filters)
787
+ keys = filters.flatten.flat_map { |f| f.is_a?(Hash) ? f.keys : f }
788
+ values = params.require(keys)
789
+ values.size == 1 ? values.first : values
790
+ end
661
791
 
662
- params.permit!
792
+ # Same as `expect`, but raises an `ActionController::ExpectedParameterMissing`
793
+ # instead of `ActionController::ParameterMissing`. Unlike `expect` which
794
+ # will render a 400 response, `expect!` will raise an exception that is
795
+ # not handled. This is intended for debugging invalid params for an
796
+ # internal API where incorrectly formatted params would indicate a bug
797
+ # in a client library that should be fixed.
798
+ #
799
+ def expect!(*filters)
800
+ expect(*filters)
801
+ rescue ParameterMissing => e
802
+ raise ExpectedParameterMissing.new(e.param, e.keys)
663
803
  end
664
804
 
665
805
  # Returns a parameter for the given `key`. If not found, returns `nil`.
@@ -686,7 +826,7 @@ module ActionController
686
826
  #
687
827
  # params = ActionController::Parameters.new(person: { name: "Francesco" })
688
828
  # params.fetch(:person) # => #<ActionController::Parameters {"name"=>"Francesco"} permitted: false>
689
- # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
829
+ # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: none
690
830
  # params.fetch(:none, {}) # => #<ActionController::Parameters {} permitted: false>
691
831
  # params.fetch(:none, "Francesco") # => "Francesco"
692
832
  # params.fetch(:none) { "Francesco" } # => "Francesco"
@@ -999,6 +1139,26 @@ module ActionController
999
1139
  hash
1000
1140
  end
1001
1141
 
1142
+ # Filters self and optionally checks for unpermitted keys
1143
+ def permit_filters(filters, on_unpermitted: nil, explicit_arrays: true)
1144
+ params = self.class.new
1145
+
1146
+ filters.flatten.each do |filter|
1147
+ case filter
1148
+ when Symbol, String
1149
+ # Declaration [:name, "age"]
1150
+ permitted_scalar_filter(params, filter)
1151
+ when Hash
1152
+ # Declaration [{ person: ... }]
1153
+ hash_filter(params, filter, on_unpermitted:, explicit_arrays:)
1154
+ end
1155
+ end
1156
+
1157
+ unpermitted_parameters!(params, on_unpermitted:)
1158
+
1159
+ params.permit!
1160
+ end
1161
+
1002
1162
  private
1003
1163
  def new_instance_with_inherited_permitted_status(hash)
1004
1164
  self.class.new(hash, @logging_context).tap do |new_instance|
@@ -1088,23 +1248,45 @@ module ActionController
1088
1248
  end
1089
1249
  end
1090
1250
 
1091
- def each_element(object, filter, &block)
1251
+ # When an array is expected, you must specify an array explicitly
1252
+ # using the following format:
1253
+ #
1254
+ # params.expect(comments: [[:flavor]])
1255
+ #
1256
+ # Which will match only the following array formats:
1257
+ #
1258
+ # { pies: [{ flavor: "rhubarb" }, { flavor: "apple" }] }
1259
+ # { pies: { "0" => { flavor: "key lime" }, "1" => { flavor: "mince" } } }
1260
+ #
1261
+ # When using `permit`, arrays are specified the same way as hashes:
1262
+ #
1263
+ # params.expect(pies: [:flavor])
1264
+ #
1265
+ # In this case, `permit` would also allow matching with a hash (or vice versa):
1266
+ #
1267
+ # { pies: { flavor: "cherry" } }
1268
+ #
1269
+ def array_filter?(filter)
1270
+ filter.is_a?(Array) && filter.size == 1 && filter.first.is_a?(Array)
1271
+ end
1272
+
1273
+ # Called when an explicit array filter is encountered.
1274
+ def each_array_element(object, filter, &block)
1092
1275
  case object
1093
1276
  when Array
1094
1277
  object.grep(Parameters).filter_map(&block)
1095
1278
  when Parameters
1096
1279
  if object.nested_attributes? && !specify_numeric_keys?(filter)
1097
1280
  object.each_nested_attribute(&block)
1098
- else
1099
- yield object
1100
1281
  end
1101
1282
  end
1102
1283
  end
1103
1284
 
1104
- def unpermitted_parameters!(params)
1285
+ def unpermitted_parameters!(params, on_unpermitted: self.class.action_on_unpermitted_parameters)
1286
+ return unless on_unpermitted
1105
1287
  unpermitted_keys = unpermitted_keys(params)
1106
1288
  if unpermitted_keys.any?
1107
- case self.class.action_on_unpermitted_parameters
1289
+ case on_unpermitted
1108
1290
  when :log
1109
1291
  name = "unpermitted_parameters.action_controller"
1110
1292
  ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys, context: @logging_context)
@@ -1118,6 +1300,9 @@ module ActionController
1118
1300
  keys - params.keys - always_permitted_parameters
1119
1301
  end
1120
1302
 
1303
+ #
1304
+ # --- Filtering ----------------------------------------------------------
1305
+ #
1121
1306
  # This is a list of permitted scalar types that includes the ones supported in
1122
1307
  # XML and JSON requests.
1123
1308
  #
@@ -1171,45 +1356,63 @@ module ActionController
1171
1356
  end
1172
1357
  end
1173
1358
 
1174
- def array_of_permitted_scalars?(value)
1175
- if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
1176
- yield value
1177
- end
1178
- end
1179
-
1180
1359
  def non_scalar?(value)
1181
1360
  value.is_a?(Array) || value.is_a?(Parameters)
1182
1361
  end
1183
1362
 
1184
1363
  EMPTY_ARRAY = [] # :nodoc:
1185
1364
  EMPTY_HASH = {} # :nodoc:
1186
- def hash_filter(params, filter)
1365
+ def hash_filter(params, filter, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
1187
1366
  filter = filter.with_indifferent_access
1188
1367
 
1189
1368
  # Slicing filters out non-declared keys.
1190
1369
  slice(*filter.keys).each do |key, value|
1191
1370
  next unless value
1192
1371
  next unless has_key? key
1372
+ result = permit_value(value, filter[key], on_unpermitted:, explicit_arrays:)
1373
+ params[key] = result unless result.nil?
1374
+ end
1375
+ end
1193
1376
 
1194
- if filter[key] == EMPTY_ARRAY
1195
- # Declaration { comment_ids: [] }.
1196
- array_of_permitted_scalars?(self[key]) do |val|
1197
- params[key] = val
1198
- end
1199
- elsif filter[key] == EMPTY_HASH
1200
- # Declaration { preferences: {} }.
1201
- if value.is_a?(Parameters)
1202
- params[key] = permit_any_in_parameters(value)
1203
- end
1204
- elsif non_scalar?(value)
1205
- # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
1206
- params[key] = each_element(value, filter[key]) do |element|
1207
- element.permit(*Array.wrap(filter[key]))
1208
- end
1209
- end
1377
+ def permit_value(value, filter, on_unpermitted:, explicit_arrays:)
1378
+ if filter == EMPTY_ARRAY # Declaration { comment_ids: [] }.
1379
+ permit_array_of_scalars(value)
1380
+ elsif filter == EMPTY_HASH # Declaration { preferences: {} }.
1381
+ permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1382
+ elsif array_filter?(filter) # Declaration { comments: [[:text]] }
1383
+ permit_array_of_hashes(value, filter.first, on_unpermitted:, explicit_arrays:)
1384
+ elsif explicit_arrays # Declaration { user: { address: ... } } or { user: [:name, ...] } (only allows hash value)
1385
+ permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1386
+ elsif non_scalar?(value) # Declaration { user: { address: ... } } or { user: [:name, ...] }
1387
+ permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
1210
1388
  end
1211
1389
  end
1212
1390
 
1391
+ def permit_array_of_scalars(value)
1392
+ value if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
1393
+ end
1394
+
1395
+ def permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:)
1396
+ each_array_element(value, filter) do |element|
1397
+ element.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
1398
+ end
1399
+ end
1400
+
1401
+ def permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1402
+ return unless value.is_a?(Parameters)
1403
+
1404
+ if filter == EMPTY_HASH
1405
+ permit_any_in_parameters(value)
1406
+ else
1407
+ value.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
1408
+ end
1409
+ end
1410
+
1411
+ def permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
1412
+ permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:) ||
1413
+ permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1414
+ end
1415
+
1213
1416
  def permit_any_in_parameters(params)
1214
1417
  self.class.new.tap do |sanitized|
1215
1418
  params.each do |key, value|
@@ -1284,7 +1487,7 @@ module ActionController
1284
1487
  # # list between create and update. Also, you can specialize this method
1285
1488
  # # with per-user checking of permissible attributes.
1286
1489
  # def person_params
1287
- # params.require(:person).permit(:name, :age)
1490
+ # params.expect(person: [:name, :age])
1288
1491
  # end
1289
1492
  # end
1290
1493
  #
@@ -1311,11 +1514,12 @@ module ActionController
1311
1514
  # # It's mandatory to specify the nested attributes that should be permitted.
1312
1515
  # # If you use `permit` with just the key that points to the nested attributes hash,
1313
1516
  # # it will return an empty hash.
1314
- # params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ])
1517
+ # params.expect(person: [ :name, :age, pets_attributes: [ :id, :name, :category ] ])
1315
1518
  # end
1316
1519
  # end
1317
1520
  #
1318
- # See ActionController::Parameters.require and
1521
+ # See ActionController::Parameters.expect,
1522
+ # See ActionController::Parameters.require, and
1319
1523
  # ActionController::Parameters.permit for more information.
1320
1524
  module StrongParameters
1321
1525
  # Returns a new ActionController::Parameters object that has been instantiated
@@ -123,7 +123,7 @@ module ActionController
123
123
  app.config.active_record.query_log_tags |= [:action]
124
124
 
125
125
  ActiveSupport.on_load(:active_record) do
126
- ActiveRecord::QueryLogs.taggings.merge!(
126
+ ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
127
127
  controller: ->(context) { context[:controller]&.controller_name },
128
128
  action: ->(context) { context[:controller]&.action_name },
129
129
  namespaced_controller: ->(context) {
@@ -96,6 +96,7 @@ module ActionController
96
96
  # * `:script_name` - The portion of the incoming request's URL path that
97
97
  # corresponds to the application. Converts to Rack's `SCRIPT_NAME`.
98
98
  # * `:input` - The input stream. Converts to Rack's `rack.input`.
99
+ #
99
100
  # * `defaults` - Default values for the Rack env. Entries are specified in the
100
101
  # same format as `env`. `env` will be merged on top of these values.
101
102
  # `defaults` will be retained when calling #new on a renderer instance.
@@ -22,6 +22,8 @@ module ActionController
22
22
  # database on the main thread, so they could open a txn, then the controller
23
23
  # thread will open a new connection and try to access data that's only visible
24
24
  # to the main thread's txn. This is the problem in #23483.
25
+ alias_method :original_new_controller_thread, :new_controller_thread
26
+
25
27
  silence_redefinition_of_method :new_controller_thread
26
28
  def new_controller_thread # :nodoc:
27
29
  yield