rails params validation in controller - ruby-on-rails

Is there a best practice to validate params in a controller?
#user = User.find_by_id(params[:id])
If I tamper with the param to give it an invalid :id param, say by visiting "/users/test", I can generate the following error:
Conversion failed when converting the nvarchar value 'test' to data type int.
I am thinking right now of params that won't go straight to a model and can be validated by model validations.

Yes you should always validate your parameters. People can always mess around with the parameters in their web browser's address bar, or modify parameters stored in the DOM. Another example where parameters can be screwed up is if the webpage is left open a long time. Imagine someone is viewing the page "/users/3/edit" and leaves it open for an hour, then hits refresh. In the mean time that user may have been deleted. You don't want your website to crash - it should handle that gracefully.
Depending on your database and adapter, doing User.find_by_id("test") will not crash. But your database/adapter was not able to convert the string in to an integer. One thing you can do in this particular case is use Ruby's .to_i method.
User.find_by_id(params[:id].to_i)
If params[:id] = "12", Ruby will convert that to the integer 12 and the code will run fine. If params[:id] = "test", Ruby will convert that to the integer 0, and you should never have a database record with an ID of 0.
You can also use regular expressions to test if a string is an integer.
But in general, yes, try to always validate your parameters so you can handle errors gracefully and control the data coming in.

Related

Can't modify frozen String on Hash

I am a bit confused with the frozen string and utilizing them with test cases.
I just added the following line at the top of my test cases :
# frozen_string_literal: true
And i have the following two test cases:
test "Create upload invoice invalid invoice id" do
post :upload, params = {invoices_data: [{invoice_id: 987654, unit_id: 1321}]}
assert_response :not_found
end
test "Create upload invoice request to fortnox with non array request parameter" do
request = {invoices_data: {invoice_id: "invoice.id", unit_id: 321}}
post :upload_invoices, params = request
assert_response :bad_request
end
All of a sudden my second test failed with
RuntimeError: can't modify frozen String
at this line
post :upload_invoices, params = request
however, if I change the string to some symbol for instance :invoice_id then it works just fine.
Can someone guide why about the following two things:
Why does sending a string value fails in this case reporting that I
am trying to modify a String and which string value I am trying to
modify?
Why does it fail on post request, if it has to fail then it
should fail when creating the request i.e request = {invoices_data: {invoice_id: "invoice.id", unit_id: 321}}
What i can do to send string value instead of Symbol in the hash?
1a) Sending a string value fails in this case because your upload_invoices controller action attempts to modify the invoice_id parameter itself. (Or you're running an old version of Rails where the #post method itself attempts to modify the invoice_id parameter by converting it to UTF-8 encoding.)
1b) The string value you're trying to modify is "invoice.id".
2 ) It fails on the post request and not the assignment to the request variable because the assignment to the request variable is not where the attempted modification happens. The frozen string literal is attempted to be modified by the call to #post. See answer 1a above.
3 ) You can send a non-frozen string value in the hash a few different ways. You could remove the # frozen_string_literal: true magic comment, but I feel you don't want to do that. Otherwise, the simplest thing to do is to send along a duplicate of the string with either +'invoice.id' or the less esoteric 'invoice.id'.dup. Or you can create a non-literal string with something silly like ['invoice', 'id'].join('.') or :invoice.to_s. No doubt there are other ways.
However, it seems EXTREMELY unlikely you want to pass a string here at all. The invoice_id parameter is almost assuredly an integer, and passing a string to it makes little sense unless I guess you're trying to test that the controller action can handle that kind of erroneous input. If so, one of the string duplication techniques +'string_literal'/'string_literal'.dup would be your best option.
I would wager by the name of the test that you're actually trying to send along a real invoice_id which means you don't want to pass along a string, but instead an integer. Maybe the ID of an Invoice fixture you have setup?
And on another slightly unrelated note, you're not passing params to the #post method properly. It should be params: ... not params = ....

Unpermitted parameters issue Ruby on Rails 5

