actionpack 7.2.2.1 → 8.1.2

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.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +408 -95
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/asset_paths.rb +4 -2
  5. data/lib/abstract_controller/base.rb +12 -17
  6. data/lib/abstract_controller/caching.rb +6 -3
  7. data/lib/abstract_controller/callbacks.rb +6 -0
  8. data/lib/abstract_controller/collector.rb +1 -1
  9. data/lib/abstract_controller/helpers.rb +1 -1
  10. data/lib/abstract_controller/logger.rb +2 -1
  11. data/lib/abstract_controller/rendering.rb +0 -1
  12. data/lib/action_controller/api.rb +1 -0
  13. data/lib/action_controller/base.rb +3 -2
  14. data/lib/action_controller/caching.rb +1 -2
  15. data/lib/action_controller/form_builder.rb +4 -4
  16. data/lib/action_controller/log_subscriber.rb +22 -3
  17. data/lib/action_controller/metal/allow_browser.rb +12 -2
  18. data/lib/action_controller/metal/conditional_get.rb +30 -1
  19. data/lib/action_controller/metal/data_streaming.rb +5 -5
  20. data/lib/action_controller/metal/exceptions.rb +5 -0
  21. data/lib/action_controller/metal/flash.rb +1 -4
  22. data/lib/action_controller/metal/head.rb +3 -1
  23. data/lib/action_controller/metal/instrumentation.rb +1 -2
  24. data/lib/action_controller/metal/live.rb +66 -26
  25. data/lib/action_controller/metal/params_wrapper.rb +3 -3
  26. data/lib/action_controller/metal/permissions_policy.rb +9 -0
  27. data/lib/action_controller/metal/rate_limiting.rb +39 -9
  28. data/lib/action_controller/metal/redirecting.rb +109 -16
  29. data/lib/action_controller/metal/renderers.rb +29 -9
  30. data/lib/action_controller/metal/rendering.rb +8 -2
  31. data/lib/action_controller/metal/request_forgery_protection.rb +21 -11
  32. data/lib/action_controller/metal/rescue.rb +9 -0
  33. data/lib/action_controller/metal/streaming.rb +5 -84
  34. data/lib/action_controller/metal/strong_parameters.rb +277 -92
  35. data/lib/action_controller/railtie.rb +33 -15
  36. data/lib/action_controller/renderer.rb +0 -1
  37. data/lib/action_controller/structured_event_subscriber.rb +116 -0
  38. data/lib/action_controller/test_case.rb +12 -2
  39. data/lib/action_dispatch/constants.rb +6 -0
  40. data/lib/action_dispatch/http/cache.rb +138 -11
  41. data/lib/action_dispatch/http/content_security_policy.rb +14 -1
  42. data/lib/action_dispatch/http/filter_parameters.rb +5 -3
  43. data/lib/action_dispatch/http/mime_negotiation.rb +63 -4
  44. data/lib/action_dispatch/http/mime_types.rb +1 -0
  45. data/lib/action_dispatch/http/param_builder.rb +187 -0
  46. data/lib/action_dispatch/http/param_error.rb +26 -0
  47. data/lib/action_dispatch/http/parameters.rb +3 -3
  48. data/lib/action_dispatch/http/permissions_policy.rb +6 -0
  49. data/lib/action_dispatch/http/query_parser.rb +55 -0
  50. data/lib/action_dispatch/http/request.rb +73 -23
  51. data/lib/action_dispatch/http/response.rb +65 -17
  52. data/lib/action_dispatch/http/url.rb +112 -16
  53. data/lib/action_dispatch/journey/formatter.rb +8 -3
  54. data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
  55. data/lib/action_dispatch/journey/gtg/transition_table.rb +37 -45
  56. data/lib/action_dispatch/journey/nodes/node.rb +2 -1
  57. data/lib/action_dispatch/journey/parser.rb +99 -196
  58. data/lib/action_dispatch/journey/route.rb +45 -31
  59. data/lib/action_dispatch/journey/router/utils.rb +8 -14
  60. data/lib/action_dispatch/journey/router.rb +59 -81
  61. data/lib/action_dispatch/journey/routes.rb +7 -0
  62. data/lib/action_dispatch/journey/scanner.rb +44 -42
  63. data/lib/action_dispatch/journey/visitors.rb +55 -23
  64. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  65. data/lib/action_dispatch/log_subscriber.rb +7 -3
  66. data/lib/action_dispatch/middleware/cookies.rb +8 -4
  67. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -5
  68. data/lib/action_dispatch/middleware/debug_view.rb +11 -5
  69. data/lib/action_dispatch/middleware/exception_wrapper.rb +14 -14
  70. data/lib/action_dispatch/middleware/executor.rb +17 -4
  71. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -6
  72. data/lib/action_dispatch/middleware/remote_ip.rb +11 -5
  73. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  74. data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
  75. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  76. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  77. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
  78. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  79. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  80. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
  82. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
  83. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  84. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  89. data/lib/action_dispatch/railtie.rb +21 -0
  90. data/lib/action_dispatch/request/session.rb +1 -0
  91. data/lib/action_dispatch/request/utils.rb +9 -3
  92. data/lib/action_dispatch/routing/inspector.rb +80 -57
  93. data/lib/action_dispatch/routing/mapper.rb +409 -228
  94. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  95. data/lib/action_dispatch/routing/redirection.rb +10 -7
  96. data/lib/action_dispatch/routing/route_set.rb +21 -12
  97. data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
  98. data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
  99. data/lib/action_dispatch/system_test_case.rb +3 -3
  100. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  101. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
  102. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  103. data/lib/action_dispatch/testing/assertions/response.rb +26 -2
  104. data/lib/action_dispatch/testing/assertions/routing.rb +27 -15
  105. data/lib/action_dispatch/testing/integration.rb +16 -7
  106. data/lib/action_dispatch/testing/request_encoder.rb +9 -9
  107. data/lib/action_dispatch/testing/test_process.rb +1 -2
  108. data/lib/action_dispatch.rb +14 -4
  109. data/lib/action_pack/gem_version.rb +3 -3
  110. metadata +19 -38
  111. data/lib/action_dispatch/journey/parser.y +0 -50
  112. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -10,7 +10,6 @@ require "active_support/deep_mergeable"
10
10
  require "action_dispatch/http/upload"
11
11
  require "rack/test"
12
12
  require "stringio"
13
- require "set"
14
13
  require "yaml"
15
14
 
16
15
  module ActionController
@@ -18,16 +17,18 @@ module ActionController
18
17
  #
19
18
  # params = ActionController::Parameters.new(a: {})
20
19
  # params.fetch(:b)
21
- # # => ActionController::ParameterMissing: param is missing or the value is empty: b
20
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: b
22
21
  # params.require(:a)
23
- # # => ActionController::ParameterMissing: param is missing or the value is empty: a
22
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a
23
+ # params.expect(a: [])
24
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a
24
25
  class ParameterMissing < KeyError
25
26
  attr_reader :param, :keys # :nodoc:
26
27
 
27
28
  def initialize(param, keys = nil) # :nodoc:
28
29
  @param = param
29
30
  @keys = keys
30
- super("param is missing or the value is empty: #{param}")
31
+ super("param is missing or the value is empty or invalid: #{param}")
31
32
  end
32
33
 
33
34
  if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
@@ -39,6 +40,15 @@ module ActionController
39
40
  end
40
41
  end
41
42
 
43
+ # Raised from `expect!` when an expected parameter is missing or is of an
44
+ # incompatible type.
45
+ #
46
+ # params = ActionController::Parameters.new(a: {})
47
+ # params.expect!(:a)
48
+ # # => ActionController::ExpectedParameterMissing: param is missing or the value is empty or invalid: a
49
+ class ExpectedParameterMissing < ParameterMissing
50
+ end
51
+
42
52
  # Raised when a supplied parameter is not expected and
43
53
  # ActionController::Parameters.action_on_unpermitted_parameters is set to
44
54
  # `:raise`.
@@ -78,9 +88,14 @@ module ActionController
78
88
  #
79
89
  # Allows you to choose which attributes should be permitted for mass updating
80
90
  # and thus prevent accidentally exposing that which shouldn't be exposed.
81
- # 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.
91
+ #
92
+ # Provides methods for filtering and requiring params:
93
+ #
94
+ # * `expect` to safely permit and require parameters in one step.
95
+ # * `permit` to filter params for mass assignment.
96
+ # * `require` to require a parameter or raise an error.
97
+ #
98
+ # Examples:
84
99
  #
85
100
  # params = ActionController::Parameters.new({
86
101
  # person: {
@@ -90,14 +105,14 @@ module ActionController
90
105
  # }
91
106
  # })
92
107
  #
93
- # permitted = params.require(:person).permit(:name, :age)
94
- # permitted # => #<ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
95
- # permitted.permitted? # => true
108
+ # permitted = params.expect(person: [:name, :age])
109
+ # permitted # => #<ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
96
110
  #
97
111
  # Person.first.update!(permitted)
98
112
  # # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
99
113
  #
100
- # It provides two options that controls the top-level behavior of new instances:
114
+ # Parameters provides two options that control the top-level behavior of new
115
+ # instances:
101
116
  #
102
117
  # * `permit_all_parameters` - If it's `true`, all the parameters will be
103
118
  # permitted by default. The default is `false`.
@@ -112,8 +127,6 @@ module ActionController
112
127
  # * `:raise` to raise an ActionController::UnpermittedParameters
113
128
  # exception.
114
129
  #
115
- #
116
- #
117
130
  # Examples:
118
131
  #
119
132
  # params = ActionController::Parameters.new
@@ -250,20 +263,6 @@ module ActionController
250
263
  cattr_accessor :always_permitted_parameters, default: %w( controller action )
251
264
 
252
265
  class << self
253
- def allow_deprecated_parameters_hash_equality
254
- ActionController.deprecator.warn <<-WARNING.squish
255
- `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality` is
256
- deprecated and will be removed in Rails 8.0.
257
- WARNING
258
- end
259
-
260
- def allow_deprecated_parameters_hash_equality=(value)
261
- ActionController.deprecator.warn <<-WARNING.squish
262
- `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`
263
- is deprecated and will be removed in Rails 8.0.
264
- WARNING
265
- end
266
-
267
266
  def nested_attribute?(key, value) # :nodoc:
268
267
  /\A-?\d+\z/.match?(key) && (value.is_a?(Hash) || value.is_a?(Parameters))
269
268
  end
@@ -481,16 +480,16 @@ module ActionController
481
480
  # Otherwise raises ActionController::ParameterMissing:
482
481
  #
483
482
  # ActionController::Parameters.new.require(:person)
484
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
483
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
485
484
  #
486
485
  # ActionController::Parameters.new(person: nil).require(:person)
487
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
486
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
488
487
  #
489
488
  # ActionController::Parameters.new(person: "\t").require(:person)
490
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
489
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
491
490
  #
492
491
  # ActionController::Parameters.new(person: {}).require(:person)
493
- # # ActionController::ParameterMissing: param is missing or the value is empty: person
492
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
494
493
  #
495
494
  # When given an array of keys, the method tries to require each one of them in
496
495
  # order. If it succeeds, an array with the respective return values is returned:
@@ -502,23 +501,21 @@ module ActionController
502
501
  #
503
502
  # params = ActionController::Parameters.new(user: {}, profile: {})
504
503
  # user_params, profile_params = params.require([:user, :profile])
505
- # # ActionController::ParameterMissing: param is missing or the value is empty: user
504
+ # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
506
505
  #
507
- # Technically this method can be used to fetch terminal values:
506
+ # This method is not recommended for fetching terminal values because it does
507
+ # not permit the values. For example, this can cause problems:
508
508
  #
509
509
  # # CAREFUL
510
510
  # params = ActionController::Parameters.new(person: { name: "Finn" })
511
511
  # name = params.require(:person).require(:name) # CAREFUL
512
512
  #
513
- # but take into account that at some point those ones have to be permitted:
513
+ # It is recommended to use `expect` instead:
514
514
  #
515
515
  # def person_params
516
- # params.require(:person).permit(:name).tap do |person_params|
517
- # person_params.require(:name) # SAFER
518
- # end
516
+ # params.expect(person: :name).require(:name)
519
517
  # end
520
518
  #
521
- # for example.
522
519
  def require(key)
523
520
  return key.map { |k| require(k) } if key.is_a?(Array)
524
521
  value = self[key]
@@ -536,8 +533,8 @@ module ActionController
536
533
  # This is useful for limiting which attributes should be allowed for mass
537
534
  # updating.
538
535
  #
539
- # params = ActionController::Parameters.new(user: { name: "Francesco", age: 22, role: "admin" })
540
- # permitted = params.require(:user).permit(:name, :age)
536
+ # params = ActionController::Parameters.new(name: "Francesco", age: 22, role: "admin")
537
+ # permitted = params.permit(:name, :age)
541
538
  # permitted.permitted? # => true
542
539
  # permitted.has_key?(:name) # => true
543
540
  # permitted.has_key?(:age) # => true
@@ -567,7 +564,7 @@ module ActionController
567
564
  # `permit` ensures values in the returned structure are permitted scalars and
568
565
  # filters out anything else.
569
566
  #
570
- # You can also use `permit` on nested parameters, like:
567
+ # You can also use `permit` on nested parameters:
571
568
  #
