Rails: form_for security hole? - ruby-on-rails

I have a user model with :name and :is_admin attributes. You should not change is_admin value. If you write a form in which any user may edit their name:
<%= form_for #user %>
<%= f.label :given_name %>
<%= f.text_field :given_name %>
<%= f.submit "Update" %>
<% end %>
Are you opening up a security hole?
Kind regards,

No.
This is because parameters are protected when they come into the controller by the strong parameters feature within Rails. In controllers now you define a create action like this:
def create
#user = User.new(user_params)
...
end
That user_params method looks like this:
def user_params
params.require(:user).permit(:name)
end
This code will permit the name parameter from the user parameters and outright reject everything else.
This is talked about in this section of the Getting Started guide.

Related

Filling additional form parameters from one input

I'm fairly new to Rails and can't find any information on exactly how to do this.
Currently, users create items by filling in a form with URL, Title, Content, etc.
#resource = Resource.new(resource_params)
.
.
.
def resource_params
params.require(:resource).permit(:title, :url, :content, :name, :tags_as_string)
end
I want users to be able to input only the URL, and generate the input for the rest of the parameters using the MetaInspector gem (https://github.com/jaimeiniesta/metainspector), but then be able to go back to the created item and edit its content manually.
Can somebody point me in the right direction? I have a feeling I need to create some kind of helper method, but this is the real first programming I've encountered in my project.
To prevent users from passing value of any field except url, you will need to remove all fields but url from your new resource form.
app/views/resources/new.html.erb
<%= form_for(#resource) do |f| %>
<%= f.text_field :url %>
<% end %>
And in your controller action create, permit only :url in params.
app/controllers/resources_controller.rb
def create
#resource = Resource.new(params.require(:resource).permit(:url))
# Set other attributes using `metainspector`. See documentation for usage.
if #resource.save
redirect_to resources_path
else
render :new
end
end
You can have a separate form (with all the fields) for editing a resource manually and a different set of permitted params for update action.
app/views/resources/edit.html.erb
<%= form_for(#resource) do |f| %>
<%= f.text_field :url %>
<%= f.text_field :title %>
<%= f.text_field :content %>
<!-- Add other editable fields here -->
<% end %>
app/controllers/resources_controller.rb
before_action :fetch_resource, only: [:edit, :update]
def update
if #resource.update_attributes(resource_params)
redirect_to resources_path
else
render :edit
end
end
private
def fetch_resource
# Fetch `Resource` instance from database. Homework for you.
end
def resource_params
params.require(:resource).permit(:title, :url, :content, :name, :tags_as_string)
end
Note: This code is not tested. It is just to give you hints on how you should proceed. You might have to change some method/field names to make them suit your application.

Ruby on Rails form param is missing or the value is empty. But value is set

I'm trying to submit a form in ruby on rails that i made, but keep getting de next error.
Ruby on Rails form: param is missing or the value is empty
my form
<%= form_for #test do |f| %>
<div class="field">
<%= f.text_field :first_name %><br>
<%= f.text_field :last_name %><br>
</div>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
my controller
def new
#test = Test.new
end
def create
#test = Test.new(allow_params)
if #test.save
redirect_to 'test/index'
else
render 'test/new'
end
end
private
def allow_params
params.require(:last_name).permit(:first_name)
end
my routes
resources :test
get 'test/index'
get 'test/new'
get 'test/create'
post '/tests' => 'test#create'
Your attributes are within the testlabel, so here you should go :
def allow_params
params.require(:test).permit(:first_name, :last_name)
end
Look, this is what you form posts when you click submit:
{"utf8"=>"✓","authenticity_token"=>"...", "test"=>"first_name"=>"poldo", "last_name"=>"de poldis"},"commit"=>"Create"}
As you can see first_name and last_name are inside an hash as value of a key called test. Indeed your function allow_params expects something like this
last_name: {first_name: 'poldo'}
as you can see the param (last_name) is missing, because is inside test!
The right way is as Ben answered:
params.require(:test).permit(:first_name, :last_name)
To understand better how strong parameters works I suggest to you to check this page Api doc or even better The latest version ofthe official manual

Strong params and action mailer issues

I have tried to create a mailer using the following code:
routes code
resources :listings do
member do
put :lead
end
end
mailer controller code
def lead(listing)
#listing = listing
mail(to: #listing.leadrecepient, subject: "test")
end
standard controller code
def lead
Enquiry.lead(#listing).deliver
end
view
<%= form_for lead_listing_path(#listing), method: :put do |listing| %>
<%= listing.text_field :name %>
<%= listing.submit %>
<% end %>
In the context of a business directory, I want it so that there is a enquiry form on each listing page that when filled out and submitted, the information is sent to the relative listing email.
The problem however is that when I type into the form and click submit, I get the following error:
param is missing or the value is empty: listing
This seems to be because I have it in the "listing" controller which controls the showing and creation of the business listing itself. I therefore have strong params for a new listing which contains all the new listing variables:
def listing_params
params.require(:listing).permit(:fullname, :jobtitle, :email, :franchisename, :leadrecepint, :shortdescription, :longdescription, :website, :branchcount, :scale, :mininvestment, :investmentrange, :category, :hexbg, :logourl, :facebook, :twitter, :linkedin, :googleplus, :approved)
end
How do I go about fixing this? I'm a beginner if I'm honest, could really do with some help to get this mailer working! Thanks.
Strong params are for when you are submitting new resources or modifications to resources. To protect against people adding extra parameters that may circumvent security or other aspects of your application unexpectedly.
If you are adding an action to an existing resource that the user is authorized to access, which this appears to be, you want to just find the object by ID, and use it. So instead of finding it using the params filtered through listing_params, just find it like this in the controller:
def lead
listing = Listing.find(params[:id])
Enquiry.lead(listing).deliver
redirect_to listing
end
And invoke it using a simple link, instead of this:
<%= form_for lead_listing_path(#listing), method: :put do |listing| %>
<%= listing.text_field :name %>
<%= listing.submit %>
<% end %>
Just use this in your view:
= link_to 'Go!', lead_listing_path(#listing), method: :put
Nothing more to it.

How does rails params are created?

I'm having a trouble when I'm trying to user params.require(...).permit(...)
In my application I received the follow param dic:
{"utf8"=>"✓",
"authenticity_token"=>"Vatzcb5tgTu2+wL1t6Of+FbIK8Ibp+tM03Naai4b2OU=",
"/login"=>{"username_or_email"=>"jonatasteixeira",
"password"=>"[FILTERED]"},
"commit"=>"Save /login" }
I would like to know why the my key received the "/login" name.
My view:
<h1>Login</h1>
<%= form_for(login_path) do |f| %>
<div class="field">
<%= f.label :username_or_email %><br>
<%= f.text_field :username_or_email %>
</div>
<div class="field">
<%= f.label :password %><br>
<%= f.password_field :password %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<%= link_to 'Back', root_path %>
In my controller
class SessionsController < ApplicationController
# GET /login
def new
#user = User.new
end
# POST /login
def create
#user = User.find_by_emai(session_params[:username_or_email]) || User.find_by_username(session_params[:username_or_email])
if #user && #user.authenticate(session_params[:password])
session[:current_user_id] = #user.id
flash[:notice] = 'You are logged in'
else
flash[:notice] = 'Invalid password, username or email'
end
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def session_params
logger.info :login
params.require("/login").permit(:username_or_email, :password)
end
end
I dont want to use "/login" as key, I would like to use :login. Some one knows how could I adjust it?
Thanks!!
As #Rafal pointed out, you could code your call to form_for like this to get rid of the awkward /login key in your params:
<%= form_for(:login) do |f| %>
Strong parameters are really only for scenarios where you are doing mass assignment on an object. If you were creating the user, for example, then you would probably want to pass the attributes into the new initializer method using strong parameters.
#user = User.new(session_params)
But because you're not doing mass assignment in this case, you can just pass in the values directly without a session_params method:
# POST /login
def create
#user = User.find_by(email: params[:login][:username_or_email]) || User.find_by(username: params[:login][:username_or_email])
if #user && #user.authenticate(params[:login][:password])
session[:current_user_id] = #user.id
flash[:notice] = 'You are logged in'
else
flash[:notice] = 'Invalid password, username or email'
end
end
The whole point of strong parameters is so no one can pass in extra attributes. In your /login scenario, your code is completely in control of the values being handled, so you don't need to worry about it.
Form_For
When you use form_for, Rails expects an object to be passed so it can build a variety of different elements from it:
[The form_for] helper is designed to make working with resources much easier
compared to using vanilla HTML.
The problem is you're passing a route to this method, which I'm surprised actually works.
--
form_tag
You'll be better using a symbol, as recommended by the accepted answer, or by using form_tag, which doesn't require an object:
<%= form_tag login_path do %>
<%= text_field_tag :username_or_email %>
<%= password_field_tag :password %>
<%= submit_button_tag "Go" %>
<% end %>
This will remove the references to the "login" key of your params, and will give you the ability to do this (no need for require):
params.permit(:username_or_email, :password)
Instead of
<%= form_for(login_path) do |f| %>
use
<%= form_for(:login) do |f| %>

Passing ID Value to a Controller, Getting Mass Assignment Security Error

I have a message model and a user model. my message belongs_to my user and user has_many messages.
I'm trying to allow a user to private message another user while on their public profile page (their show template). I have tried a number of attempts, but I ultimately run back into the issue of requiring an ID to be attr_accessible (which I heard is bad to do). Am I doing something wrong?
My message model, I have :user_id (which is the current user, aka a sending_from ID), :to_id, :content.
When I'm looking at a users profile page, on the show template I have
<%= form_for([current_user, #message]) do |f| %>
<%= f.hidden_field :to_id, :value => #user.id %>
<div class="field">
<%= f.text_area :content, placeholder: "Send a private message..." %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
In my user show action, I have
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
if user_signed_in?
#message = current_user.messages.build(params[:messages], to_id: #user.id)
end
end
when the form submits, it goes to my message create action
def create
#message = current_user.messages.build(params[:message])
redirect_to user_path(params[:message][:to_id])
end
However, I always get the error
`Can't mass-assign protected attributes: to_id`
It seems like I can fix it by making :to_id attr_accessible, however I have heard it is not very safe to do so. Am I doing something wrong? This issue has been killing me.
Any help would be appreciated. Thanks
Making to_id accessible is fine. But if you don't want that error just fix it like this:
def create
#message = current_user.messages.build
#message.to_id = params[:message][:to_id]
# manually assign whatever other params you need to
redirect_to user_path(params[:message][:to_id])
end
Mass assignment just means you can't use update_attributes, you can still use model.attribute=. The reason for doing it that way might be to add additional whitelisting parameters, such as:
def create
safe_params = params[:model].slice(:safe_attr1,:safe_attr2)
#model = Model.new(safe_params)
whitelist = ['some_safe_string','another_safe_string']
if whitelist.include?(params[:model][:dangerous])
#model.dangerous_attribute = params[:model][:dangerous]
end
#model.save
redirect_to #model
end

Resources