strong_parameters.rb 32.6 KB
Newer Older
1 2 3 4
require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/hash/transform_values"
require "active_support/core_ext/array/wrap"
require "active_support/core_ext/string/filters"
5
require "active_support/core_ext/object/to_query"
6 7 8 9 10 11
require "active_support/rescuable"
require "action_dispatch/http/upload"
require "rack/test"
require "stringio"
require "set"
require "yaml"
12

13
module ActionController
14 15 16 17
  # Raised when a required parameter is missing.
  #
  #   params = ActionController::Parameters.new(a: {})
  #   params.fetch(:b)
18
  #   # => ActionController::ParameterMissing: param is missing or the value is empty: b
19
  #   params.require(:a)
20
  #   # => ActionController::ParameterMissing: param is missing or the value is empty: a
21
  class ParameterMissing < KeyError
22
    attr_reader :param # :nodoc:
23

24
    def initialize(param) # :nodoc:
25
      @param = param
26
      super("param is missing or the value is empty: #{param}")
27 28 29
    end
  end

30 31
  # Raised when a supplied parameter is not expected and
  # ActionController::Parameters.action_on_unpermitted_parameters
32
  # is set to <tt>:raise</tt>.
33 34 35
  #
  #   params = ActionController::Parameters.new(a: "123", b: "456")
  #   params.permit(:c)
36
  #   # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b
37 38
  class UnpermittedParameters < IndexError
    attr_reader :params # :nodoc:
39

40
    def initialize(params) # :nodoc:
41
      @params = params
42
      super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.map { |e| ":#{e}" }.join(", ")}")
43 44 45
    end
  end

46
  # == Action Controller \Parameters
47
  #
T
Tom Kadwill 已提交
48
  # Allows you to choose which attributes should be whitelisted for mass updating
49
  # and thus prevent accidentally exposing that which shouldn't be exposed.
50 51
  # Provides two methods for this purpose: #require and #permit. The former is
  # used to mark parameters as required. The latter is used to set the parameter
52
  # as permitted and limit which attributes should be allowed for mass updating.
53 54 55 56 57 58 59 60 61 62
  #
  #   params = ActionController::Parameters.new({
  #     person: {
  #       name: 'Francesco',
  #       age:  22,
  #       role: 'admin'
  #     }
  #   })
  #
  #   permitted = params.require(:person).permit(:name, :age)
63
  #   permitted            # => <ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
64 65
  #   permitted.permitted? # => true
  #
66
  #   Person.first.update!(permitted)
67
  #   # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
68
  #
69 70 71 72 73 74 75 76 77
  # It provides two options that controls the top-level behavior of new instances:
  #
  # * +permit_all_parameters+ - If it's +true+, all the parameters will be
  #   permitted by default. The default is +false+.
  # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters
  #   that are not explicitly permitted are found. The values can be <tt>:log</tt> to
  #   write a message on the logger or <tt>:raise</tt> to raise
  #   ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
  #   in test and development environments, +false+ otherwise.
78
  #
79 80
  # Examples:
  #
81
  #   params = ActionController::Parameters.new
82
  #   params.permitted? # => false
83 84 85 86 87 88
  #
  #   ActionController::Parameters.permit_all_parameters = true
  #
  #   params = ActionController::Parameters.new
  #   params.permitted? # => true
  #
89 90
  #   params = ActionController::Parameters.new(a: "123", b: "456")
  #   params.permit(:c)
91
  #   # => <ActionController::Parameters {} permitted: true>
92 93 94 95 96 97 98
  #
  #   ActionController::Parameters.action_on_unpermitted_parameters = :raise
  #
  #   params = ActionController::Parameters.new(a: "123", b: "456")
  #   params.permit(:c)
  #   # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
  #
99 100 101 102
  # Please note that these options *are not thread-safe*. In a multi-threaded
  # environment they should only be set once at boot-time and never mutated at
  # runtime.
  #
103 104
  # You can fetch values of <tt>ActionController::Parameters</tt> using either
  # <tt>:key</tt> or <tt>"key"</tt>.
105 106 107 108
  #
  #   params = ActionController::Parameters.new(key: 'value')
  #   params[:key]  # => "value"
  #   params["key"] # => "value"