I'm currently trying to understand how permitted parameters works in ruby.
Usually, in my_model.rb I have:
has_many: some_other_model
*
*
*
def my_model_params
params.require(:my_model).permit( :column1, some_other_model_attributes %i[other_column1])
etc...
and in the update function
my_object.update_attributes(my_model_params)
with a well formatted json which has some my_model root, and some_other_model_attributes as a child (array) with values.
My problem is I receive a json like this one
However the different arrays inside (such as codification, general_information) do contain attributes of the mission (general_information contains reference that is a column in the mission table) but there isn't any column named codification, or relation to a codification_attributes.
So, when I add :
general_information: %i[reference] in the permitted params, it says unknown attribute 'general_information' for Mission.
If not, no error are raised but in the log I can see unpermitted_parameter: general_information. And my object is not updated.
Finally if I reject it, there is no more unpermitted_parameter: general_information in the log but my object is not updated either.
I tried to set config.action_controller.action_on_unpermitted_parameters to false in my development config, it did nothing and it's probably a bad idea for production environment anyway.
The use of .permit! (even if it works) is currently not an option. And even though I think the json needs to be re-formatted it'd be better to find an other solution.
Thanks for the help.
unpermitted_parameter: ... in logs in not a problem which you need to fix, it's just an info.
How it works - you just permit needed parameters, you may think about them as a hash. Unpermitted parameters will not go into the model even if they are present in params. It means when you call
my_object.update_attributes(my_model_params)
it works like
my_object.update_attributes(column1: value_for_column1_from_params)
Keys in params should be named exactly as columns in the model, otherwise you need to prepare params somehow before create/update

Rails SQL Injection: How vulnerable is this code?

I'm trying to understand SQL Injection. It seems like people can get pretty creative. Which gets me wondering about my search-based rails webapp I'm making.
Suppose I just fed user-entered information directly into the "where" statement of my SQL query. How much damage could be done to my database by allowing this?
def self.search(search)
if search
includes(:hobbies, :addresses).where(search)
else
self.all
end
So basically, whatever the user types into the search bar on the home page gets fed straight into that 'where' statement.
An example of a valid 'search' would be:
"hobby LIKE ? OR (gender LIKE ? AND hobby LIKE ?)", "golf", "male", "polo"
Does the fact that it's limited to the context of a 'where' statement provide any sort of defense? Could they still somehow perform delete or create operations?
EDIT:
When I look at this tutorial, I don't see a straightforward way to perform a deletion or creation action out of the where clause. If my database contains no information that I'm not willing to display from a valid search result, and there's no such thing as user accounts or admin privileges, what's really the danger here?
I took this from another post here: Best way to go about sanitizing user input in rails
TL;DR
Regarding user input and queries: Make sure to always use the active record query methods (such as .where), and avoid passing parameters using string interpolation; pass them as hash parameter values, or as parameterized statements.
Regarding rendering potentially unsafe user-generated html / javascript content: As of Rails 3, html/javascript text is automatically properly escaped so that it appears as plain text on the page, rather than interpreted as html/javascript, so you don't need to explicitly sanitize (or use <%= h(potentially_unsafe_user_generated_content)%>
If I understand you correctly, you don't need to worry about sanitizing data in this manner, as long as you use the active record query methods correctly. For example:
Lets say our parameter map looks like this, as a result of a malicious user inputting the following string into the user_name field:
:user_name => "(select user_name from users limit 1)"
The bad way (don't do this):
Users.where("user_name = #{params[:id}") # string interpolation is bad here
The resulting query would look like:
SELECT users.* FROM users WHERE (user_name = (select user_name from users limit 1))
Direct string interpolation in this manner will place the literal contents of the parameter value with key :user_name into the query without sanitization. As you probably know, the malicious user's input is treated as plain 'ol SQL, and the danger is pretty clear.
The good way (Do this):
Users.where(id: params[:id]) # hash parameters
OR
Users.where("id = ?", params[:id]) # parameterized statement
The resulting query would look like:
SELECT users.* FROM users WHERE user_name = '(select user_name from users limit 1)'
So as you can see, Rails in fact sanitizes it for you, so long as you pass the parameter in as a hash, or method parameter (depending on which query method you're using).
The case for sanitization of data on creating new model records doesn't really apply, as the new or create methods are expecting a hash of values. Even if you attempt to inject unsafe SQL code into the hash, the values of the hash are treated as plain strings, for example:
User.create(:user_name=>"bobby tables); drop table users;")
Results in the query:
INSERT INTO users (user_name) VALUES ('bobby tables); drop table users;')
So, same situation as above.
I hope that helps. Let me know if I've missed or misunderstood anything.
Edit Regarding escaping html and javascript, the short version is that ERB "escapes" your string content for you so that it is treated as plain text. You can have it treated like html if you really want, by doing your_string_content.html_safe.
However, simply doing something like <%= your_string_content %> is perfectly safe. The content is treated as a string on the page. In fact, if you examine the DOM using Chrome Developer Tools or Firebug, you should in fact see quotes around that string.

Rails ActiveRecord callbacks

I'm having an issue with a date format. I have a time picker that has the date in a funky format (well, it's a nice format, actually, but not to the computer). I'm trying to have Chronic parse the date so that it can be saved properly.
At first, I was doing, in the create action of my controller:
params[:event][:start] = Chronic.parse(params[:event][:start])
but if and when validation fails, it sends the parsed value back to the view, and my datetimepicker is all botched, then.
So, I thought... callback? In my model, I added:
private
def date_change
self.start = Chronic.parse(self.start)
end
I tried before_save, before_validation, after_validation... but nothing seems to get that date formatted correctly.
As it stands, I keep getting ArgumentError in EventsController#create - Argument out of range. I assume that's because the database is expecting a properly formatted datetime object.
Any idea on how I can accomplish my goal, here, of not changing the params, but still being able to save a properly formatted object?
I'm guessing that the problem is occurring the the start= mutator method that ActiveRecord supplies. If you're doing things like this in your controller:
#event.update_attributes(params[:events])
#event = Event.create(params[:event])
#...
then create and update_attributes should call start= internally. That should allow you to put the Chronic stuff in your own start=:
def start=(t)
super(Chronic.parse(t))
end
You might need to adjust that for non-String ts, I'm not sure what Chronic.parse(Time.now), for example, would do. You could also call write_attribute(:start, Chronic.parse(t)) or self[:start] = Chronic.parse(t) if you didn't want to punt to super.
Note that before_validation and similar handlers will be called too late to bypass whatever default string-to-timestamp conversion ActiveRecord is doing but a mutator override should happen at the right time.
Alternatively, you could parse the time in the controller with something like this:
event = params[:events].dup
events[:start] = Chronic.parse(events[:start])
#event = Event.create(event)
Assumption is the mother of all mess ups :)
are you sure the callback is hit? Because if it would, and the error occurred (like it did), wouldn't it still send back the incorrect data (because parsed) back to the view? In case of doubt: log something to make sure it is hit.
are you sure which field causes the Argument out of range error.
Most cases bugs are so hard to find/fix because we assume we know the error, but we are looking at the error in the wrong way.
Easy ways to test which attribute causes the error:
open rails console, build an object with the parameters, save it, and ask the errors. Something like
e = Event.new(params[:event]) # copy params verbatim from your logfile
e.save
e.errors
and that will display which field causes the error.
Alternatively: use pry and add a line binding.pry just after the save, so you inspect the errors (more info)
Answer (assuming your assumption was correct)
I see two options to do what you want:
use the after_validation callback, if you are sure the data will always be correct, and correctly parsed by Chronic. This way if validation is passed, then convert the field and normally nothing can go wrong anymore, and the value is never sent to the browser again.
Note: if some other attribute is causing the error, this callback is never hit, of course. Because it does not pass the validation.
use a virtual attribute, e.g. start_str, which is a visual representation of your start, and
before_save convert it to start. It does not really matter that much here, because if validation fails, you just show start_str and not the "real" start field.

Ruby on Rails - passing hashes

I have a piece of controller code where some values are calculated. The result is in the form of an array of hashes. This needs to get into a partial form somehow so that it may be retrieved later during commit (which is through the Submit button).
The questions is how do we pass the array of hashes?
thanks.
Is there a reason it has to be through the form? This is the type of thing I usually use the session for.
I can't really think of a nice way to do what you're asking with forms. I guess you could create hidden fields for each key in your hash in the form with hidden_field_tag as an alternative. Then you run into problems translating it (what if a key's value is an array or another hash?).
You could easily store the hash in the session and then on each page load, check to see if there is a hash where you expect it. On calculating values:
session[:expected_info] = results
And each page load, something like this:
if session.has_key?(:expected_info)
results = session.delete(:expected_info)
# you already calculated the results, just grab them and
# do what you need to do
else
# you don't have the expected info
end
You should be able to pass it as a string to your partial:
[{}].inspect
and eval it when it is submitted back through the form:
eval("[{}]"))
but that would be really dirty…

Resources