actionpack 7.2.1.1 → 8.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +86 -115
- data/lib/action_controller/api.rb +1 -0
- 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 -1
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +274 -73
- data/lib/action_controller/railtie.rb +1 -1
- data/lib/action_controller/test_case.rb +4 -3
- data/lib/action_dispatch/http/cache.rb +27 -10
- data/lib/action_dispatch/http/content_security_policy.rb +1 -0
- data/lib/action_dispatch/http/filter_parameters.rb +4 -9
- data/lib/action_dispatch/http/filter_redirect.rb +2 -9
- data/lib/action_dispatch/http/permissions_policy.rb +2 -0
- data/lib/action_dispatch/http/request.rb +4 -2
- 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/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/railtie.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +26 -17
- data/lib/action_dispatch/routing/route_set.rb +18 -6
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +12 -34
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -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
|
-
#
|
82
|
-
#
|
83
|
-
#
|
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.
|
94
|
-
# permitted
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
526
|
+
# It is recommended to use `expect` instead:
|
514
527
|
#
|
515
528
|
# def person_params
|
516
|
-
# 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(
|
540
|
-
# permitted = params.
|
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
|
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.
|
636
|
+
# params.permit(person: :contact).require(:person)
|
604
637
|
# # => #<ActionController::Parameters {} permitted: true>
|
605
638
|
#
|
606
|
-
# params.
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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)
|
@@ -1174,45 +1356,63 @@ module ActionController
|
|
1174
1356
|
end
|
1175
1357
|
end
|
1176
1358
|
|
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
1359
|
def non_scalar?(value)
|
1184
1360
|
value.is_a?(Array) || value.is_a?(Parameters)
|
1185
1361
|
end
|
1186
1362
|
|
1187
1363
|
EMPTY_ARRAY = [] # :nodoc:
|
1188
1364
|
EMPTY_HASH = {} # :nodoc:
|
1189
|
-
def hash_filter(params, filter)
|
1365
|
+
def hash_filter(params, filter, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
|
1190
1366
|
filter = filter.with_indifferent_access
|
1191
1367
|
|
1192
1368
|
# Slicing filters out non-declared keys.
|
1193
1369
|
slice(*filter.keys).each do |key, value|
|
1194
1370
|
next unless value
|
1195
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
|
1196
1376
|
|
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
|
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:)
|
1213
1388
|
end
|
1214
1389
|
end
|
1215
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
|
+
|
1216
1416
|
def permit_any_in_parameters(params)
|
1217
1417
|
self.class.new.tap do |sanitized|
|
1218
1418
|
params.each do |key, value|
|
@@ -1287,7 +1487,7 @@ module ActionController
|
|
1287
1487
|
# # list between create and update. Also, you can specialize this method
|
1288
1488
|
# # with per-user checking of permissible attributes.
|
1289
1489
|
# def person_params
|
1290
|
-
# params.
|
1490
|
+
# params.expect(person: [:name, :age])
|
1291
1491
|
# end
|
1292
1492
|
# end
|
1293
1493
|
#
|
@@ -1314,11 +1514,12 @@ module ActionController
|
|
1314
1514
|
# # It's mandatory to specify the nested attributes that should be permitted.
|
1315
1515
|
# # If you use `permit` with just the key that points to the nested attributes hash,
|
1316
1516
|
# # it will return an empty hash.
|
1317
|
-
# params.
|
1517
|
+
# params.expect(person: [ :name, :age, pets_attributes: [ :id, :name, :category ] ])
|
1318
1518
|
# end
|
1319
1519
|
# end
|
1320
1520
|
#
|
1321
|
-
# See ActionController::Parameters.
|
1521
|
+
# See ActionController::Parameters.expect,
|
1522
|
+
# See ActionController::Parameters.require, and
|
1322
1523
|
# ActionController::Parameters.permit for more information.
|
1323
1524
|
module StrongParameters
|
1324
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) {
|
@@ -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
|
@@ -427,9 +429,7 @@ module ActionController
|
|
427
429
|
# Note that the request method is not verified. The different methods are
|
428
430
|
# available to make the tests more expressive.
|
429
431
|
def get(action, **args)
|
430
|
-
|
431
|
-
cookies.update res.cookies
|
432
|
-
res
|
432
|
+
process(action, method: "GET", **args)
|
433
433
|
end
|
434
434
|
|
435
435
|
# Simulate a POST request with the given parameters and set/volley the response.
|
@@ -637,6 +637,7 @@ module ActionController
|
|
637
637
|
unless @request.cookie_jar.committed?
|
638
638
|
@request.cookie_jar.write(@response)
|
639
639
|
cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
|
640
|
+
cookies.update(@response.cookies)
|
640
641
|
end
|
641
642
|
end
|
642
643
|
@response.prepare!
|
@@ -9,6 +9,8 @@ module ActionDispatch
|
|
9
9
|
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
|
10
10
|
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
|
11
11
|
|
12
|
+
mattr_accessor :strict_freshness, default: false
|
13
|
+
|
12
14
|
def if_modified_since
|
13
15
|
if since = get_header(HTTP_IF_MODIFIED_SINCE)
|
14
16
|
Time.rfc2822(since) rescue nil
|
@@ -34,19 +36,32 @@ module ActionDispatch
|
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
|
-
# Check response freshness (`Last-Modified` and ETag) against request
|
38
|
-
# `If-Modified-Since` and `If-None-Match` conditions.
|
39
|
-
# supplied,
|
39
|
+
# Check response freshness (`Last-Modified` and `ETag`) against request
|
40
|
+
# `If-Modified-Since` and `If-None-Match` conditions.
|
41
|
+
# If both headers are supplied, based on configuration, either `ETag` is preferred over `Last-Modified`
|
42
|
+
# or both are considered equally. You can adjust the preference with
|
43
|
+
# `config.action_dispatch.strict_freshness`.
|
44
|
+
# Reference: http://tools.ietf.org/html/rfc7232#section-6
|
40
45
|
def fresh?(response)
|
41
|
-
|
42
|
-
|
46
|
+
if Request.strict_freshness
|
47
|
+
if if_none_match
|
48
|
+
etag_matches?(response.etag)
|
49
|
+
elsif if_modified_since
|
50
|
+
not_modified?(response.last_modified)
|
51
|
+
else
|
52
|
+
false
|
53
|
+
end
|
54
|
+
else
|
55
|
+
last_modified = if_modified_since
|
56
|
+
etag = if_none_match
|
43
57
|
|
44
|
-
|
58
|
+
return false unless last_modified || etag
|
45
59
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
60
|
+
success = true
|
61
|
+
success &&= not_modified?(response.last_modified) if last_modified
|
62
|
+
success &&= etag_matches?(response.etag) if etag
|
63
|
+
success
|
64
|
+
end
|
50
65
|
end
|
51
66
|
end
|
52
67
|
|
@@ -171,6 +186,7 @@ module ActionDispatch
|
|
171
186
|
PUBLIC = "public"
|
172
187
|
PRIVATE = "private"
|
173
188
|
MUST_REVALIDATE = "must-revalidate"
|
189
|
+
IMMUTABLE = "immutable"
|
174
190
|
|
175
191
|
def handle_conditional_get!
|
176
192
|
# Normally default cache control setting is handled by ETag middleware. But, if
|
@@ -221,6 +237,7 @@ module ActionDispatch
|
|
221
237
|
options << MUST_REVALIDATE if control[:must_revalidate]
|
222
238
|
options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
|
223
239
|
options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
|
240
|
+
options << IMMUTABLE if control[:immutable]
|
224
241
|
options.concat(extras) if extras
|
225
242
|
end
|
226
243
|
|
@@ -68,17 +68,12 @@ module ActionDispatch
|
|
68
68
|
ActiveSupport::ParameterFilter.new(filters)
|
69
69
|
end
|
70
70
|
|
71
|
+
KV_RE = "[^&;=]+"
|
72
|
+
PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
|
71
73
|
def filtered_query_string # :doc:
|
72
|
-
|
73
|
-
|
74
|
-
if part.include?("=")
|
75
|
-
key, value = part.split("=", 2)
|
76
|
-
parameter_filter.filter(key => value).first.join("=")
|
77
|
-
else
|
78
|
-
part
|
79
|
-
end
|
74
|
+
query_string.gsub(PAIR_RE) do |_|
|
75
|
+
parameter_filter.filter($1 => $2).first.join("=")
|
80
76
|
end
|
81
|
-
filtered_parts.join("")
|
82
77
|
end
|
83
78
|
end
|
84
79
|
end
|