adds support for arbitrary hashes in strong parameters

This commit is contained in:
Xavier Noria 2016-11-08 09:58:58 +01:00
parent a5e933410d
commit e86524c0c5
4 changed files with 101 additions and 3 deletions

View File

@ -1,3 +1,11 @@
* Add support for arbitrary hashes in strong parameters:
```ruby
params.permit(preferences: {})
```
*Xavier Noria*
* Add `ActionController::Parameters#merge!`, which behaves the same as `Hash#merge!`.
*Yuji Yaginuma*

View File

@ -334,6 +334,15 @@ module ActionController
# params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
# params.permit(tags: [])
#
# Sometimes it is not possible or convenient to declare the valid keys of
# a hash parameter or its internal structure. Just map to an empty hash:
#
# params.permit(preferences: {})
#
# but be careful because this opens the door to arbitrary input. In this
# case, +permit+ ensures values in the returned structure are permitted
# scalars and filters out anything else.
#
# You can also use +permit+ on nested parameters, like:
#
# params = ActionController::Parameters.new({
@ -766,6 +775,7 @@ module ActionController
end
EMPTY_ARRAY = []
EMPTY_HASH = {}
def hash_filter(params, filter)
filter = filter.with_indifferent_access
@ -779,6 +789,11 @@ module ActionController
array_of_permitted_scalars?(self[key]) do |val|
params[key] = val
end
elsif filter[key] == EMPTY_HASH
# Declaration { preferences: {} }
if value.is_a?(Parameters)
params[key] = permit_any_in_parameters(value)
end
elsif non_scalar?(value)
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
@ -788,6 +803,36 @@ module ActionController
end
end
def permit_any_in_parameters(params)
self.class.new.tap do |sanitized|
params.each do |key, value|
if permitted_scalar?(value)
sanitized[key] = value
elsif value.is_a?(Array)
sanitized[key] = permit_any_in_array(value)
elsif value.is_a?(Parameters)
sanitized[key] = permit_any_in_parameters(value)
else
# Filter this one out.
end
end
end
end
def permit_any_in_array(array)
[].tap do |sanitized|
array.each do |element|
if permitted_scalar?(element)
sanitized << element
elsif element.is_a?(Parameters)
sanitized << permit_any_in_parameters(element)
else
# Filter this one out.
end
end
end
end
def initialize_copy(source)
super
@parameters = @parameters.dup

View File

@ -168,6 +168,39 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
end
test "key to empty hash: arbitrary hashes are permitted" do
params = ActionController::Parameters.new(
username: "fxn",
preferences: {
scheme: "Marazul",
font: {
name: "Source Code Pro",
size: 12
},
tabstops: [4, 8, 12, 16],
suspicious: [true, Object.new, false, /yo!/],
dubious: [{a: :a, b: /wtf!/}, {c: :c}],
injected: Object.new
},
hacked: 1 # not a hash
)
permitted = params.permit(:username, preferences: {}, hacked: {})
assert_equal "fxn", permitted[:username]
assert_equal "Marazul", permitted[:preferences][:scheme]
assert_equal "Source Code Pro", permitted[:preferences][:font][:name]
assert_equal 12, permitted[:preferences][:font][:size]
assert_equal [4, 8, 12, 16], permitted[:preferences][:tabstops]
assert_equal [true, false], permitted[:preferences][:suspicious]
assert_equal :a, permitted[:preferences][:dubious][0][:a]
assert_equal :c, permitted[:preferences][:dubious][1][:c]
assert_filtered_out permitted[:preferences][:dubious][0], :b
assert_filtered_out permitted[:preferences], :injected
assert_filtered_out permitted, :hacked
end
test "fetch raises ParameterMissing exception" do
e = assert_raises(ActionController::ParameterMissing) do
@params.fetch :foo

View File

@ -258,6 +258,17 @@ scalar values, map the key to an empty array:
params.permit(id: [])
```
Sometimes it is not possible or convenient to declare the valid keys of
a hash parameter or its internal structure. Just map to an empty hash:
```ruby
params.permit(preferences: {})
```
but be careful because this opens the door to arbitrary input. In this
case, `permit` ensures values in the returned structure are permitted
scalars and filters out anything else.
To whitelist an entire hash of parameters, the `permit!` method can be
used:
@ -265,9 +276,10 @@ used:
params.require(:log_entry).permit!
```
This will mark the `:log_entry` parameters hash and any sub-hash of it as
permitted. Extreme care should be taken when using `permit!`, as it
will allow all current and future model attributes to be mass-assigned.
This marks the `:log_entry` parameters hash and any sub-hash of it as
permitted and does not check for permitted scalars, anything is accepted.
Extreme care should be taken when using `permit!`, as it will allow all current
and future model attributes to be mass-assigned.
#### Nested Parameters