actionpack 7.2.3 → 8.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -187
- data/README.rdoc +1 -1
- data/lib/abstract_controller/base.rb +12 -1
- data/lib/abstract_controller/collector.rb +1 -1
- data/lib/abstract_controller/helpers.rb +1 -3
- data/lib/action_controller/metal/allow_browser.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +5 -1
- data/lib/action_controller/metal/http_authentication.rb +4 -1
- data/lib/action_controller/metal/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +11 -3
- data/lib/action_controller/metal/params_wrapper.rb +3 -3
- data/lib/action_controller/metal/rate_limiting.rb +13 -4
- data/lib/action_controller/metal/redirecting.rb +3 -4
- data/lib/action_controller/metal/renderers.rb +2 -1
- data/lib/action_controller/metal/rendering.rb +1 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +1 -3
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +277 -73
- data/lib/action_controller/railtie.rb +1 -1
- data/lib/action_controller/renderer.rb +1 -0
- data/lib/action_controller/test_case.rb +2 -0
- data/lib/action_dispatch/constants.rb +0 -6
- data/lib/action_dispatch/http/cache.rb +27 -10
- data/lib/action_dispatch/http/content_security_policy.rb +13 -25
- 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/mime_negotiation.rb +3 -8
- data/lib/action_dispatch/http/permissions_policy.rb +2 -0
- data/lib/action_dispatch/http/request.rb +7 -6
- data/lib/action_dispatch/http/response.rb +1 -15
- data/lib/action_dispatch/http/url.rb +2 -2
- data/lib/action_dispatch/journey/formatter.rb +3 -8
- data/lib/action_dispatch/journey/gtg/transition_table.rb +4 -4
- 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 +17 -6
- data/lib/action_dispatch/middleware/exception_wrapper.rb +3 -3
- data/lib/action_dispatch/middleware/executor.rb +2 -5
- data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/ssl.rb +13 -3
- data/lib/action_dispatch/railtie.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +30 -22
- data/lib/action_dispatch/routing/route_set.rb +18 -6
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +3 -2
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_dispatch/testing/test_process.rb +2 -1
- data/lib/action_dispatch.rb +0 -4
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +16 -49
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
|
@@ -165,95 +165,16 @@ module ActionController # :nodoc:
|
|
|
165
165
|
#
|
|
166
166
|
# ## Web server support
|
|
167
167
|
#
|
|
168
|
-
#
|
|
169
|
-
# instructions for each of them.
|
|
170
|
-
#
|
|
171
|
-
# #### Unicorn
|
|
172
|
-
#
|
|
173
|
-
# Unicorn supports streaming but it needs to be configured. For this, you need
|
|
174
|
-
# to create a config file as follow:
|
|
175
|
-
#
|
|
176
|
-
# # unicorn.config.rb
|
|
177
|
-
# listen 3000, tcp_nopush: false
|
|
178
|
-
#
|
|
179
|
-
# And use it on initialization:
|
|
180
|
-
#
|
|
181
|
-
# unicorn_rails --config-file unicorn.config.rb
|
|
182
|
-
#
|
|
183
|
-
# You may also want to configure other parameters like `:tcp_nodelay`.
|
|
184
|
-
#
|
|
185
|
-
# For more information, please check the
|
|
186
|
-
# [documentation](https://bogomips.org/unicorn/Unicorn/Configurator.html#method-
|
|
187
|
-
# i-listen).
|
|
188
|
-
#
|
|
189
|
-
# If you are using Unicorn with NGINX, you may need to tweak NGINX. Streaming
|
|
190
|
-
# should work out of the box on Rainbows.
|
|
191
|
-
#
|
|
192
|
-
# #### Passenger
|
|
193
|
-
#
|
|
194
|
-
# Phusion Passenger with NGINX, offers two streaming mechanisms out of the box.
|
|
195
|
-
#
|
|
196
|
-
# 1. NGINX response buffering mechanism which is dependent on the value of
|
|
197
|
-
# `passenger_buffer_response` option (default is "off").
|
|
198
|
-
# 2. Passenger buffering system which is always 'on' irrespective of the value
|
|
199
|
-
# of `passenger_buffer_response`.
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
# When `passenger_buffer_response` is turned "on", then streaming would be done
|
|
203
|
-
# at the NGINX level which waits until the application is done sending the
|
|
204
|
-
# response back to the client.
|
|
205
|
-
#
|
|
206
|
-
# For more information, please check the [documentation]
|
|
207
|
-
# (https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response).
|
|
168
|
+
# Rack 3+ compatible servers all support streaming.
|
|
208
169
|
module Streaming
|
|
209
|
-
class Body # :nodoc:
|
|
210
|
-
TERM = "\r\n"
|
|
211
|
-
TAIL = "0#{TERM}"
|
|
212
|
-
|
|
213
|
-
# Store the response body to be chunked.
|
|
214
|
-
def initialize(body)
|
|
215
|
-
@body = body
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# For each element yielded by the response body, yield the element in chunked
|
|
219
|
-
# encoding.
|
|
220
|
-
def each(&block)
|
|
221
|
-
term = TERM
|
|
222
|
-
@body.each do |chunk|
|
|
223
|
-
size = chunk.bytesize
|
|
224
|
-
next if size == 0
|
|
225
|
-
|
|
226
|
-
yield [size.to_s(16), term, chunk.b, term].join
|
|
227
|
-
end
|
|
228
|
-
yield TAIL
|
|
229
|
-
yield term
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Close the response body if the response body supports it.
|
|
233
|
-
def close
|
|
234
|
-
@body.close if @body.respond_to?(:close)
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
170
|
private
|
|
239
|
-
# Set proper cache control and transfer encoding when streaming
|
|
240
|
-
def _process_options(options)
|
|
241
|
-
super
|
|
242
|
-
if options[:stream]
|
|
243
|
-
if request.version == "HTTP/1.0"
|
|
244
|
-
options.delete(:stream)
|
|
245
|
-
else
|
|
246
|
-
headers["Cache-Control"] ||= "no-cache"
|
|
247
|
-
headers["Transfer-Encoding"] = "chunked"
|
|
248
|
-
headers.delete("Content-Length")
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
|
|
253
171
|
# Call render_body if we are streaming instead of usual `render`.
|
|
254
172
|
def _render_template(options)
|
|
255
173
|
if options.delete(:stream)
|
|
256
|
-
|
|
174
|
+
# It shoudn't be necessary to set this.
|
|
175
|
+
headers["cache-control"] ||= "no-cache"
|
|
176
|
+
|
|
177
|
+
view_renderer.render_body(view_context, options)
|
|
257
178
|
else
|
|
258
179
|
super
|
|
259
180
|
end
|
|
@@ -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)
|
|
@@ -1118,6 +1300,9 @@ module ActionController
|
|
|
1118
1300
|
keys - params.keys - always_permitted_parameters
|
|
1119
1301
|
end
|
|
1120
1302
|
|
|
1303
|
+
#
|
|
1304
|
+
# --- Filtering ----------------------------------------------------------
|
|
1305
|
+
#
|
|
1121
1306
|
# This is a list of permitted scalar types that includes the ones supported in
|
|
1122
1307
|
# XML and JSON requests.
|
|
1123
1308
|
#
|
|
@@ -1171,45 +1356,63 @@ module ActionController
|
|
|
1171
1356
|
end
|
|
1172
1357
|
end
|
|
1173
1358
|
|
|
1174
|
-
def array_of_permitted_scalars?(value)
|
|
1175
|
-
if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
|
|
1176
|
-
yield value
|
|
1177
|
-
end
|
|
1178
|
-
end
|
|
1179
|
-
|
|
1180
1359
|
def non_scalar?(value)
|
|
1181
1360
|
value.is_a?(Array) || value.is_a?(Parameters)
|
|
1182
1361
|
end
|
|
1183
1362
|
|
|
1184
1363
|
EMPTY_ARRAY = [] # :nodoc:
|
|
1185
1364
|
EMPTY_HASH = {} # :nodoc:
|
|
1186
|
-
def hash_filter(params, filter)
|
|
1365
|
+
def hash_filter(params, filter, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false)
|
|
1187
1366
|
filter = filter.with_indifferent_access
|
|
1188
1367
|
|
|
1189
1368
|
# Slicing filters out non-declared keys.
|
|
1190
1369
|
slice(*filter.keys).each do |key, value|
|
|
1191
1370
|
next unless value
|
|
1192
1371
|
next unless has_key? key
|
|
1372
|
+
result = permit_value(value, filter[key], on_unpermitted:, explicit_arrays:)
|
|
1373
|
+
params[key] = result unless result.nil?
|
|
1374
|
+
end
|
|
1375
|
+
end
|
|
1193
1376
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
|
|
1206
|
-
params[key] = each_element(value, filter[key]) do |element|
|
|
1207
|
-
element.permit(*Array.wrap(filter[key]))
|
|
1208
|
-
end
|
|
1209
|
-
end
|
|
1377
|
+
def permit_value(value, filter, on_unpermitted:, explicit_arrays:)
|
|
1378
|
+
if filter == EMPTY_ARRAY # Declaration { comment_ids: [] }.
|
|
1379
|
+
permit_array_of_scalars(value)
|
|
1380
|
+
elsif filter == EMPTY_HASH # Declaration { preferences: {} }.
|
|
1381
|
+
permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
|
1382
|
+
elsif array_filter?(filter) # Declaration { comments: [[:text]] }
|
|
1383
|
+
permit_array_of_hashes(value, filter.first, on_unpermitted:, explicit_arrays:)
|
|
1384
|
+
elsif explicit_arrays # Declaration { user: { address: ... } } or { user: [:name, ...] } (only allows hash value)
|
|
1385
|
+
permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
|
1386
|
+
elsif non_scalar?(value) # Declaration { user: { address: ... } } or { user: [:name, ...] }
|
|
1387
|
+
permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
|
|
1210
1388
|
end
|
|
1211
1389
|
end
|
|
1212
1390
|
|
|
1391
|
+
def permit_array_of_scalars(value)
|
|
1392
|
+
value if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
|
|
1393
|
+
end
|
|
1394
|
+
|
|
1395
|
+
def permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:)
|
|
1396
|
+
each_array_element(value, filter) do |element|
|
|
1397
|
+
element.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
|
|
1398
|
+
end
|
|
1399
|
+
end
|
|
1400
|
+
|
|
1401
|
+
def permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
|
1402
|
+
return unless value.is_a?(Parameters)
|
|
1403
|
+
|
|
1404
|
+
if filter == EMPTY_HASH
|
|
1405
|
+
permit_any_in_parameters(value)
|
|
1406
|
+
else
|
|
1407
|
+
value.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:)
|
|
1408
|
+
end
|
|
1409
|
+
end
|
|
1410
|
+
|
|
1411
|
+
def permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:)
|
|
1412
|
+
permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:) ||
|
|
1413
|
+
permit_hash(value, filter, on_unpermitted:, explicit_arrays:)
|
|
1414
|
+
end
|
|
1415
|
+
|
|
1213
1416
|
def permit_any_in_parameters(params)
|
|
1214
1417
|
self.class.new.tap do |sanitized|
|
|
1215
1418
|
params.each do |key, value|
|
|
@@ -1284,7 +1487,7 @@ module ActionController
|
|
|
1284
1487
|
# # list between create and update. Also, you can specialize this method
|
|
1285
1488
|
# # with per-user checking of permissible attributes.
|
|
1286
1489
|
# def person_params
|
|
1287
|
-
# params.
|
|
1490
|
+
# params.expect(person: [:name, :age])
|
|
1288
1491
|
# end
|
|
1289
1492
|
# end
|
|
1290
1493
|
#
|
|
@@ -1311,11 +1514,12 @@ module ActionController
|
|
|
1311
1514
|
# # It's mandatory to specify the nested attributes that should be permitted.
|
|
1312
1515
|
# # If you use `permit` with just the key that points to the nested attributes hash,
|
|
1313
1516
|
# # it will return an empty hash.
|
|
1314
|
-
# params.
|
|
1517
|
+
# params.expect(person: [ :name, :age, pets_attributes: [ :id, :name, :category ] ])
|
|
1315
1518
|
# end
|
|
1316
1519
|
# end
|
|
1317
1520
|
#
|
|
1318
|
-
# See ActionController::Parameters.
|
|
1521
|
+
# See ActionController::Parameters.expect,
|
|
1522
|
+
# See ActionController::Parameters.require, and
|
|
1319
1523
|
# ActionController::Parameters.permit for more information.
|
|
1320
1524
|
module StrongParameters
|
|
1321
1525
|
# Returns a new ActionController::Parameters object that has been instantiated
|
|
@@ -123,7 +123,7 @@ module ActionController
|
|
|
123
123
|
app.config.active_record.query_log_tags |= [:action]
|
|
124
124
|
|
|
125
125
|
ActiveSupport.on_load(:active_record) do
|
|
126
|
-
ActiveRecord::QueryLogs.taggings.merge
|
|
126
|
+
ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
|
|
127
127
|
controller: ->(context) { context[:controller]&.controller_name },
|
|
128
128
|
action: ->(context) { context[:controller]&.action_name },
|
|
129
129
|
namespaced_controller: ->(context) {
|
|
@@ -96,6 +96,7 @@ module ActionController
|
|
|
96
96
|
# * `:script_name` - The portion of the incoming request's URL path that
|
|
97
97
|
# corresponds to the application. Converts to Rack's `SCRIPT_NAME`.
|
|
98
98
|
# * `:input` - The input stream. Converts to Rack's `rack.input`.
|
|
99
|
+
#
|
|
99
100
|
# * `defaults` - Default values for the Rack env. Entries are specified in the
|
|
100
101
|
# same format as `env`. `env` will be merged on top of these values.
|
|
101
102
|
# `defaults` will be retained when calling #new on a renderer instance.
|
|
@@ -22,6 +22,8 @@ module ActionController
|
|
|
22
22
|
# database on the main thread, so they could open a txn, then the controller
|
|
23
23
|
# thread will open a new connection and try to access data that's only visible
|
|
24
24
|
# to the main thread's txn. This is the problem in #23483.
|
|
25
|
+
alias_method :original_new_controller_thread, :new_controller_thread
|
|
26
|
+
|
|
25
27
|
silence_redefinition_of_method :new_controller_thread
|
|
26
28
|
def new_controller_thread # :nodoc:
|
|
27
29
|
yield
|