572
569
  # params = ActionController::Parameters.new({
573
570
  # person: {
@@ -587,6 +584,29 @@ module ActionController
587
584
  # permitted[:person][:pets][0][:name] # => "Purplish"
588
585
  # permitted[:person][:pets][0][:category] # => nil
589
586
  #
587
+ # This has the added benefit of rejecting user-modified inputs that send a
588
+ # string when a hash is expected.
589
+ #
590
+ # When followed by `require`, you can both filter and require parameters
591
+ # following the typical pattern of a Rails form. The `expect` method was
592
+ # made specifically for this use case and is the recommended way to require
593
+ # and permit parameters.
594
+ #
595
+ # permitted = params.expect(person: [:name, :age])
596
+ #
597
+ # When using `permit` and `require` separately, pay careful attention to the
598
+ # order of the method calls.
599
+ #
600
+ # params = ActionController::Parameters.new(person: { name: "Martin", age: 40, role: "admin" })
601
+ # permitted = params.permit(person: [:name, :age]).require(:person) # correct
602
+ #
603
+ # When require is used first, it is possible for users of your application to
604
+ # trigger a NoMethodError when the user, for example, sends a string for :person.
605
+ #
606
+ # params = ActionController::Parameters.new(person: "tampered")
607
+ # permitted = params.require(:person).permit(:name, :age) # not recommended
608
+ # # => NoMethodError: undefined method `permit' for an instance of String
609
+ #
590
610
  # Note that if you use `permit` in a key that points to a hash, it won't allow
591
611
  # all the hash. You also need to specify which attributes inside the hash should
592
612
  # be permitted.
@@ -600,13 +620,13 @@ module ActionController
600
620
  # }
601
621
  # })
602
622
  #
603
- # params.require(:person).permit(:contact)
604
- # # => #<ActionController::Parameters {} permitted: true>
623
+ # params.permit(person: :contact).require(:person)
624
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: person
605
625
  #
606
- # params.require(:person).permit(contact: :phone)
626
+ # params.permit(person: { contact: :phone }).require(:person)
607
627
  # # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
608
628
  #
609
- # params.require(:person).permit(contact: [ :email, :phone ])
629
+ # params.permit(person: { contact: [ :email, :phone ] }).require(:person)
610
630
  # # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
611
631
  #
612
632
  # If your parameters specify multiple parameters indexed by a number, you can
@@ -646,20 +666,127 @@ module ActionController
646
666
  # params.permit(person: { '0': [:email], '1': [:phone]}).to_h
647
667
  # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"phone"=>"555-6789"}}}
648
668
  def permit(*filters)
