提交 14a3bd52 编写于 作者: P Prem Sichanugrist

Make AC::Parameters not inherited from Hash

This is another take at #14384 as we decided to wait until `master` is
targeting Rails 5.0. This commit is implementation-complete, as it
guarantees that all the public methods on the hash-inherited Parameters
are still working (based on test case). We can decide to follow-up later
if we want to remove some methods out from Parameters.
上级 a0b4dc21
* `ActionController::Parameters` no longer inherits from
`HashWithIndifferentAccess`
Inheriting from `HashWithIndifferentAccess` allowed users to call any
enumerable methods on `Parameters` object, resulting in a risk of losing the
`permitted?` status or even getting back a pure `Hash` object instead of
a `Parameters` object with proper sanitization.
By stop inheriting from `HashWithIndifferentAccess`, we are able to make
sure that all methods that are defined in `Parameters` object will return
a proper `Parameters` object with a correct `permitted?` flag.
*Prem Sichanugrist*
* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
from the concurrent-ruby gem.
......
......@@ -104,10 +104,12 @@ def initialize(params) # :nodoc:
# params = ActionController::Parameters.new(key: 'value')
# params[:key] # => "value"
# params["key"] # => "value"
class Parameters < ActiveSupport::HashWithIndifferentAccess
class Parameters
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters
# 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
......@@ -144,11 +146,19 @@ def self.const_missing(const_name)
# params = ActionController::Parameters.new(name: 'Francesco')
# params.permitted? # => true
# Person.new(params) # => #<Person id: nil, name: "Francesco">
def initialize(attributes = nil)
super(attributes)
def initialize(parameters = {})
@parameters = parameters.with_indifferent_access
@permitted = self.class.permit_all_parameters
end
def ==(other_hash)
if other_hash.respond_to?(:permitted?)
super
else
@parameters == other_hash
end
end
# Returns a safe +Hash+ representation of this parameter with all
# unpermitted keys removed.
#
......@@ -162,7 +172,7 @@ def initialize(attributes = nil)
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
def to_h
if permitted?
to_hash
@parameters.to_h
else
slice(*self.class.always_permitted_parameters).permit!.to_h
end
......@@ -170,20 +180,17 @@ def to_h
# Returns an unsafe, unfiltered +Hash+ representation of this parameter.
def to_unsafe_h
to_hash
@parameters
end
alias_method :to_unsafe_hash, :to_unsafe_h
# 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)
super do |key, value|
convert_hashes_to_parameters(key, value)
@parameters.each_pair do |key, value|
yield key, convert_hashes_to_parameters(key, value)
end
super
end
alias_method :each, :each_pair
# Attribute that keeps track of converted arrays, if any, to avoid double
......@@ -347,7 +354,11 @@ def permit(*filters)
# params[:person] # => {"name"=>"Francesco"}
# params[:none] # => nil
def [](key)
convert_hashes_to_parameters(key, super)
convert_hashes_to_parameters(key, @parameters[key])
end
def []=(key, value)
@parameters[key] = value
end
# Returns a parameter for the given +key+. If the +key+
......@@ -361,8 +372,12 @@ def [](key)
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
def fetch(key, *args)
convert_hashes_to_parameters(key, super, false)
def fetch(key, *args, &block)
convert_hashes_to_parameters(
key,
@parameters.fetch(key, *args, &block),
false
)
rescue KeyError
raise ActionController::ParameterMissing.new(key)
end
......@@ -375,7 +390,16 @@ def fetch(key, *args)
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {}
def slice(*keys)
new_instance_with_inherited_permitted_status(super)
new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
end
def slice!(*keys)
@parameters.slice!(*keys)
self
end
def except(*keys)
new_instance_with_inherited_permitted_status(@parameters.except(*keys))
end
# Removes and returns the key/value pairs matching the given keys.
......@@ -384,7 +408,7 @@ def slice(*keys)
# params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
# params # => {"c"=>3}
def extract!(*keys)
new_instance_with_inherited_permitted_status(super)
new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
end
# Returns a new <tt>ActionController::Parameters</tt> with the results of
......@@ -393,36 +417,70 @@ def extract!(*keys)
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.transform_values { |x| x * 2 }
# # => {"a"=>2, "b"=>4, "c"=>6}
def transform_values
if block_given?
new_instance_with_inherited_permitted_status(super)
def transform_values(&block)
if block
new_instance_with_inherited_permitted_status(
@parameters.transform_values(&block)
)
else
super
@parameters.transform_values
end
end
def transform_values!(&block)
@parameters.transform_values!(&block)
self
end
# This method is here only to make sure that the returned object has the
# correct +permitted+ status. It should not matter since the parent of
# this object is +HashWithIndifferentAccess+
def transform_keys # :nodoc:
if block_given?
new_instance_with_inherited_permitted_status(super)
def transform_keys(&block) # :nodoc:
if block
new_instance_with_inherited_permitted_status(
@parameters.transform_keys(&block)
)
else
super
@parameters.transform_keys
end
end
def transform_keys!(&block)
@parameters.transform_keys!(&block)
self
end
# 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)
convert_hashes_to_parameters(key, super, false)
convert_hashes_to_parameters(key, @parameters.delete(key), false)
end
def select(&block)
new_instance_with_inherited_permitted_status(@parameters.select(&block))
end
# Equivalent to Hash#keep_if, but returns nil if no changes were made.
def select!(&block)
convert_value_to_parameters(super)
@parameters.select!(&block)
self
end
alias_method :keep_if, :select!
def reject(&block)
new_instance_with_inherited_permitted_status(@parameters.reject(&block))
end
def reject!(&block)
@parameters.reject!(&block)
self
end
alias_method :delete_if, :reject!
def values_at(*keys)
convert_value_to_parameters(@parameters.values_at(*keys))
end
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
......@@ -439,6 +497,18 @@ def dup
end
end
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
# pass +Parameters+ to a mass assignment methods in a model.
def stringify_keys
dup
end
protected
def permitted=(new_permitted)
@permitted = new_permitted
......@@ -453,7 +523,7 @@ def new_instance_with_inherited_permitted_status(hash)
def convert_hashes_to_parameters(key, value, assign_if_converted=true)
converted = convert_value_to_parameters(value)
self[key] = converted if assign_if_converted && !converted.equal?(value)
@parameters[key] = converted if assign_if_converted && !converted.equal?(value)
converted
end
......@@ -482,7 +552,8 @@ def each_element(object)
end
def fields_for_style?(object)
object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
(object.is_a?(Hash) || object.is_a?(Parameters)) &&
object.to_unsafe_h.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
end
def unpermitted_parameters!(params)
......@@ -571,7 +642,7 @@ def hash_filter(params, filter)
else
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
if element.is_a?(Hash)
if element.is_a?(Hash) || element.is_a?(Parameters)
element = self.class.new(element) unless element.respond_to?(:permit)
element.permit(*Array.wrap(filter[key]))
end
......
......@@ -171,6 +171,10 @@ def url_for(options = nil)
route_name = options.delete :use_route
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
route_name)
when ActionController::Parameters
route_name = options.delete :use_route
_routes.url_for(options.to_unsafe_h.symbolize_keys.
reverse_merge!(url_options), route_name)
when String
options
when Symbol
......
......@@ -7,7 +7,7 @@ class UsersController < ActionController::API
wrap_parameters :person, format: [:json]
def test
self.last_parameters = params.except(:controller, :action)
self.last_parameters = params.except(:controller, :action).to_unsafe_h
head :ok
end
end
......
......@@ -136,7 +136,7 @@ def assert_filtered_out(params, key)
authors_attributes: {
:'0' => { name: 'William Shakespeare', age_of_death: '52' },
:'1' => { name: 'Unattributed Assistant' },
:'2' => { name: %w(injected names)}
:'2' => { name: %w(injected names) }
}
}
})
......
......@@ -253,7 +253,6 @@ def assert_filtered_out(params, key)
assert @params.to_h.is_a? Hash
assert_not @params.to_h.is_a? ActionController::Parameters
assert_equal @params.to_hash, @params.to_h
end
test "to_h returns converted hash when .permit_all_parameters is set" do
......@@ -284,6 +283,5 @@ def assert_filtered_out(params, key)
test "to_unsafe_h returns unfiltered params" do
assert @params.to_h.is_a? Hash
assert_not @params.to_h.is_a? ActionController::Parameters
assert_equal @params.to_hash, @params.to_unsafe_h
end
end
......@@ -45,7 +45,7 @@ def render_body
end
def test_params
render text: ::JSON.dump(params)
render text: ::JSON.dump(params.to_unsafe_h)
end
def test_query_parameters
......
......@@ -14,7 +14,13 @@ def assign_parameters
def dump_params_keys(hash = params)
hash.keys.sort.inject("") do |s, k|
value = hash[k]
value = Hash === value ? "(#{dump_params_keys(value)})" : ""
if value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
value = "(#{dump_params_keys(value)})"
else
value = ""
end
s << ", " unless s.empty?
s << "#{k}#{value}"
end
......
......@@ -113,7 +113,7 @@ class << self
def parse
self.class.last_request_parameters = request.request_parameters
self.class.last_parameters = params
self.class.last_parameters = params.to_unsafe_h
head :ok
end
end
......
......@@ -91,6 +91,16 @@ def url_for(options = nil)
end
end
super(options)
when ActionController::Parameters
unless options.key?(:only_path)
if options[:host].nil?
options[:only_path] = _generate_paths_by_default
else
options[:only_path] = false
end
end
super(options)
when :back
_back_url
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册