strong_parameters.rb 26.4 KB
Newer Older
1
require 'active_support/core_ext/hash/indifferent_access'
R
Rafael Mendonça França 已提交
2
require 'active_support/core_ext/array/wrap'
X
Xavier Noria 已提交
3
require 'active_support/core_ext/string/filters'
4
require 'active_support/rescuable'
5
require 'action_dispatch/http/upload'
6
require 'stringio'
7
require 'set'
8 9

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

20
    def initialize(param) # :nodoc:
21
      @param = param
22
      super("param is missing or the value is empty: #{param}")
23 24 25
    end
  end

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

36
    def initialize(params) # :nodoc:
37
      @params = params
38
      super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}")
39 40 41
    end
  end

42
  # == Action Controller \Parameters
43 44
  #
  # Allows to choose which attributes should be whitelisted for mass updating
45
  # and thus prevent accidentally exposing that which shouldn't be exposed.
46 47
  # 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
48
  # as permitted and limit which attributes should be allowed for mass updating.
49 50 51 52 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)
  #   permitted            # => {"name"=>"Francesco", "age"=>22}
  #   permitted.class      # => ActionController::Parameters
  #   permitted.permitted? # => true
  #
63
  #   Person.first.update!(permitted)
64
  #   # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
65
  #
66 67 68 69 70 71 72 73 74
  # 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.
75
  #
76 77
  # Examples:
  #
78
  #   params = ActionController::Parameters.new
79
  #   params.permitted? # => false
80 81 82 83 84 85
  #
  #   ActionController::Parameters.permit_all_parameters = true
  #
  #   params = ActionController::Parameters.new
  #   params.permitted? # => true
  #
86 87 88 89 90 91 92 93 94 95
  #   params = ActionController::Parameters.new(a: "123", b: "456")
  #   params.permit(:c)
  #   # => {}
  #
  #   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
  #
96 97 98 99 100
  # 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.
  #
  # <tt>ActionController::Parameters</tt> inherits from
101 102 103 104 105 106
  # <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
  # that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
  #
  #   params = ActionController::Parameters.new(key: 'value')
  #   params[:key]  # => "value"
  #   params["key"] # => "value"
107
  class Parameters
108
    cattr_accessor :permit_all_parameters, instance_accessor: false
109 110
    cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false

111 112
    delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters

113 114 115 116
    # 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 已提交
117 118 119
    # config. For instance:
    #
    #    config.always_permitted_parameters = %w( controller action format )
120 121 122 123
    cattr_accessor :always_permitted_parameters
    self.always_permitted_parameters = %w( controller action )

    def self.const_missing(const_name)
124
      return super unless const_name == :NEVER_UNPERMITTED_PARAMS
X
Xavier Noria 已提交
125 126 127 128 129 130
      ActiveSupport::Deprecation.warn(<<-MSG.squish)
        `ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated.
        Use `ActionController::Parameters.always_permitted_parameters` instead.
      MSG

      always_permitted_parameters
131
    end
132

133 134 135 136
    # 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 已提交
137
    #   class Person < ActiveRecord::Base
138 139 140 141
    #   end
    #
    #   params = ActionController::Parameters.new(name: 'Francesco')
    #   params.permitted?  # => false
142
    #   Person.new(params) # => ActiveModel::ForbiddenAttributesError
143 144 145 146
    #
    #   ActionController::Parameters.permit_all_parameters = true
    #
    #   params = ActionController::Parameters.new(name: 'Francesco')
147
    #   params.permitted?  # => true
148
    #   Person.new(params) # => #<Person id: nil, name: "Francesco">
149 150
    def initialize(parameters = {})
      @parameters = parameters.with_indifferent_access
151
      @permitted = self.class.permit_all_parameters
152 153
    end

154 155 156
    # Returns true if another +Parameters+ object contains the same content and
    # permitted flag, or other Hash-like object contains the same content. This
    # override is in place so you can perform a comparison with `Hash`.
157 158 159 160 161 162 163 164
    def ==(other_hash)
      if other_hash.respond_to?(:permitted?)
        super
      else
        @parameters == other_hash
      end
    end

165 166 167 168 169 170 171 172 173 174 175 176 177
    # Returns a safe +Hash+ representation of this parameter with all
    # unpermitted keys removed.
    #
    #   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?
178
        @parameters.to_h
179 180 181 182 183
      else
        slice(*self.class.always_permitted_parameters).permit!.to_h
      end
    end

P
Prem Sichanugrist 已提交
184 185
    # Returns an unsafe, unfiltered +Hash+ representation of this parameter.
    def to_unsafe_h
186
      @parameters.to_h
P
Prem Sichanugrist 已提交
187 188 189
    end
    alias_method :to_unsafe_hash, :to_unsafe_h

190 191 192
    # Convert all hashes in values into parameters, then yield each pair like
    # the same way as <tt>Hash#each_pair</tt>
    def each_pair(&block)
193 194
      @parameters.each_pair do |key, value|
        yield key, convert_hashes_to_parameters(key, value)
195 196 197 198
      end
    end
    alias_method :each, :each_pair

199 200 201
    # 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.
202 203 204 205
    #
    # 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.
206
    def converted_arrays
207
      @converted_arrays ||= Set.new
208 209
    end

210 211 212 213 214 215 216 217 218 219
    # 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

220 221 222 223 224 225 226
    # 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')
227
    #   params.permitted?  # => false
228 229 230 231
    #   Person.new(params) # => ActiveModel::ForbiddenAttributesError
    #   params.permit!
    #   params.permitted?  # => true
    #   Person.new(params) # => #<Person id: nil, name: "Francesco">
232
    def permit!
233
      each_pair do |key, value|
C
Corey Ward 已提交
234 235
        Array.wrap(value).each do |v|
          v.permit! if v.respond_to? :permit!
236
        end
237 238
      end

239 240 241 242
      @permitted = true
      self
    end

243 244 245
    # Ensures that a parameter is present. If it's present, returns
    # the parameter at the given +key+, otherwise raises an
    # <tt>ActionController::ParameterMissing</tt> error.
246
    #
247
    #   ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
248
    #   # => {"name"=>"Francesco"}
249
    #
250
    #   ActionController::Parameters.new(person: nil).require(:person)
251
    #   # => ActionController::ParameterMissing: param is missing or the value is empty: person
252
    #
253
    #   ActionController::Parameters.new(person: {}).require(:person)
254
    #   # => ActionController::ParameterMissing: param is missing or the value is empty: person
255
    def require(key)
256 257 258 259 260 261
      value = self[key]
      if value.present? || value == false
        value
      else
        raise ParameterMissing.new(key)
      end
262 263
    end

264
    # Alias of #require.
265 266
    alias :required :require

267
    # Returns a new <tt>ActionController::Parameters</tt> instance that
268 269
    # includes only the given +filters+ and sets the +permitted+ attribute
    # for the object to +true+. This is useful for limiting which attributes
270 271 272 273
    # should be allowed for mass updating.
    #
    #   params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
    #   permitted = params.require(:user).permit(:name, :age)
274
    #   permitted.permitted?      # => true
275 276 277 278
    #   permitted.has_key?(:name) # => true
    #   permitted.has_key?(:age)  # => true
    #   permitted.has_key?(:role) # => false
    #
279 280 281 282
    # Only permitted scalars pass the filter. For example, given
    #
    #   params.permit(:name)
    #
283
    # +:name+ passes if it is a key of +params+ whose associated value is of type
284
    # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
285 286 287
    # +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
    # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
    # Otherwise, the key +:name+ is filtered out.
288 289 290 291
    #
    # You may declare that the parameter should be an array of permitted scalars
    # by mapping it to an empty array:
    #
292
    #   params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
293
    #   params.permit(tags: [])
294
    #
295 296 297
    # You can also use +permit+ on nested parameters, like:
    #
    #   params = ActionController::Parameters.new({
298
    #     person: {
299 300 301 302 303 304 305 306 307
    #       name: 'Francesco',
    #       age:  22,
    #       pets: [{
    #         name: 'Purplish',
    #         category: 'dogs'
    #       }]
    #     }
    #   })
    #
308
    #   permitted = params.permit(person: [ :name, { pets: :name } ])
309 310
    #   permitted.permitted?                    # => true
    #   permitted[:person][:name]               # => "Francesco"
311
    #   permitted[:person][:age]                # => nil
312 313
    #   permitted[:person][:pets][0][:name]     # => "Purplish"
    #   permitted[:person][:pets][0][:category] # => nil
314 315 316 317 318 319 320 321
    #
    # 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 已提交
322
    #         email: 'none@test.com',
323 324 325 326 327 328 329 330 331
    #         phone: '555-1234'
    #       }
    #     }
    #   })
    #
    #   params.require(:person).permit(:contact)
    #   # => {}
    #
    #   params.require(:person).permit(contact: :phone)
332
    #   # => {"contact"=>{"phone"=>"555-1234"}}
333 334 335
    #
    #   params.require(:person).permit(contact: [ :email, :phone ])
    #   # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
336 337 338
    def permit(*filters)
      params = self.class.new

339
      filters.flatten.each do |filter|
340
        case filter
341 342
        when Symbol, String
          permitted_scalar_filter(params, filter)
343
        when Hash then
344
          hash_filter(params, filter)
345 346 347
        end
      end

348
      unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
349

350 351 352
      params.permit!
    end

353 354 355
    # Returns a parameter for the given +key+. If not found,
    # returns +nil+.
    #
356
    #   params = ActionController::Parameters.new(person: { name: 'Francesco' })
357
    #   params[:person] # => {"name"=>"Francesco"}
358
    #   params[:none]   # => nil
359
    def [](key)
360 361 362
      convert_hashes_to_parameters(key, @parameters[key])
    end

363 364
    # Assigns a value to a given +key+. The given key may still get filtered out
    # when +permit+ is called.
365 366
    def []=(key, value)
      @parameters[key] = value
367 368
    end

369 370 371 372 373 374 375
    # 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' })
376
    #   params.fetch(:person)               # => {"name"=>"Francesco"}
377
    #   params.fetch(:none)                 # => ActionController::ParameterMissing: param is missing or the value is empty: none
378
    #   params.fetch(:none, 'Francesco')    # => "Francesco"
379
    #   params.fetch(:none) { 'Francesco' } # => "Francesco"
380 381 382 383 384 385
    def fetch(key, *args, &block)
      convert_hashes_to_parameters(
        key,
        @parameters.fetch(key, *args, &block),
        false
      )
386 387 388 389
    rescue KeyError
      raise ActionController::ParameterMissing.new(key)
    end

390 391 392 393 394 395 396
    # 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)
    #   params.slice(:a, :b) # => {"a"=>1, "b"=>2}
    #   params.slice(:d)     # => {}
397
    def slice(*keys)
398 399 400
      new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
    end

401 402
    # Returns current <tt>ActionController::Parameters</tt> instance which
    # contains only the given +keys+.
403 404 405 406 407
    def slice!(*keys)
      @parameters.slice!(*keys)
      self
    end

408 409 410 411 412 413
    # Returns a new <tt>ActionController::Parameters</tt> instance that
    # filters out the given +keys+.
    #
    #   params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
    #   params.except(:a, :b) # => {"c"=>3}
    #   params.except(:d)     # => {"a"=>1,"b"=>2,"c"=>3}
414 415
    def except(*keys)
      new_instance_with_inherited_permitted_status(@parameters.except(*keys))
416 417
    end

418 419 420 421 422 423
    # Removes and returns the key/value pairs matching the given keys.
    #
    #   params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
    #   params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
    #   params                  # => {"c"=>3}
    def extract!(*keys)
424
      new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
425 426 427 428 429 430 431 432
    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 }
    #   # => {"a"=>2, "b"=>4, "c"=>6}
433 434 435 436 437
    def transform_values(&block)
      if block
        new_instance_with_inherited_permitted_status(
          @parameters.transform_values(&block)
        )
438
      else
439
        @parameters.transform_values
440 441 442
      end
    end

443 444
    # Performs values transformation and returns the altered
    # <tt>ActionController::Parameters</tt> instance.
445 446 447 448 449
    def transform_values!(&block)
      @parameters.transform_values!(&block)
      self
    end

450 451 452
    # 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)
453 454 455 456
      if block
        new_instance_with_inherited_permitted_status(
          @parameters.transform_keys(&block)
        )
457
      else
458
        @parameters.transform_keys
459 460 461
      end
    end

462 463
    # Performs keys transfomration and returns the altered
    # <tt>ActionController::Parameters</tt> instance.
464 465 466 467 468
    def transform_keys!(&block)
      @parameters.transform_keys!(&block)
      self
    end

469 470 471 472 473
    # 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.
    def delete(key, &block)
474 475 476
      convert_hashes_to_parameters(key, @parameters.delete(key), false)
    end

477 478
    # Returns a new instance of <tt>ActionController::Parameters</tt> with only
    # items that the block evaluates to true.
479 480
    def select(&block)
      new_instance_with_inherited_permitted_status(@parameters.select(&block))
481 482 483 484
    end

    # Equivalent to Hash#keep_if, but returns nil if no changes were made.
    def select!(&block)
485 486 487 488 489
      @parameters.select!(&block)
      self
    end
    alias_method :keep_if, :select!

490 491
    # Returns a new instance of <tt>ActionController::Parameters</tt> with items
    # that the block evaluates to true removed.
492 493 494 495
    def reject(&block)
      new_instance_with_inherited_permitted_status(@parameters.reject(&block))
    end

496
    # Removes items that the block evaluates to true and returns self.
497 498 499 500 501 502
    def reject!(&block)
      @parameters.reject!(&block)
      self
    end
    alias_method :delete_if, :reject!

503 504
    # Return values that were assigned to the given +keys+. Note that all the
    # +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
505 506
    def values_at(*keys)
      convert_value_to_parameters(@parameters.values_at(*keys))
507 508
    end

509 510 511 512 513 514 515 516
    # Returns an exact copy of the <tt>ActionController::Parameters</tt>
    # instance. +permitted+ state is kept on the duped object.
    #
    #   params = ActionController::Parameters.new(a: 1)
    #   params.permit!
    #   params.permitted?        # => true
    #   copy_params = params.dup # => {"a"=>1}
    #   copy_params.permitted?   # => true
517 518
    def dup
      super.tap do |duplicate|
519
        duplicate.permitted = @permitted
520 521 522
      end
    end

523 524
    # Returns a new <tt>ActionController::Parameters</tt> with all keys from
    # +other_hash+ merges into current hash.
525 526 527 528 529 530 531
    def merge(other_hash)
      new_instance_with_inherited_permitted_status(
        @parameters.merge(other_hash)
      )
    end

    # This is required by ActiveModel attribute assignment, so that user can
532 533 534
    # pass +Parameters+ to a mass assignment methods in a model. It should not
    # matter as we are using +HashWithIndifferentAccess+ internally.
    def stringify_keys # :nodoc:
535 536 537
      dup
    end

538 539 540 541 542
    protected
      def permitted=(new_permitted)
        @permitted = new_permitted
      end

543
    private
544 545 546 547 548 549
      def new_instance_with_inherited_permitted_status(hash)
        self.class.new(hash).tap do |new_instance|
          new_instance.permitted = @permitted
        end
      end

X
Xavier Noria 已提交
550
      def convert_hashes_to_parameters(key, value, assign_if_converted=true)
551
        converted = convert_value_to_parameters(value)
552
        @parameters[key] = converted if assign_if_converted && !converted.equal?(value)
553 554 555
        converted
      end

556 557 558 559
      def convert_value_to_parameters(value)
        if value.is_a?(Array) && !converted_arrays.member?(value)
          converted = value.map { |_| convert_value_to_parameters(_) }
          converted_arrays << converted
560
          converted
561
        elsif value.is_a?(Parameters) || !value.is_a?(Hash)
562 563
          value
        else
564
          self.class.new(value)
565 566 567 568
        end
      end

      def each_element(object)
A
Aaron Patterson 已提交
569 570 571 572 573 574 575 576 577 578 579
        case object
        when Array
          object.grep(Parameters).map { |el| yield el }.compact
        when Parameters
          if fields_for_style?(object)
            hash = object.class.new
            object.each { |k,v| hash[k] = yield v }
            hash
          else
            yield object
          end
580 581
        end
      end
582

583
      def fields_for_style?(object)
A
Aaron Patterson 已提交
584
        object.to_unsafe_h.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
585 586
      end

587 588 589 590 591
      def unpermitted_parameters!(params)
        unpermitted_keys = unpermitted_keys(params)
        if unpermitted_keys.any?
          case self.class.action_on_unpermitted_parameters
          when :log
592 593
            name = "unpermitted_parameters.action_controller"
            ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
594 595 596 597 598 599 600
          when :raise
            raise ActionController::UnpermittedParameters.new(unpermitted_keys)
          end
        end
      end

      def unpermitted_keys(params)
601
        self.keys - params.keys - self.always_permitted_parameters
602
      end
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626

      #
      # --- 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,
627
        ActionDispatch::Http::UploadedFile,
628
        Rack::Test::UploadedFile,
629 630 631 632 633 634 635 636 637 638 639
      ]

      def permitted_scalar?(value)
        PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
      end

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

640
        keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
641 642
          if permitted_scalar?(self[k])
            params[k] = self[k]
643 644 645 646 647
          end
        end
      end

      def array_of_permitted_scalars?(value)
A
Aaron Patterson 已提交
648
        if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)}
649
          yield value
650 651 652
        end
      end

653
      EMPTY_ARRAY = []
654 655 656 657 658
      def hash_filter(params, filter)
        filter = filter.with_indifferent_access

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

662
          if filter[key] == EMPTY_ARRAY
663
            # Declaration { comment_ids: [] }.
A
Aaron Patterson 已提交
664
            array_of_permitted_scalars?(self[key]) do |val|
665 666
              params[key] = val
            end
667
          else
V
Vipul A M 已提交
668
            # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
669
            params[key] = each_element(value) do |element|
A
Aaron Patterson 已提交
670
              element.permit(*Array.wrap(filter[key]))
671 672 673 674
            end
          end
        end
      end
675 676
  end

677
  # == Strong \Parameters
678
  #
679
  # It provides an interface for protecting attributes from end-user
680
  # assignment. This makes Action Controller parameters forbidden
681
  # to be used in Active Model mass assignment until they have been
682 683 684 685 686 687 688
  # 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
689 690 691 692
  #     # Using "Person.create(params[:person])" would raise an
  #     # ActiveModel::ForbiddenAttributes exception because it'd
  #     # be using mass assignment without an explicit permit step.
  #     # This is the recommended form:
693
  #     def create
694
  #       Person.create(person_params)
695 696 697
  #     end
  #
  #     # This will pass with flying colors as long as there's a person key in the
698
  #     # parameters, otherwise it'll raise an ActionController::MissingParameter
699
  #     # exception, which will get caught by ActionController::Base and turned
700
  #     # into a 400 Bad Request reply.
701 702
  #     def update
  #       redirect_to current_account.people.find(params[:id]).tap { |person|
703
  #         person.update!(person_params)
704 705 706 707 708
  #       }
  #     end
  #
  #     private
  #       # Using a private method to encapsulate the permissible parameters is
709
  #       # just a good pattern since you'll be able to reuse the same permit
710 711 712 713 714 715 716
  #       # 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
  #
717
  # In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
  # will need to specify which nested attributes should be whitelisted.
  #
  #   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.
738
  #         params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
739 740 741
  #       end
  #   end
  #
742 743
  # See ActionController::Parameters.require and ActionController::Parameters.permit
  # for more information.
744 745 746 747
  module StrongParameters
    extend ActiveSupport::Concern
    include ActiveSupport::Rescuable

748 749
    # Returns a new ActionController::Parameters object that
    # has been instantiated with the <tt>request.parameters</tt>.
750 751 752 753
    def params
      @_params ||= Parameters.new(request.parameters)
    end

754 755 756 757 758
    # 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
759 760 761
    end
  end
end