strong_parameters.rb 19.2 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'
3
require 'active_support/rescuable'
4
require 'action_dispatch/http/upload'
5
require 'stringio'
6
require 'set'
7 8

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

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

25 26 27 28
  # Raised when a supplied parameter is not expected.
  #
  #   params = ActionController::Parameters.new(a: "123", b: "456")
  #   params.permit(:c)
29 30 31
  #   # => ActionController::UnpermittedParameters: found unexpected keys: a, b
  class UnpermittedParameters < IndexError
    attr_reader :params # :nodoc:
32

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

39
  # == Action Controller \Parameters
40 41 42 43 44
  #
  # Allows to choose which attributes should be whitelisted for mass updating
  # and thus prevent accidentally exposing that which shouldn’t be exposed.
  # 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
45
  # as permitted and limit which attributes should be allowed for mass updating.
46 47 48 49 50 51 52 53 54 55 56 57 58 59
  #
  #   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
  #
60
  #   Person.first.update!(permitted)
61
  #   # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
62
  #
63 64 65 66 67 68 69 70 71
  # 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.
72
  #
73 74
  # Examples:
  #
75
  #   params = ActionController::Parameters.new
76
  #   params.permitted? # => false
77 78 79 80 81 82
  #
  #   ActionController::Parameters.permit_all_parameters = true
  #
  #   params = ActionController::Parameters.new
  #   params.permitted? # => true
  #
83 84 85 86 87 88 89 90 91 92
  #   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
  #
93 94 95 96 97 98 99
  # <tt>ActionController::Parameters</tt> is inherited from
  # <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"
100
  class Parameters < ActiveSupport::HashWithIndifferentAccess
101
    cattr_accessor :permit_all_parameters, instance_accessor: false
102 103 104 105 106
    cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false

    # Never raise an UnpermittedParameters exception because of these params
    # are present. They are added by Rails and it's of no concern.
    NEVER_UNPERMITTED_PARAMS = %w( controller action )
107

108 109 110 111
    # 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 已提交
112
    #   class Person < ActiveRecord::Base
113 114 115 116
    #   end
    #
    #   params = ActionController::Parameters.new(name: 'Francesco')
    #   params.permitted?  # => false
117
    #   Person.new(params) # => ActiveModel::ForbiddenAttributesError
118 119 120 121
    #
    #   ActionController::Parameters.permit_all_parameters = true
    #
    #   params = ActionController::Parameters.new(name: 'Francesco')
122
    #   params.permitted?  # => true
123
    #   Person.new(params) # => #<Person id: nil, name: "Francesco">
124 125
    def initialize(attributes = nil)
      super(attributes)
126
      @permitted = self.class.permit_all_parameters
127 128
    end

129 130 131 132
    # 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.
    def converted_arrays
133
      @converted_arrays ||= {}
134 135
    end

136 137 138 139 140 141 142 143 144 145
    # 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

146 147 148 149 150 151 152
    # 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')
153
    #   params.permitted?  # => false
154 155 156 157
    #   Person.new(params) # => ActiveModel::ForbiddenAttributesError
    #   params.permit!
    #   params.permitted?  # => true
    #   Person.new(params) # => #<Person id: nil, name: "Francesco">
158
    def permit!
159
      each_pair do |key, value|
160 161 162 163
        value = convert_hashes_to_parameters(key, value)
        Array.wrap(value).each do |_|
          _.permit! if _.respond_to? :permit!
        end
164 165
      end

166 167 168 169
      @permitted = true
      self
    end

170 171 172
    # 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.
173
    #
174
    #   ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
175
    #   # => {"name"=>"Francesco"}
176
    #
177 178
    #   ActionController::Parameters.new(person: nil).require(:person)
    #   # => ActionController::ParameterMissing: param not found: person
179
    #
180
    #   ActionController::Parameters.new(person: {}).require(:person)
181
    #   # => ActionController::ParameterMissing: param not found: person
182
    def require(key)
183
      self[key].presence || raise(ParameterMissing.new(key))
184 185
    end

186
    # Alias of #require.
187 188
    alias :required :require

189
    # Returns a new <tt>ActionController::Parameters</tt> instance that
190 191
    # includes only the given +filters+ and sets the +permitted+ attribute
    # for the object to +true+. This is useful for limiting which attributes
192 193 194 195
    # should be allowed for mass updating.
    #
    #   params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
    #   permitted = params.require(:user).permit(:name, :age)
196
    #   permitted.permitted?      # => true
197 198 199 200
    #   permitted.has_key?(:name) # => true
    #   permitted.has_key?(:age)  # => true
    #   permitted.has_key?(:role) # => false
    #
201 202 203 204 205 206
    # Only permitted scalars pass the filter. For example, given
    #
    #   params.permit(:name)
    #
    # +:name+ passes it is a key of +params+ whose associated value is of type
    # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
207 208 209
    # +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
    # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
    # Otherwise, the key +:name+ is filtered out.
210 211 212 213
    #
    # You may declare that the parameter should be an array of permitted scalars
    # by mapping it to an empty array:
    #
214
    #   params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
215
    #   params.permit(tags: [])
216
    #
217 218 219
    # You can also use +permit+ on nested parameters, like:
    #
    #   params = ActionController::Parameters.new({
220
    #     person: {
221 222 223 224 225 226 227 228 229
    #       name: 'Francesco',
    #       age:  22,
    #       pets: [{
    #         name: 'Purplish',
    #         category: 'dogs'
    #       }]
    #     }
    #   })
    #
230
    #   permitted = params.permit(person: [ :name, { pets: :name } ])
231 232
    #   permitted.permitted?                    # => true
    #   permitted[:person][:name]               # => "Francesco"
233
    #   permitted[:person][:age]                # => nil
234 235
    #   permitted[:person][:pets][0][:name]     # => "Purplish"
    #   permitted[:person][:pets][0][:category] # => nil
236 237 238 239 240 241 242 243
    #
    # 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 已提交
244
    #         email: 'none@test.com',
245 246 247 248 249 250 251 252 253
    #         phone: '555-1234'
    #       }
    #     }
    #   })
    #
    #   params.require(:person).permit(:contact)
    #   # => {}
    #
    #   params.require(:person).permit(contact: :phone)
254
    #   # => {"contact"=>{"phone"=>"555-1234"}}
255 256 257
    #
    #   params.require(:person).permit(contact: [ :email, :phone ])
    #   # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
258 259 260
    def permit(*filters)
      params = self.class.new

261
      filters.flatten.each do |filter|
262
        case filter
263 264
        when Symbol, String
          permitted_scalar_filter(params, filter)
265
        when Hash then
266
          hash_filter(params, filter)
267 268 269
        end
      end

270
      unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
271

272 273 274
      params.permit!
    end

275 276 277
    # Returns a parameter for the given +key+. If not found,
    # returns +nil+.
    #
278
    #   params = ActionController::Parameters.new(person: { name: 'Francesco' })
279
    #   params[:person] # => {"name"=>"Francesco"}
280
    #   params[:none]   # => nil
281 282 283 284
    def [](key)
      convert_hashes_to_parameters(key, super)
    end

285 286 287 288 289 290 291
    # 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' })
292 293
    #   params.fetch(:person)               # => {"name"=>"Francesco"}
    #   params.fetch(:none)                 # => ActionController::ParameterMissing: param not found: none
294
    #   params.fetch(:none, 'Francesco')    # => "Francesco"
295
    #   params.fetch(:none) { 'Francesco' } # => "Francesco"
296
    def fetch(key, *args)
X
Xavier Noria 已提交
297
      convert_hashes_to_parameters(key, super, false)
298 299 300 301
    rescue KeyError
      raise ActionController::ParameterMissing.new(key)
    end

302 303 304 305 306 307 308
    # 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)     # => {}
309
    def slice(*keys)
310
      self.class.new(super).tap do |new_instance|
311
        new_instance.permitted = @permitted
312
      end
313 314
    end

315 316 317 318 319 320 321 322
    # 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
323 324
    def dup
      super.tap do |duplicate|
325
        duplicate.permitted = @permitted
326 327 328
      end
    end

329 330 331 332 333
    protected
      def permitted=(new_permitted)
        @permitted = new_permitted
      end

334
    private
X
Xavier Noria 已提交
335
      def convert_hashes_to_parameters(key, value, assign_if_converted=true)
336
        converted = convert_value_to_parameters(key, value)
X
Xavier Noria 已提交
337
        self[key] = converted if assign_if_converted && !converted.equal?(value)
338 339 340
        converted
      end

341 342 343 344
      def convert_value_to_parameters(key, value)
        if value.is_a?(Array) && !converted_arrays.member?(key)
          converted = value.map { |v| convert_value_to_parameters(nil, v) }
          converted_arrays[key] = converted if key
345
          converted
346
        elsif value.is_a?(Parameters) || !value.is_a?(Hash)
347 348
          value
        else
349
          self.class.new(value)
350 351 352 353 354 355
        end
      end

      def each_element(object)
        if object.is_a?(Array)
          object.map { |el| yield el }.compact
356
        elsif fields_for_style?(object)
357 358 359
          hash = object.class.new
          object.each { |k,v| hash[k] = yield v }
          hash
360 361 362 363
        else
          yield object
        end
      end
364

365 366 367 368
      def fields_for_style?(object)
        object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
      end

369 370 371 372 373
      def unpermitted_parameters!(params)
        unpermitted_keys = unpermitted_keys(params)
        if unpermitted_keys.any?
          case self.class.action_on_unpermitted_parameters
          when :log
374 375
            name = "unpermitted_parameters.action_controller"
            ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
376 377 378 379 380 381 382 383 384
          when :raise
            raise ActionController::UnpermittedParameters.new(unpermitted_keys)
          end
        end
      end

      def unpermitted_keys(params)
        self.keys - params.keys - NEVER_UNPERMITTED_PARAMS
      end
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408

      #
      # --- 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,
409
        ActionDispatch::Http::UploadedFile,
410
        Rack::Test::UploadedFile,
411 412 413 414 415 416 417 418 419 420 421
      ]

      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

422
        keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
423 424
          if permitted_scalar?(self[k])
            params[k] = self[k]
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
          end
        end
      end

      def array_of_permitted_scalars?(value)
        if value.is_a?(Array)
          value.all? {|element| permitted_scalar?(element)}
        end
      end

      def array_of_permitted_scalars_filter(params, key)
        if has_key?(key) && array_of_permitted_scalars?(self[key])
          params[key] = self[key]
        end
      end

441
      EMPTY_ARRAY = []
442 443 444 445 446
      def hash_filter(params, filter)
        filter = filter.with_indifferent_access

        # Slicing filters out non-declared keys.
        slice(*filter.keys).each do |key, value|
447
          next unless value
448

449
          if filter[key] == EMPTY_ARRAY
450
            # Declaration { comment_ids: [] }.
451 452
            array_of_permitted_scalars_filter(params, key)
          else
V
Vipul A M 已提交
453
            # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
454 455 456 457 458 459 460 461 462
            params[key] = each_element(value) do |element|
              if element.is_a?(Hash)
                element = self.class.new(element) unless element.respond_to?(:permit)
                element.permit(*Array.wrap(filter[key]))
              end
            end
          end
        end
      end
463 464
  end

465
  # == Strong \Parameters
466
  #
467
  # It provides an interface for protecting attributes from end-user
468
  # assignment. This makes Action Controller parameters forbidden
469
  # to be used in Active Model mass assignment until they have been
470 471 472 473 474 475 476
  # 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
477 478 479 480
  #     # 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:
481
  #     def create
482
  #       Person.create(person_params)
483 484 485
  #     end
  #
  #     # This will pass with flying colors as long as there's a person key in the
486
  #     # parameters, otherwise it'll raise an ActionController::MissingParameter
487
  #     # exception, which will get caught by ActionController::Base and turned
488
  #     # into a 400 Bad Request reply.
489 490
  #     def update
  #       redirect_to current_account.people.find(params[:id]).tap { |person|
491
  #         person.update!(person_params)
492 493 494 495 496
  #       }
  #     end
  #
  #     private
  #       # Using a private method to encapsulate the permissible parameters is
497
  #       # just a good pattern since you'll be able to reuse the same permit
498 499 500 501 502 503 504
  #       # 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
  #
505
  # In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
  # 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.
526
  #         params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
527 528 529
  #       end
  #   end
  #
530 531
  # See ActionController::Parameters.require and ActionController::Parameters.permit
  # for more information.
532 533 534 535
  module StrongParameters
    extend ActiveSupport::Concern
    include ActiveSupport::Rescuable

536 537
    # Returns a new ActionController::Parameters object that
    # has been instantiated with the <tt>request.parameters</tt>.
538 539 540 541
    def params
      @_params ||= Parameters.new(request.parameters)
    end

542 543 544 545 546
    # 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
547 548 549
    end
  end
end