649
- 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
669
+ permit_filters(filters, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
670
+ end
659
671
 
660
- unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
672
+ # `expect` is the preferred way to require and permit parameters.
673
+ # It is safer than the previous recommendation to call `permit` and `require`
674
+ # in sequence, which could allow user triggered 500 errors.
675
+ #
676
+ # `expect` is more strict with types to avoid a number of potential pitfalls
677
+ # that may be encountered with the `.require.permit` pattern.
678
+ #
679
+ # For example:
680
+ #
681
+ # params = ActionController::Parameters.new(comment: { text: "hello" })
682
+ # params.expect(comment: [:text])
683
+ # # => #<ActionController::Parameters { text: "hello" } permitted: true>
684
+ #
685
+ # params = ActionController::Parameters.new(comment: [{ text: "hello" }, { text: "world" }])
686
+ # params.expect(comment: [:text])
687
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comment
688
+ #
689
+ # In order to permit an array of parameters, the array must be defined
690
+ # explicitly. Use double array brackets, an array inside an array, to
691
+ # declare that an array of parameters is expected.
692
+ #
693
+ # params = ActionController::Parameters.new(comments: [{ text: "hello" }, { text: "world" }])
694
+ # params.expect(comments: [[:text]])
695
+ # # => [#<ActionController::Parameters { "text" => "hello" } permitted: true>,
696
+ # # #<ActionController::Parameters { "text" => "world" } permitted: true>]
697
+ #
698
+ # params = ActionController::Parameters.new(comments: { text: "hello" })
699
+ # params.expect(comments: [[:text]])
700
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comments
701
+ #
702
+ # `expect` is intended to protect against array tampering.
703
+ #
704
+ # params = ActionController::Parameters.new(user: "hack")
705
+ # # The previous way of requiring and permitting parameters will error
706
+ # params.require(:user).permit(:name, pets: [:name]) # wrong
707
+ # # => NoMethodError: undefined method `permit' for an instance of String
708
+ #
709
+ # # similarly with nested parameters
710
+ # params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
711
+ # user_params = params.require(:user).permit(:name, pets: [:name]) # wrong
712
+ # # user_params[:pets] is expected to be an array but is a hash
713
+ #
714
+ # `expect` solves this by being more strict with types.
715
+ #
716
+ # params = ActionController::Parameters.new(user: "hack")
717
+ # params.expect(user: [ :name, pets: [[:name]] ])
718
+ # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: user
719
+ #
720
+ # # with nested parameters
721
+ # params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } })
722
+ # user_params = params.expect(user: [:name, pets: [[:name]] ])
723
+ # user_params[:pets] # => nil
724
+ #
725
+ # As the examples show, `expect` requires the `:user` key, and any root keys
726
+ # similar to the `.require.permit` pattern. If multiple root keys are
727
+ # expected, they will all be required.
728
+ #
729
+ # params = ActionController::Parameters.new(name: "Martin", pies: [{ type: "dessert", flavor: "pumpkin"}])
730
+ # name, pies = params.expect(:name, pies: [[:type, :flavor]])
731
+ # name # => "Martin"
732
+ # pies # => [#<ActionController::Parameters {"type"=>"dessert", "flavor"=>"pumpkin"} permitted: true>]
733
+ #
734
+ # When called with a hash with multiple keys, `expect` will permit the
735
+ # parameters and require the keys in the order they are given in the hash,
736
+ # returning an array of the permitted parameters.
737
+ #
738
+ # params = ActionController::Parameters.new(subject: { name: "Martin" }, object: { pie: "pumpkin" })
739
+ # subject, object = params.expect(subject: [:name], object: [:pie])
740
+ # subject # => #<ActionController::Parameters {"name"=>"Martin"} permitted: true>
741
+ # object # => #<ActionController::Parameters {"pie"=>"pumpkin"} permitted: true>
742
+ #
743
+ # Besides being more strict about array vs hash params, `expect` uses permit
744
+ # internally, so it will behave similarly.
745
+ #
746
+ # params = ActionController::Parameters.new({
747
+ # person: {
748
+ # name: "Francesco",
749
+ # age: 22,
750
+ # pets: [{
751
+ # name: "Purplish",
752
+ # category: "dogs"
753
+ # }]
754
+ # }
755
+ # })
756
+ #
757
+ # permitted = params.expect(person: [ :name, { pets: [[:name]] } ])
758
+ # permitted.permitted? # => true
759
+ # permitted[:name] # => "Francesco"
760
+ # permitted[:age] # => nil
761
+ # permitted[:pets][0][:name] # => "Purplish"
762
+ # permitted[:pets][0][:category] # => nil
763
+ #
764
+ # An array of permitted scalars may be expected with the following:
765
+ #
766
+ # params = ActionController::Parameters.new(tags: ["rails", "parameters"])
767
+ # permitted = params.expect(tags: [])
768
+ # permitted # => ["rails", "parameters"]
769
+ # permitted.is_a?(Array) # => true
770
+ # permitted.size # => 2
771
+ #
772
+ def expect(*filters)
773
+ params = permit_filters(filters)
774
+ keys = filters.flatten.flat_map { |f| f.is_a?(Hash) ? f.keys : f }
775
+ values = params.require(keys)
776
+ values.size == 1 ? values.first : values
777
+ end
661
778
 
662
- params.permit!
779
+ # Same as `expect`, but raises an `ActionController::ExpectedParameterMissing`
780
+ # instead of `ActionController::ParameterMissing`. Unlike `expect` which
781
+ # will render a 400 response, `expect!` will raise an exception that is
782
+ # not handled. This is intended for debugging invalid params for an
783
+ # internal API where incorrectly formatted params would indicate a bug
784
+ # in a client library that should be fixed.
785
+ #
786
+ def expect!(*filters)
787
+ expect(*filters)
788
+ rescue ParameterMissing => e
789
+ raise ExpectedParameterMissing.new(e.param, e.keys)
663
790
  end
664
791
 
665
792
  # Returns a parameter for the given `key`. If not found, returns `nil`.
@@ -686,7 +813,7 @@ module ActionController
686
813
  #
687
814
  # params = ActionController::Parameters.new(person: { name: "Francesco" })
688
815
  # params.fetch(:person) # => #<ActionController::Parameters {"name"=>"Francesco"} permitted: false>
689
- # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
816
+ # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: none
690
817
  # params.fetch(:none, {}) # => #<ActionController::Parameters {} permitted: false>
691
818
  # params.fetch(:none, "Francesco") # => "Francesco"
692
819
  # params.fetch(:none) { "Francesco" } # => "Francesco"
@@ -999,6 +1126,26 @@ module ActionController
999
1126
  hash
1000
1127
  end
1001
1128
 
1129
+ # Filters self and optionally checks for unpermitted keys
1130
+ def permit_filters(filters, on_unpermitted: nil, explicit_arrays: true)
1131
+ params = self.class.new
1132
+
1133
+ filters.flatten.each do |filter|
1134
+ case filter
1135
+ when Symbol, String
1136
+ # Declaration [:name, "age"]
1137
+ permitted_scalar_filter(params, filter)
1138
+ when Hash
1139
+ # Declaration [{ person: ... }]
1140
+ hash_filter(params, filter, on_unpermitted:, explicit_arrays:)
1141
+ end
1142
+ end
1143
+
1144
+ unpermitted_parameters!(params, on_unpermitted:)
1145
+
1146
+ params.permit!
1147
+ end
1148
+
1002
1149
  private
1003
1150
  def new_instance_with_inherited_permitted_status(hash)
1004
1151
  self.class.new(hash, @logging_context).tap do |new_instance|
@@ -1088,23 +1235,45 @@ module ActionController
1088
1235
  end
1089
1236
  end
1090
1237
 
1091
- def each_element(object, filter, &block)
1238
+ # When an array is expected, you must specify an array explicitly
1239
+ # using the following format:
1240
+ #
1241
+ # params.expect(comments: [[:flavor]])
1242
+ #
1243
+ # Which will match only the following array formats:
1244
+ #
1245
+ # { pies: [{ flavor: "rhubarb" }, { flavor: "apple" }] }
1246
+ # { pies: { "0" => { flavor: "key lime" }, "1" => { flavor: "mince" } } }
1247
+ #
1248
+ # When using `permit`, arrays are specified the same way as hashes:
1249
+ #
1250
+ # params.expect(pies: [:flavor])
1251
+ #
1252
+ # In this case, `permit` would also allow matching with a hash (or vice versa):
1253
+ #
1254
+ # { pies: { flavor: "cherry" } }
1255
+ #
1256
+ def array_filter?(filter)
1257
+ filter.is_a?(Array) && filter.size == 1 && filter.first.is_a?(Array)
1258
+ end
1259
+
1260
+ # Called when an explicit array filter is encountered.
1261
+ def each_array_element(object, filter, &block)
1092
1262
  case object
1093
1263
  when Array
1094
1264
  object.grep(Parameters).filter_map(&block)
1095
1265
  when Parameters
1096
1266
  if object.nested_attributes? && !specify_numeric_keys?(filter)
1097
1267
  object.each_nested_attribute(&block)
1098
- else
1099
- yield object
1100
1268
  end
1101
1269
  end
1102
1270
  end
1103
1271
 
1104
- def unpermitted_parameters!(params)
1272
+ def unpermitted_parameters!(params, on_unpermitted: self.class.action_on_unpermitted_parameters)
1273
+ return unless on_unpermitted
1105
1274
  unpermitted_keys = unpermitted_keys(params)
1106
1275
  if unpermitted_keys.any?
1107
- case self.class.action_on_unpermitted_parameters
1276
+ case on_unpermitted
1108
1277
  when :log
1109
1278
  name = "unpermitted_parameters.action_controller"
1110
1279
  ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys, context: @logging_context)
@@ -1118,9 +1287,6 @@ module ActionController
1118
1287
  keys - params.keys - always_permitted_parameters
1119
1288
  end
1120
1289
 
1121
- #
1122
- # --- Filtering ----------------------------------------------------------
1123
- #
1124
1290
  # This is a list of permitted scalar types that includes the ones supported in
1125
1291
  # XML and JSON requests.
1126
1292
  #
@@ -1174,45 +1340,63 @@ module ActionController
1174
1340
  end
1175
1341
  end
1176
1342
 
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
1343
  def non_scalar?(value)
1184
1344
  value.is_a?(Array) || value.is_a?(Parameters)
1185
1345
  end
1186
1346
 
1187
1347
  EMPTY_ARRAY = [] # :nodoc:
1188
1348
  EMPTY_HASH = {} # :nodoc:
1189
- def hash_filter(params, filter)
1349
+ def hash_filter(params, filter, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
1190
1350
  filter = filter.with_indifferent_access
1191
1351
 
1192
1352
  # Slicing filters out non-declared keys.
1193
1353
  slice(*filter.keys).each do |key, value|
1194
1354
  next unless value
1195
1355
  next unless has_key? key
1356
+ result = permit_value(value, filter[key], on_unpermitted:, explicit_arrays:)
1357
+ params[key] = result unless result.nil?
1358
+ end
1359
+ end
1196
1360
 
1197
- if filter[key] == EMPTY_ARRAY
1198
- # Declaration { comment_ids: [] }.
1199
- array_of_permitted_scalars?(self[key]) do |val|
1200
- params[key] = val
1201
- end
1202
- elsif filter[key] == EMPTY_HASH
1203
- # Declaration { preferences: {} }.
1204
- if value.is_a?(Parameters)
1205
- params[key] = permit_any_in_parameters(value)
1206
- end
1207
- elsif non_scalar?(value)
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
1361
+ def permit_value(value, filter, on_unpermitted:, explicit_arrays:)
1362
+ if filter == EMPTY_ARRAY # Declaration { comment_ids: [] }.
1363
+ permit_array_of_scalars(value)
1364
+ elsif filter == EMPTY_HASH # Declaration { preferences: {} }.
1365
+ permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1366
+ elsif array_filter?(filter) # Declaration { comments: [[:text]] }
1367
+ permit_array_of_hashes(value, filter.first, on_unpermitted:, explicit_arrays:)
1368
+ elsif explicit_arrays # Declaration { user: { address: ... } } or { user: [:name, ...] } (only allows hash value)
1369
+ permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1370
+ elsif non_scalar?(value) # Declaration { user: { address: ... } } or { user: [:name, ...] }
1371
+ permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
1213
1372
  end
1214
1373
  end
1215
1374
 
1375
+ def permit_array_of_scalars(value)
1376
+ value if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
1377
+ end
1378
+
1379
+ def permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:)
1380
+ each_array_element(value, filter) do |element|
1381
+ element.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
1382
+ end
1383
+ end
1384
+
1385
+ def permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1386
+ return unless value.is_a?(Parameters)
1387
+
1388
+ if filter == EMPTY_HASH
1389
+ permit_any_in_parameters(value)
1390
+ else
1391
+ value.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
1392
+ end
1393
+ end
1394
+
1395
+ def permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
1396
+ permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:) ||
1397
+ permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
1398
+ end
1399
+
1216
1400
  def permit_any_in_parameters(params)
