mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
adds support for arbitrary hashes in strong parameters
This commit is contained in:
parent
a5e933410d
commit
e86524c0c5
4 changed files with 101 additions and 3 deletions
|
@ -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!`.
|
* Add `ActionController::Parameters#merge!`, which behaves the same as `Hash#merge!`.
|
||||||
|
|
||||||
*Yuji Yaginuma*
|
*Yuji Yaginuma*
|
||||||
|
|
|
@ -334,6 +334,15 @@ module ActionController
|
||||||
# params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
|
# params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
|
||||||
# params.permit(tags: [])
|
# 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:
|
# You can also use +permit+ on nested parameters, like:
|
||||||
#
|
#
|
||||||
# params = ActionController::Parameters.new({
|
# params = ActionController::Parameters.new({
|
||||||
|
@ -766,6 +775,7 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
EMPTY_ARRAY = []
|
EMPTY_ARRAY = []
|
||||||
|
EMPTY_HASH = {}
|
||||||
def hash_filter(params, filter)
|
def hash_filter(params, filter)
|
||||||
filter = filter.with_indifferent_access
|
filter = filter.with_indifferent_access
|
||||||
|
|
||||||
|
@ -779,6 +789,11 @@ module ActionController
|
||||||
array_of_permitted_scalars?(self[key]) do |val|
|
array_of_permitted_scalars?(self[key]) do |val|
|
||||||
params[key] = val
|
params[key] = val
|
||||||
end
|
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)
|
elsif non_scalar?(value)
|
||||||
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
|
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
|
||||||
params[key] = each_element(value) do |element|
|
params[key] = each_element(value) do |element|
|
||||||
|
@ -788,6 +803,36 @@ module ActionController
|
||||||
end
|
end
|
||||||
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)
|
def initialize_copy(source)
|
||||||
super
|
super
|
||||||
@parameters = @parameters.dup
|
@parameters = @parameters.dup
|
||||||
|
|
|
@ -168,6 +168,39 @@ class ParametersPermitTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
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
|
test "fetch raises ParameterMissing exception" do
|
||||||
e = assert_raises(ActionController::ParameterMissing) do
|
e = assert_raises(ActionController::ParameterMissing) do
|
||||||
@params.fetch :foo
|
@params.fetch :foo
|
||||||
|
|
|
@ -258,6 +258,17 @@ scalar values, map the key to an empty array:
|
||||||
params.permit(id: [])
|
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
|
To whitelist an entire hash of parameters, the `permit!` method can be
|
||||||
used:
|
used:
|
||||||
|
|
||||||
|
@ -265,9 +276,10 @@ used:
|
||||||
params.require(:log_entry).permit!
|
params.require(:log_entry).permit!
|
||||||
```
|
```
|
||||||
|
|
||||||
This will mark the `:log_entry` parameters hash and any sub-hash of it as
|
This marks the `:log_entry` parameters hash and any sub-hash of it as
|
||||||
permitted. Extreme care should be taken when using `permit!`, as it
|
permitted and does not check for permitted scalars, anything is accepted.
|
||||||
will allow all current and future model attributes to be mass-assigned.
|
Extreme care should be taken when using `permit!`, as it will allow all current
|
||||||
|
and future model attributes to be mass-assigned.
|
||||||
|
|
||||||
#### Nested Parameters
|
#### Nested Parameters
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue