adds support for arbitrary hashes in strong parameters
This commit is contained in:
parent
a5e933410d
commit
e86524c0c5
|
@ -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*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue