-
Notifications
You must be signed in to change notification settings - Fork 21.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Strong parameters: allow hashes with unknown keys to be permitted #9454
Comments
Another possibility which I think I like even better would be to allow
|
I am really interested in a way to solve this as well, so +1. I think your 2. approach looks cleaner to me as well, passing {} to permit all sub keys looks weird to me. |
strong parameters is not designed to handle every possible situation. We wanted to keep the API on point to handle the most common situations. As assigning a hash with unknown keys more or less defeates the purpose of strong parameters (restricting allowed keys), It's not supported because it could make your code vulnerable. As you mentioned already you can simply fall back on a normal assignment if you know that you want to permit unknown keys. /cc @fxn @rafaelfranca |
Confirm, the API is designed to whitelist every single key. If your use case does not fit well then you need to resort to Ruby. That flexibility is also by design, in the end it is just Ruby. |
Understand, but this doesn't exactly seem like an uncommon use case (particularly with the introduction of Hstore and JSON types), and with I would argue that this actually makes the API more consistent, since
Except you'd only be permitting an unknown hash on a single key within the params. It's certainly more secure than doing a
My main problem with this is that I go from having to test one message ( Here's a concrete example of how my proposal would improve things. Before: class ProductsController < ApplicationController
def create
@product = Product.new(product_params)
@product.data = params[:product][:data]
@product.save!
end
def update
@product = Product.find(params[:id])
@product.data = params[:product][:data]
@product.update!(product_params)
end
private
def product_params
params.require(:product).permit(:name)
end
end After: class ProductsController < ApplicationController
def create
@product = Product.create!(product_params)
end
def update
@product = Product.find(params[:id])
@product.update!(product_params)
end
private
def product_params
params.require(:product).permit(:name).permit!(:data)
end
end I do think this needs more discussion. It's all well and good to say just do it manually, but it feels wrong to have to work around a feature (and make my code more complicated), particularly when the alternatives have been removed. |
To avoid duplication set the trusted parameter in the helper (untested): def product_params
params.require(:product).permit(:name).tap do |whitelisted|
whitelisted[:data] = params[:product][:data]
end
end |
Thanks @fxn. That is a reasonable solution I had not thought of. :) |
@spohlenz awesome :), this feedback was useful we are going to document a few edge cases like this or similar. |
The current ActionController guide does not mention strong parameters at all. I integrated the README into the guide to explain the API. I also included a section to illustrate that the API does not solve all possible whitelisting scenarios. The origin was rails#9454.
@senny It may be helpful to have the Strong Parameters README mirror the last paragraph on using with Hstore and JSON data types. Latter ends up being a more obvious reference point thanks to Google. |
@hakanensari I'm not sure if duplicating the guides in the README is a good thing. They will get out of sync quickly. I'd like to keep the additional examples in the guides because they are the reference for Rails. Maybe we could link from the README to the relevant section in the guides? (http://guides.rubyonrails.org/action_controller_overview.html#more-examples) @fxn what do you think? |
@senny definitely linking. |
The solution mentioned by @fxn only works if you have decided to log instead of raise when un-permitted parameters are present. There are plenty of situations (metadata, configuration, etc.) where whitelisting a sub-hash is completely valid. I think that @spohlenz solution might be the most similar and I will have to implement something like it. |
I like this approach #12609. |
that's a sufficient workaround but hardly a good solution. what about |
Yes, I believe this use case deserves API. Something like |
fyi, the approach in #12609 is not feasible for hashes of a depth greater than 2. |
👍 This has also come up with us as an issue for supporting HStore metadata values on our models. Also, and perhaps I'm doing something wrong, but the solution from @fxn seems to actually set nil values on the params if they aren't already set: params = ActionController::Parameters.new({ test: true })
params.permit(:test).tap do |whitelisted|
whitelisted[:more] = params[:more]
end
# => {"test"=>true, "more"=>nil} |
@jhubert that definitely makes sense. |
@lanej Thank. Yeah, that makes sense. It was unexpected behavior for me and my tests failed because the parameter was being set to nil instead of being unprovided. Figured I would mention it in case other people just copy / paste it expecting it to just permit the variable if it was provided and not change the params themselves. |
@jhubert FWIW, [1] pry(main)> p = ActionController::Parameters.new({test: true})
=> {"test"=>true}
[2] pry(main)> p[:foo]
=> nil
[3] pry(main)> p.fetch :foo
ActionController::ParameterMissing: param is missing or the value is empty: foo # ....
[4] pry(main)> p.fetch :foo, nil
=> nil |
thanks, this works for me: def product_params
params.require(:product).permit(:name).tap do |while_listed|
while_listed[:data] = params[:product][:data]
end
end |
i think this would be a neat feature to add. but maybe there is a better way to do that? idk. ('usually is) |
My company doesn't use the key value of the nested we expect the nested hash to have and id property if its persisted for example |
@chinshr You're right! thanks for pointing this case. |
Thanks, @koenpunt. From the server logs, it looks as though the JSON I submit to the server (ie. as I originally presented it):
is getting converted by Rails to
I just need to figure out why... |
Hmmm, seems the issue is in the JS before the AJAX call, sorry. jQuery needs contentType to be set to "application/json". See http://stackoverflow.com/questions/6410810/rails-not-decoding-json-from-jquery-correctly-array-becoming-a-hash-with-intege |
Hey @fxn and @senny, it's the year of 2016 now. Rails got background jobs and even websockets, things that were originally neglected/ignored. Since 2013, the use of free-form JSON and Hstore values has rapidly grown too, and maybe it's high time to not ignore them either. The values submitted to them are free-form by design, therefore not subject to any whitelisting/filtration. But they don't live alone. They live inside models whose fields are whitelisted and filtered against mass assignment. Consider Stripe gateway. They allow users to store free-form metadata on customer object, and that's just a hash. How to achieve that in Rails if the JSON request looks like this: POST /customers, {description: ..., source: ..., metadata: {... free form data here...}}. Answer: fight the framework - and many people in this thread did it their own way. Currently, if there's any JSON field without a predefined "schema" inside the params, Strong Parameters has to be worked around for each field. In 2016, letting a non-checked JSON in is a common situation. There's already @sdepold's pull request to allow this here: rails/strong_parameters#231. I hope you can reevaluate this feature and give it a green light. If there's any issue/feedback about that PR, I can take it over from the original submitter and finish it up if he's unreachable. Please let me know. |
@Nowaker agree, we've been conservative, but time says this use-case deserves a dedicated API. The The patch is no big deal, I might write one based on that PR (and give credit of course). |
Implemented here. |
it would be nice if it also allow to set structure incoming hash, like a params.permit(preferences: [{:scheme, font: {:name, :size}}]) for next cases in params: params = ActionController::Parameters.new(
username: "fxn",
preferences: [
{
scheme: "Marazul",
font: {
name: "Source Code Pro",
size: 12
}
},
{
scheme: "new scheme",
font:
{
name: "Another Font",
size: 14
}
}
]) |
I must be missing something, but Rails 5.0.1 still gives me grief:
|
Patch versions don't have new features, this is going to come with 5.1. |
@aliibrahim thanks, that worked for me! |
I can't see a solution to this that has been merged. I have the following input hash (obfuscated sample):
(Edited) I want to whitelist all the keys in Can someone point me in the right direction? |
@stevebissett The right direction would be for |
@Nowaker I can't turn that into a hash, because I need multiple of them, (edited slightly). |
|
Rails 5 workaround - def product_params
load_params = params.require(:product).permit(:name)
load_params[:json_data] = params[:product][:json_data]
load_params.permit!
end I realize this doesn't whitelist the data within the json_data param, but it excludes same-level unpermitted params. Oh and hi @stevebissett 👍 |
@spohlenz i have faced similer problem and after lot of googling and going through various stackoverflow Q/A was still not able to figure out good solution. So, i tried myself some hack. Advantages
|
The @olmesm solution works for Rails |
This thread started quite a while ago, but it's still active, so here's something I just discovered. I was able to permit a def user_params
params.require(:user).permit(:email, :password, data: [:one, :two, :three])
end Where |
Running on def user_params
params.require(:person).permit(:name, :description, :age, custom_attributes: [:key, :value])
end This will allow the following params to be permitted: {
"person": {
"name": "John",
"description": "has custom_attributes",
"age": 42,
"custom_attributes": [
{
"key": "the key",
"value": "the value"
}
]
}
} This can be seen in the source here:
If you don't know what might be in def user_params
params.require(:person).slice(:name, :description, :age, :custom_attributes)
end |
In the majority of these comments, the keys are known. This thread's subject was for unknown keys. A few of the better workarounds are:
Fix is coming in 5.1.2 e86524c |
need to add a if condition otherwise it add a nil value for json_data, which causes issue if you call but good workaround, thank you! |
@anklos your example work, saves the data correctly (thx alot) but It still complains about the concerned json column being unpermitted. Is that normal ? |
@deesx I would like to know the answer to that also. 50+ rep bonus for the answer |
@olmesm , @anklos ,
works for most of my data, except when my json_data looks like this
I get the following error. I am using Mongoid by the way.
|
Perhaps this is overkill, but one simple workaround that I didn't see here is as follows:
The other workarounds don't appear to work for arbitrarily nested hashes and arrays. |
Since the ability to whitelist arbitrary hashes was added (rails#9454 was resolved by rails@e86524c), this example is no longer outside of what strong_params can do. Moved this specific example out of the "Outside the Scope" section and into the regular "Examples" section, but left the "Outside the Scope" section as it was since the advice is still relevant for weirder whitelisting situations (maybe someone wants to add a new example that can't be handled natively).
From what I can tell, strong parameters currently has no ability to permit a hash with unknown keys. Where this would be particularly useful is for the newly supported Hstore and JSON data types -- a good example of this use case can be found here: http://schneems.com/post/19298469372/you-got-nosql-in-my-postgres-using-hstore-in-rails
I would have expected that passing an empty hash as an permitted value would allow a hash to be passed through. e.g.
however this does not pass the data hash through (though it is not documented that it should work).
Assigning the data parameter separately is an option but it complicates my code unnecessarily -- I would prefer to be able to stick with mass-assignment for all attributes.
Happy to work on a patch for this if this proposal is reasonable. I've only just started looking into strong parameters though so there may be drawbacks I haven't considered.
The text was updated successfully, but these errors were encountered: