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