1217
1401
  self.class.new.tap do |sanitized|
1218
1402
  params.each do |key, value|
@@ -1287,7 +1471,7 @@ module ActionController
1287
1471
  # # list between create and update. Also, you can specialize this method
1288
1472
  # # with per-user checking of permissible attributes.
1289
1473
  # def person_params
1290
- # params.require(:person).permit(:name, :age)
1474
+ # params.expect(person: [:name, :age])
1291
1475
  # end
1292
1476
  # end
1293
1477
  #
@@ -1314,11 +1498,12 @@ module ActionController
1314
1498
  # # It's mandatory to specify the nested attributes that should be permitted.
1315
1499
  # # If you use `permit` with just the key that points to the nested attributes hash,
1316
1500
  # # it will return an empty hash.
1317
- # params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ])
1501
+ # params.expect(person: [ :name, :age, pets_attributes: [ :id, :name, :category ] ])
1318
1502
  # end
1319
1503
  # end
1320
1504
  #
1321
- # See ActionController::Parameters.require and
1505
+ # See ActionController::Parameters.expect,
1506
+ # See ActionController::Parameters.require, and
1322
1507
  # ActionController::Parameters.permit for more information.
1323
1508
  module StrongParameters
1324
1509
  # Returns a new ActionController::Parameters object that has been instantiated
@@ -12,9 +12,11 @@ require "action_view/railtie"
12
12
  module ActionController
13
13
  class Railtie < Rails::Railtie # :nodoc:
14
14
  config.action_controller = ActiveSupport::OrderedOptions.new
15
- config.action_controller.raise_on_open_redirects = false
15
+ config.action_controller.action_on_open_redirect = :log
16
+ config.action_controller.action_on_path_relative_redirect = :log
16
17
  config.action_controller.log_query_tags_around_actions = true
17
18
  config.action_controller.wrap_parameters_by_default = false
19
+ config.action_controller.allowed_redirect_hosts = []
18
20
 
19
21
  config.eager_load_namespaces << AbstractController
20
22
  config.eager_load_namespaces << ActionController
@@ -31,6 +33,10 @@ module ActionController
31
33
  ActionController::Helpers.helpers_path = app.helpers_paths
32
34
  end
33
35
 
36
+ initializer "action_controller.live_streaming_excluded_keys" do |app|
37
+ ActionController::Live.live_streaming_excluded_keys = app.config.action_controller.live_streaming_excluded_keys
38
+ end
39
+
34
40
  initializer "action_controller.parameters_config" do |app|
35
41
  options = app.config.action_controller
36
42
 
@@ -48,11 +54,6 @@ module ActionController
48
54
  end
49
55
 
50
56
  ActionController::Parameters.action_on_unpermitted_parameters = action_on_unpermitted_parameters
51
-
52
- unless options.allow_deprecated_parameters_hash_equality.nil?
53
- ActionController::Parameters.allow_deprecated_parameters_hash_equality =
54
- options.allow_deprecated_parameters_hash_equality
55
- end
56
57
  end
57
58
  end
58
59
 
@@ -60,7 +61,8 @@ module ActionController
60
61
  paths = app.config.paths
61
62
  options = app.config.action_controller
62
63
 
63
- options.logger ||= Rails.logger
64
+ options.logger = options.fetch(:logger, Rails.logger)
65
+
64
66
  options.cache_store ||= Rails.cache
65
67
 
66
68
  options.javascripts_dir ||= paths["public/javascripts"].first
@@ -85,7 +87,7 @@ module ActionController
85
87
  :action_on_unpermitted_parameters,
86
88
  :always_permitted_parameters,
87
89
  :wrap_parameters_by_default,
88
- :allow_deprecated_parameters_hash_equality
90
+ :live_streaming_excluded_keys
89
91
  )
90
92
 
91
93
  filtered_options.each do |k, v|
@@ -99,12 +101,6 @@ module ActionController
99
101
  end
100
102
  end
101
103
 
102
- initializer "action_controller.compile_config_methods" do
103
- ActiveSupport.on_load(:action_controller) do
104
- config.compile_methods! if config.respond_to?(:compile_methods!)
105
- end
106
- end
107
-
108
104
  initializer "action_controller.request_forgery_protection" do |app|
109
105
  ActiveSupport.on_load(:action_controller_base) do
110
106
  if app.config.action_controller.default_protect_from_forgery
@@ -113,6 +109,22 @@ module ActionController
113
109
  end
114
110
  end
115
111
 
112
+ initializer "action_controller.open_redirects" do |app|
113
+ ActiveSupport.on_load(:action_controller, run_once: true) do
114
+ if app.config.action_controller.has_key?(:raise_on_open_redirects)
115
+ ActiveSupport.deprecator.warn(<<~MSG.squish)
116
+ `raise_on_open_redirects` is deprecated and will be removed in a future Rails version.
117
+ Use `config.action_controller.action_on_open_redirect = :raise` instead.
118
+ MSG
119
+
120
+ # Fallback to the default behavior in case of `load_default` set `action_on_open_redirect`, but apps set `raise_on_open_redirects`.
121
+ if app.config.action_controller.raise_on_open_redirects == false && app.config.action_controller.action_on_open_redirect == :raise
122
+ self.action_on_open_redirect = :log
123
+ end
124
+ end
125
+ end
126
+ end
127
+
116
128
  initializer "action_controller.query_log_tags" do |app|
117
129
  query_logs_tags_enabled = app.config.respond_to?(:active_record) &&
118
130
  app.config.active_record.query_log_tags_enabled &&
@@ -123,7 +135,7 @@ module ActionController
123
135
  app.config.active_record.query_log_tags |= [:action]
124
136
 
125
137
  ActiveSupport.on_load(:active_record) do
126
- ActiveRecord::QueryLogs.taggings.merge!(
138
+ ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
127
139
  controller: ->(context) { context[:controller]&.controller_name },
128
140
  action: ->(context) { context[:controller]&.action_name },
129
141
  namespaced_controller: ->(context) {
@@ -145,5 +157,11 @@ module ActionController
145
157
  ActionController::TestCase.executor_around_each_request = app.config.active_support.executor_around_test_case
146
158
  end
147
159
  end
160
+
161
+ initializer "action_controller.backtrace_cleaner" do
162
+ ActiveSupport.on_load(:action_controller) do
163
+ ActionController::LogSubscriber.backtrace_cleaner = Rails.backtrace_cleaner
164
+ end
165
+ end
148
166
  end
149
167
  end