aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXavier Noria <fxn@hashref.com>2016-11-08 09:58:58 +0100
committerXavier Noria <fxn@hashref.com>2016-11-11 23:31:50 +0100
commite86524c0c5a26ceec92895c830d1355ae47a7034 (patch)
tree35bed8b96f65678e5da98af61262dc0f4368bd91
parenta5e933410dcbf097c5f180ec7ce9b3567a9e3514 (diff)
downloadrails-e86524c0c5a26ceec92895c830d1355ae47a7034.tar.gz
rails-e86524c0c5a26ceec92895c830d1355ae47a7034.tar.bz2
rails-e86524c0c5a26ceec92895c830d1355ae47a7034.zip
adds support for arbitrary hashes in strong parameters
-rw-r--r--actionpack/CHANGELOG.md8
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb45
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb33
-rw-r--r--guides/source/action_controller_overview.md18
4 files changed, 101 insertions, 3 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index d4b8d9b456..b539c893e3 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -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*
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 98aacd53ab..fc9cec1e33 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -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
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 2b9a95d17c..16637d330b 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -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
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 7b1138c7d4..40eb838d32 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -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