109
  class Parameters
110
    cattr_accessor :permit_all_parameters, instance_accessor: false
111 112
    self.permit_all_parameters = false

113 114
    cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false

115
    delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?,
116
      :as_json, to: :@parameters
117

118 119 120 121
    # By default, never raise an UnpermittedParameters exception if these
    # params are present. The default includes both 'controller' and 'action'
    # because they are added by Rails and should be of no concern. One way
    # to change these is to specify `always_permitted_parameters` in your
R
Rafael Chacón 已提交
122 123 124
    # config. For instance:
    #
    #    config.always_permitted_parameters = %w( controller action format )
125 126 127
    cattr_accessor :always_permitted_parameters
    self.always_permitted_parameters = %w( controller action )

128 129 130 131
    # Returns a new instance of <tt>ActionController::Parameters</tt>.
    # Also, sets the +permitted+ attribute to the default value of
    # <tt>ActionController::Parameters.permit_all_parameters</tt>.
    #
U
Uģis Ozols 已提交
132
    #   class Person < ActiveRecord::Base
133 134 135 136
    #   end
    #
    #   params = ActionController::Parameters.new(name: 'Francesco')
    #   params.permitted?  # => false
137
    #   Person.new(params) # => ActiveModel::ForbiddenAttributesError
138 139 140 141
    #
    #   ActionController::Parameters.permit_all_parameters = true
    #
    #   params = ActionController::Parameters.new(name: 'Francesco')
142
    #   params.permitted?  # => true
143
    #   Person.new(params) # => #<Person id: nil, name: "Francesco">
144 145
    def initialize(parameters = {})
      @parameters = parameters.with_indifferent_access
146
      @permitted = self.class.permit_all_parameters
147 148
    end

149
    # Returns true if another +Parameters+ object contains the same content and
150 151 152 153 154 155 156 157 158 159 160 161 162
    # permitted flag.
    def ==(other)
      if other.respond_to?(:permitted?)
        self.permitted? == other.permitted? && self.parameters == other.parameters
      elsif other.is_a?(Hash)
        ActiveSupport::Deprecation.warn <<-WARNING.squish
          Comparing equality between `ActionController::Parameters` and a
          `Hash` is deprecated and will be removed in Rails 5.1. Please only do
          comparisons between instances of `ActionController::Parameters`. If
          you need to compare to a hash, first convert it using
          `ActionController::Parameters#new`.
        WARNING
        @parameters == other.with_indifferent_access
163
      else
164
        @parameters == other
165 166 167
      end
    end

168 169
    # Returns a safe <tt>ActiveSupport::HashWithIndifferentAccess</tt>
    # representation of this parameter with all unpermitted keys removed.
170 171 172 173 174 175 176 177 178 179 180
    #
    #   params = ActionController::Parameters.new({
    #     name: 'Senjougahara Hitagi',
    #     oddity: 'Heavy stone crab'
    #   })
    #   params.to_h # => {}
    #
    #   safe_params = params.permit(:name)
    #   safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
    def to_h
      if permitted?
181
        convert_parameters_to_hashes(@parameters, :to_h)
182 183 184 185 186
      else
        slice(*self.class.always_permitted_parameters).permit!.to_h
      end
    end

187 188 189
    # Returns an unsafe, unfiltered
    # <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
    # parameter.
190 191 192 193 194 195
    #
    #   params = ActionController::Parameters.new({
    #     name: 'Senjougahara Hitagi',
    #     oddity: 'Heavy stone crab'
    #   })
    #   params.to_unsafe_h
196
    #   # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"}
P
Prem Sichanugrist 已提交
197
    def to_unsafe_h
198
      convert_parameters_to_hashes(@parameters, :to_unsafe_h)
P
Prem Sichanugrist 已提交
199 200 201
    end
    alias_method :to_unsafe_hash, :to_unsafe_h

T
Tom Kadwill 已提交
202
    # Convert all hashes in values into parameters, then yield each pair in
203 204
    # the same way as <tt>Hash#each_pair</tt>
    def each_pair(&block)
205 206
      @parameters.each_pair do |key, value|
        yield key, convert_hashes_to_parameters(key, value)
207 208 209 210
      end
    end
    alias_method :each, :each_pair

211 212 213
    # Attribute that keeps track of converted arrays, if any, to avoid double
    # looping in the common use case permit + mass-assignment. Defined in a
    # method to instantiate it only if needed.
214 215 216 217
    #
    # Testing membership still loops, but it's going to be faster than our own
    # loop that converts values. Also, we are not going to build a new array
    # object per fetch.
218
    def converted_arrays
219
      @converted_arrays ||= Set.new
220 221
    end

222 223 224 225 226 227 228 229 230 231
    # Returns +true+ if the parameter is permitted, +false+ otherwise.
    #
    #   params = ActionController::Parameters.new
    #   params.permitted? # => false
    #   params.permit!
    #   params.permitted? # => true
    def permitted?
      @permitted
    end

232 233 234 235 236 237 238
    # Sets the +permitted+ attribute to +true+. This can be used to pass
    # mass assignment. Returns +self+.
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   params = ActionController::Parameters.new(name: 'Francesco')
239
    #   params.permitted?  # => false
240 241 242 243
    #   Person.new(params) # => ActiveModel::ForbiddenAttributesError
    #   params.permit!
    #   params.permitted?  # => true
    #   Person.new(params) # => #<Person id: nil, name: "Francesco">
244
    def permit!
245
      each_pair do |key, value|
C
Corey Ward 已提交
246 247
        Array.wrap(value).each do |v|
          v.permit! if v.respond_to? :permit!
248
        end
249 250
      end

251 252 253 254
      @permitted = true
      self
    end

255 256 257 258
    # This method accepts both a single key and an array of keys.
    #
    # When passed a single key, if it exists and its associated value is
    # either present or the singleton +false+, returns said value:
259
    #
260
    #   ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
261
    #   # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
262
    #
263 264 265 266 267
    # Otherwise raises <tt>ActionController::ParameterMissing</tt>:
    #
    #   ActionController::Parameters.new.require(:person)
    #   # ActionController::ParameterMissing: param is missing or the value is empty: person
    #
268
    #   ActionController::Parameters.new(person: nil).require(:person)
269 270 271 272
    #   # ActionController::ParameterMissing: param is missing or the value is empty: person
    #
    #   ActionController::Parameters.new(person: "\t").require(:person)
    #   # ActionController::ParameterMissing: param is missing or the value is empty: person
273
    #
274
    #   ActionController::Parameters.new(person: {}).require(:person)
275 276 277
    #   # ActionController::ParameterMissing: param is missing or the value is empty: person
    #
    # When given an array of keys, the method tries to require each one of them
X
Xavier Noria 已提交
278
    # in order. If it succeeds, an array with the respective return values is
279 280 281
    # returned:
    #
    #   params = ActionController::Parameters.new(user: { ... }, profile: { ... })
282
    #   user_params, profile_params = params.require([:user, :profile])
283
    #
T
Tom Kadwill 已提交
284
    # Otherwise, the method re-raises the first exception found:
285
    #
286
    #   params = ActionController::Parameters.new(user: {}, profile: {})
287
    #   user_params, profile_params = params.require([:user, :profile])
288 289 290 291 292 293 294 295 296 297
    #   # ActionController::ParameterMissing: param is missing or the value is empty: user
    #
    # Technically this method can be used to fetch terminal values:
    #
    #   # CAREFUL
    #   params = ActionController::Parameters.new(person: { name: 'Finn' })
    #   name = params.require(:person).require(:name) # CAREFUL
    #
    # but take into account that at some point those ones have to be permitted:
    #
298 299 300
    #   def person_params
    #     params.require(:person).permit(:name).tap do |person_params|
    #       person_params.require(:name) # SAFER
301 302
    #     end
    #   end
303
    #
304
    # for example.
305
    def require(key)
X
Xavier Noria 已提交
306
      return key.map { |k| require(k) } if key.is_a?(Array)
307 308 309 310 311 312
      value = self[key]
      if value.present? || value == false
        value
      else
        raise ParameterMissing.new(key)
      end
313 314
    end

315
    # Alias of #require.
316 317
    alias :required :require

318
    # Returns a new <tt>ActionController::Parameters</tt> instance that
319 320
    # includes only the given +filters+ and sets the +permitted+ attribute
    # for the object to +true+. This is useful for limiting which attributes
321 322 323 324
    # should be allowed for mass updating.
    #
    #   params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
    #   permitted = params.require(:user).permit(:name, :age)
325
    #   permitted.permitted?      # => true
326 327 328 329
    #   permitted.has_key?(:name) # => true
    #   permitted.has_key?(:age)  # => true
    #   permitted.has_key?(:role) # => false
    #
330 331 332 333
    # Only permitted scalars pass the filter. For example, given
    #
    #   params.permit(:name)
    #
334
    # +:name+ passes if it is a key of +params+ whose associated value is of type
335
    # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
336 337 338
    # +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
    # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
    # Otherwise, the key +:name+ is filtered out.
339 340 341 342
    #
    # You may declare that the parameter should be an array of permitted scalars
    # by mapping it to an empty array:
    #
343
    #   params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
344
    #   params.permit(tags: [])
345
    #
346 347 348
    # You can also use +permit+ on nested parameters, like:
    #
    #   params = ActionController::Parameters.new({
349
    #     person: {
350 351 352 353 354 355 356 357 358
    #       name: 'Francesco',
    #       age:  22,
    #       pets: [{
    #         name: 'Purplish',
    #         category: 'dogs'
    #       }]
    #     }
    #   })
    #
359
    #   permitted = params.permit(person: [ :name, { pets: :name } ])
360 361
    #   permitted.permitted?                    # => true
    #   permitted[:person][:name]               # => "Francesco"
362
    #   permitted[:person][:age]                # => nil
363 364
    #   permitted[:person][:pets][0][:name]     # => "Purplish"
    #   permitted[:person][:pets][0][:category] # => nil
365 366 367 368 369 370 371 372
    #
    # Note that if you use +permit+ in a key that points to a hash,
    # it won't allow all the hash. You also need to specify which
    # attributes inside the hash should be whitelisted.
    #
    #   params = ActionController::Parameters.new({
    #     person: {
    #       contact: {
I
Ilya Vorontsov 已提交
373
    #         email: 'none@test.com',
374 375 376 377 378 379
    #         phone: '555-1234'
    #       }
    #     }
    #   })
    #
    #   params.require(:person).permit(:contact)
380
    #   # => <ActionController::Parameters {} permitted: true>
381 382
    #
    #   params.require(:person).permit(contact: :phone)
383
    #   # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
384 385
    #
    #   params.require(:person).permit(contact: [ :email, :phone ])
386
    #   # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
387 388 389
    def permit(*filters)
      params = self.class.new

390
      filters.flatten.each do |filter|
391
        case filter
392 393
        when Symbol, String
          permitted_scalar_filter(params, filter)
394
        when Hash then
395
          hash_filter(params, filter)
396 397 398
        end
      end

399
      unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
400

401 402 403
      params.permit!
    end

404 405 406
    # Returns a parameter for the given +key+. If not found,
    # returns +nil+.
    #
407
    #   params = ActionController::Parameters.new(person: { name: 'Francesco' })
408
    #   params[:person] # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
409
    #   params[:none]   # => nil
410
    def [](key)
411 412 413
      convert_hashes_to_parameters(key, @parameters[key])
    end

414 415
    # Assigns a value to a given +key+. The given key may still get filtered out
    # when +permit+ is called.
416 417
    def []=(key, value)
      @parameters[key] = value
418 419
    end

420 421 422 423 424 425 426
    # Returns a parameter for the given +key+. If the +key+
    # can't be found, there are several options: With no other arguments,
    # it will raise an <tt>ActionController::ParameterMissing</tt> error;
    # if more arguments are given, then that will be returned; if a block
    # is given, then that will be run and its result returned.
    #
    #   params = ActionController::Parameters.new(person: { name: 'Francesco' })
427
    #   params.fetch(:person)               # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
428
    #   params.fetch(:none)                 # => ActionController::ParameterMissing: param is missing or the value is empty: none
429
    #   params.fetch(:none, 'Francesco')    # => "Francesco"
430
    #   params.fetch(:none) { 'Francesco' } # => "Francesco"
A
Akira Matsuda 已提交
431
    def fetch(key, *args)
432
      convert_value_to_parameters(
433 434 435 436 437 438
        @parameters.fetch(key) {
          if block_given?
            yield
          else
            args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
          end
439
        }
440
      )
441 442
    end

443 444 445 446
    if Hash.method_defined?(:dig)
      # Extracts the nested parameter from the given +keys+ by calling +dig+
      # at each step. Returns +nil+ if any intermediate step is +nil+.
      #
447 448 449
      #   params = ActionController::Parameters.new(foo: { bar: { baz: 1 } })
      #   params.dig(:foo, :bar, :baz) # => 1
      #   params.dig(:foo, :zot, :xyz) # => nil
450
      #
451 452
      #   params2 = ActionController::Parameters.new(foo: [10, 11, 12])
      #   params2.dig(:foo, 1) # => 11
453 454 455 456 457
      def dig(*keys)
        convert_value_to_parameters(@parameters.dig(*keys))
      end
    end

458 459 460 461 462
    # Returns a new <tt>ActionController::Parameters</tt> instance that
    # includes only the given +keys+. If the given +keys+
    # don't exist, returns an empty hash.
    #
    #   params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
463 464
    #   params.slice(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
    #   params.slice(:d)     # => <ActionController::Parameters {} permitted: false>
465
    def slice(*keys)
466 467 468
      new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
    end

469 470
    # Returns current <tt>ActionController::Parameters</tt> instance which
    # contains only the given +keys+.
471 472 473 474 475
    def slice!(*keys)
      @parameters.slice!(*keys)
      self
    end

476 477 478 479
    # Returns a new <tt>ActionController::Parameters</tt> instance that
    # filters out the given +keys+.
    #
    #   params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
480 481
    #   params.except(:a, :b) # => <ActionController::Parameters {"c"=>3} permitted: false>
    #   params.except(:d)     # => <ActionController::Parameters {"a"=>1, "b"=>2, "c"=>3} permitted: false>
482 483
    def except(*keys)
      new_instance_with_inherited_permitted_status(@parameters.except(*keys))
484 485
    end

486 487 488
    # Removes and returns the key/value pairs matching the given keys.
    #
    #   params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
489 490
    #   params.extract!(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
    #   params                  # => <ActionController::Parameters {"c"=>3} permitted: false>
491
    def extract!(*keys)
492
      new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
493 494 495 496 497 498 499
    end

    # Returns a new <tt>ActionController::Parameters</tt> with the results of
    # running +block+ once for every value. The keys are unchanged.
    #
    #   params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
    #   params.transform_values { |x| x * 2 }
500
    #   # => <ActionController::Parameters {"a"=>2, "b"=>4, "c"=>6} permitted: false>
501 502 503 504 505
    def transform_values(&block)
      if block
        new_instance_with_inherited_permitted_status(
          @parameters.transform_values(&block)
        )
506
      else
507
        @parameters.transform_values
508 509 510
      end
    end

511 512
    # Performs values transformation and returns the altered
    # <tt>ActionController::Parameters</tt> instance.
513 514 515 516 517
    def transform_values!(&block)
      @parameters.transform_values!(&block)
      self
    end

518 519 520
    # Returns a new <tt>ActionController::Parameters</tt> instance with the
    # results of running +block+ once for every key. The values are unchanged.
    def transform_keys(&block)
521 522 523 524
      if block
        new_instance_with_inherited_permitted_status(
          @parameters.transform_keys(&block)
        )
525
      else
526
        @parameters.transform_keys
527 528 529
      end
    end

J
Jon Atack 已提交
530
    # Performs keys transformation and returns the altered
531
    # <tt>ActionController::Parameters</tt> instance.
532 533 534 535 536
    def transform_keys!(&block)
      @parameters.transform_keys!(&block)
      self
    end

537 538 539 540
    # Deletes and returns a key-value pair from +Parameters+ whose key is equal
    # to key. If the key is not found, returns the default value. If the
    # optional code block is given and the key is not found, pass in the key
    # and return the result of block.
A
Akira Matsuda 已提交
541
    def delete(key)
542
      convert_value_to_parameters(@parameters.delete(key))
543 544
    end

545 546
    # Returns a new instance of <tt>ActionController::Parameters</tt> with only
    # items that the block evaluates to true.
547 548
    def select(&block)
      new_instance_with_inherited_permitted_status(@parameters.select(&block))
549 550 551 552
    end

    # Equivalent to Hash#keep_if, but returns nil if no changes were made.
    def select!(&block)
553 554 555 556 557
      @parameters.select!(&block)
      self
    end
    alias_method :keep_if, :select!

558 559
    # Returns a new instance of <tt>ActionController::Parameters</tt> with items
    # that the block evaluates to true removed.
560 561 562 563
    def reject(&block)
      new_instance_with_inherited_permitted_status(@parameters.reject(&block))
    end

564
    # Removes items that the block evaluates to true and returns self.
565 566 567 568 569 570
    def reject!(&block)
      @parameters.reject!(&block)
      self
    end
    alias_method :delete_if, :reject!

571
    # Returns values that were assigned to the given +keys+. Note that all the
572
    # +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
573 574
    def values_at(*keys)
      convert_value_to_parameters(@parameters.values_at(*keys))
575 576
    end

577 578
    # Returns a new <tt>ActionController::Parameters</tt> with all keys from
    # +other_hash+ merges into current hash.
579 580
    def merge(other_hash)
      new_instance_with_inherited_permitted_status(
581
        @parameters.merge(other_hash.to_h)
582 583 584 585
      )
    end

    # This is required by ActiveModel attribute assignment, so that user can
586 587 588
    # pass +Parameters+ to a mass assignment methods in a model. It should not
    # matter as we are using +HashWithIndifferentAccess+ internally.
    def stringify_keys # :nodoc:
589 590 591
      dup
    end

592
    def inspect
593
      "<#{self.class} #{@parameters} permitted: #{@permitted}>"
594 595
    end

596 597 598 599
    def self.hook_into_yaml_loading # :nodoc:
      # Wire up YAML format compatibility with Rails 4.2 and Psych 2.0.8 and 2.0.9+.
      # Makes the YAML parser call `init_with` when it encounters the keys below
      # instead of trying its own parsing routines.
600 601
      YAML.load_tags["!ruby/hash-with-ivars:ActionController::Parameters"] = name
      YAML.load_tags["!ruby/hash:ActionController::Parameters"] = name
602 603 604
    end
    hook_into_yaml_loading

605
    def init_with(coder) # :nodoc:
606
      case coder.tag
607
      when "!ruby/hash:ActionController::Parameters"
608 609 610
        # YAML 2.0.8's format where hash instance variables weren't stored.
        @parameters = coder.map.with_indifferent_access
        @permitted  = false
611
      when "!ruby/hash-with-ivars:ActionController::Parameters"
612
        # YAML 2.0.9's Hash subclass format where keys and values
613
        # were stored under an elements hash and `permitted` within an ivars hash.
614 615 616
        @parameters = coder.map["elements"].with_indifferent_access
        @permitted  = coder.map["ivars"][:@permitted]
      when "!ruby/object:ActionController::Parameters"
617 618
        # YAML's Object format. Only needed because of the format
        # backwardscompability above, otherwise equivalent to YAML's initialization.
619
        @parameters, @permitted = coder.map["parameters"], coder.map["permitted"]
620 621 622
      end
    end

623 624 625 626
    # Undefine `to_param` such that it gets caught in the `method_missing`
    # deprecation cycle below.
    undef_method :to_param

627 628 629
    def method_missing(method_sym, *args, &block)
      if @parameters.respond_to?(method_sym)
        message = <<-DEPRECATE.squish
630
          Method #{method_sym} is deprecated and will be removed in Rails 5.1,
631 632 633
          as `ActionController::Parameters` no longer inherits from
          hash. Using this deprecated behavior exposes potential security
          problems. If you continue to use this method you may be creating
634
          a security vulnerability in your app that can be exploited. Instead,
635 636
          consider using one of these documented methods which are not
          deprecated: http://api.rubyonrails.org/v#{ActionPack.version}/classes/ActionController/Parameters.html
637 638 639 640 641 642 643 644
        DEPRECATE
        ActiveSupport::Deprecation.warn(message)
        @parameters.public_send(method_sym, *args, &block)
      else
        super
      end
    end

645
    protected
646 647
      attr_reader :parameters

648 649 650 651
      def permitted=(new_permitted)
        @permitted = new_permitted
      end

652
      def fields_for_style?
653
        @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) }
654 655
      end

656
    private
657 658 659 660 661 662
      def new_instance_with_inherited_permitted_status(hash)
        self.class.new(hash).tap do |new_instance|
          new_instance.permitted = @permitted
        end
      end

663
      def convert_parameters_to_hashes(value, using)
664 665
        case value
        when Array
666
          value.map { |v| convert_parameters_to_hashes(v, using) }
667 668
        when Hash
          value.transform_values do |v|
669
            convert_parameters_to_hashes(v, using)
670 671
          end.with_indifferent_access
        when Parameters
672
          value.send(using)
673 674 675 676 677
        else
          value
        end
      end

678
      def convert_hashes_to_parameters(key, value)
679
        converted = convert_value_to_parameters(value)
680
        @parameters[key] = converted unless converted.equal?(value)
681 682 683
        converted
      end

684
      def convert_value_to_parameters(value)
685 686 687
        case value
        when Array
          return value if converted_arrays.member?(value)
688 689
          converted = value.map { |_| convert_value_to_parameters(_) }
          converted_arrays << converted
690
          converted
691
        when Hash
692
          self.class.new(value)
693 694
        else
          value
695 696 697 698
        end
      end

      def each_element(object)
A
Aaron Patterson 已提交
699 700 701 702
        case object
        when Array
          object.grep(Parameters).map { |el| yield el }.compact
        when Parameters
703
          if object.fields_for_style?
A
Aaron Patterson 已提交
704 705 706 707 708 709
            hash = object.class.new
            object.each { |k,v| hash[k] = yield v }
            hash
          else
            yield object
          end
710 711
        end
      end
712 713 714 715 716 717

      def unpermitted_parameters!(params)
        unpermitted_keys = unpermitted_keys(params)
        if unpermitted_keys.any?
          case self.class.action_on_unpermitted_parameters
          when :log
718 719
            name = "unpermitted_parameters.action_controller"
            ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
720 721 722 723 724 725 726
          when :raise
            raise ActionController::UnpermittedParameters.new(unpermitted_keys)
          end
        end
      end

      def unpermitted_keys(params)
727
        keys - params.keys - always_permitted_parameters
728
      end
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752

      #
      # --- Filtering ----------------------------------------------------------
      #

      # This is a white list of permitted scalar types that includes the ones
      # supported in XML and JSON requests.
      #
      # This list is in particular used to filter ordinary requests, String goes
      # as first element to quickly short-circuit the common case.
      #
      # If you modify this collection please update the API of +permit+ above.
      PERMITTED_SCALAR_TYPES = [
        String,
        Symbol,
        NilClass,
        Numeric,
        TrueClass,
        FalseClass,
        Date,
        Time,
        # DateTimes are Dates, we document the type but avoid the redundant check.
        StringIO,
        IO,
753
        ActionDispatch::Http::UploadedFile,
754
        Rack::Test::UploadedFile,
755 756 757
      ]

      def permitted_scalar?(value)
758
        PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) }
759 760 761 762 763 764 765
      end

      def permitted_scalar_filter(params, key)
        if has_key?(key) && permitted_scalar?(self[key])
          params[key] = self[key]
        end

766
        keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
767 768
          if permitted_scalar?(self[k])
            params[k] = self[k]
769 770 771 772 773
          end
        end
      end

      def array_of_permitted_scalars?(value)
774
        if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
775
          yield value
776 777 778
        end
      end

779 780 781 782
      def non_scalar?(value)
        value.is_a?(Array) || value.is_a?(Parameters)
      end

783
      EMPTY_ARRAY = []
784 785 786 787 788
      def hash_filter(params, filter)
        filter = filter.with_indifferent_access

        # Slicing filters out non-declared keys.
        slice(*filter.keys).each do |key, value|
789
          next unless value
A
Aaron Patterson 已提交
790
          next unless has_key? key
791

792
          if filter[key] == EMPTY_ARRAY
793
            # Declaration { comment_ids: [] }.
A
Aaron Patterson 已提交
794
            array_of_permitted_scalars?(self[key]) do |val|
795 796
              params[key] = val
            end
797
          elsif non_scalar?(value)
V
Vipul A M 已提交
798
            # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
799
            params[key] = each_element(value) do |element|
A
Aaron Patterson 已提交
800
              element.permit(*Array.wrap(filter[key]))
801 802 803 804
            end
          end
        end
      end
805 806 807

      def initialize_copy(source)
        super
808
        @parameters = @parameters.dup
809
      end
810 811
  end

812
  # == Strong \Parameters
813
  #
814
  # It provides an interface for protecting attributes from end-user
815
  # assignment. This makes Action Controller parameters forbidden
816
  # to be used in Active Model mass assignment until they have been
817 818 819 820 821 822 823
  # whitelisted.
  #
  # In addition, parameters can be marked as required and flow through a
  # predefined raise/rescue flow to end up as a 400 Bad Request with no
  # effort.
  #
  #   class PeopleController < ActionController::Base
824
  #     # Using "Person.create(params[:person])" would raise an
825
  #     # ActiveModel::ForbiddenAttributesError exception because it'd
826 827
  #     # be using mass assignment without an explicit permit step.
  #     # This is the recommended form:
828
  #     def create
829
  #       Person.create(person_params)
830 831 832
  #     end
  #
  #     # This will pass with flying colors as long as there's a person key in the
833
  #     # parameters, otherwise it'll raise an ActionController::MissingParameter
834
  #     # exception, which will get caught by ActionController::Base and turned
835
  #     # into a 400 Bad Request reply.
836 837
  #     def update
  #       redirect_to current_account.people.find(params[:id]).tap { |person|
838
  #         person.update!(person_params)
839 840 841 842 843
  #       }
  #     end
  #
  #     private
  #       # Using a private method to encapsulate the permissible parameters is
844
  #       # just a good pattern since you'll be able to reuse the same permit
845 846 847 848 849 850 851
  #       # list between create and update. Also, you can specialize this method
  #       # with per-user checking of permissible attributes.
  #       def person_params
  #         params.require(:person).permit(:name, :age)
  #       end
  #   end
  #
852
  # In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
853 854
  # will need to specify which nested attributes should be whitelisted. You might want
  # to allow +:id+ and +:_destroy+, see ActiveRecord::NestedAttributes for more information.
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873
  #
  #   class Person
  #     has_many :pets
  #     accepts_nested_attributes_for :pets
  #   end
  #
  #   class PeopleController < ActionController::Base
  #     def create
  #       Person.create(person_params)
  #     end
  #
  #     ...
  #
  #     private
  #
  #       def person_params
  #         # It's mandatory to specify the nested attributes that should be whitelisted.
  #         # If you use `permit` with just the key that points to the nested attributes hash,
  #         # it will return an empty hash.
874
  #         params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ])
875 876 877
  #       end
  #   end
  #
878 879
  # See ActionController::Parameters.require and ActionController::Parameters.permit
  # for more information.
880 881 882 883
  module StrongParameters
    extend ActiveSupport::Concern
    include ActiveSupport::Rescuable

884 885
    # Returns a new ActionController::Parameters object that
    # has been instantiated with the <tt>request.parameters</tt>.
886 887 888 889
    def params
      @_params ||= Parameters.new(request.parameters)
    end

890 891 892 893 894
    # Assigns the given +value+ to the +params+ hash. If +value+
    # is a Hash, this will create an ActionController::Parameters
    # object that has been instantiated with the given +value+ hash.
    def params=(value)
      @_params = value.is_a?(Hash) ? Parameters.new(value) : value
895 896 897
    end
  end
end