I am not sure if I understand the concept of strong parameters correctly. I should use strong parameters to params that I will use only to edit some data? Or I should use them for every params I want to get in controller? For example I want to get data between two dates, so I need date1 and date2 as params. Should I use here strong params or not?
The easiest way to understand when you should use strong parameters is to understand what a mass assignment volunerability is. In Rails 3 you could do the following:
class CreateUsers < ActiveRecord::Migration[3.0]
def change
create_table :users do |t|
t.string :email
t.string :encrypted_password
t.boolean :admin
t.timestamps
end
end
end
class UserController < ApplicationController
def create
#user = User.new(params[:user])
if #user.save
redirect_to #user
else
render :new
end
end
end
Here we are just passing a "hash" (its actually an ActionController::Parameters instance) straight into the model. All a malicous user has to do here is request:
POST /users?users[admin]=1
And they have created an admin account. In 2012 Egor Homakov famously exploited one such loophole in Github to commit to the Rails repository.
Such an attack is trivial to perform with cURL or by using the web inspector to manipulate a form.
If we whitelist which attributes the user should be able to pass:
class UserController < ApplicationController
def create
#user = User.new(
params.require(:user)
.permit(:email, :password, :password_confirmation)
)
if #user.save
redirect_to #user
else
render :new
end
end
end
Then this avoids the vulnerability - strong parameters is really just a simple DSL for slicing and dicing nested hash like structures. What changed in Rail 4 is that when you pass a n instance of ActionController::Parameters to a model an exception is raised unless calling #permitted? on the parameters object returns true. This avoids a mass assignment vulnerability from occuring simply due to programmer laziness or ignorance.
It does not sanitize your inputs in any other way. Like for example it won't prevent SQL injection or remote code execution if you treat user input carelessly.
You don't need strong parameters if you're passing parameters one by one like in this very contrived example:
class UserController < ApplicationController
def create
#user = User.new do |u|
u.email = params[:user][:email]
u.password = params[:user][:password]
u.password_confirmation = params[:user][:password_confirmation]
end
if #user.save
redirect_to #user
else
render :new
end
end
end
Related
I'm following Michael Hartl tutorial Rails course. On chapter 7, I run Reek on UsersController and I got this following warning:
app/controllers/users_controller.rb -- 1 warning:
1:InstanceVariableAssumption: UsersController assumes too much for
instance variable '#user'
[https://github.com/troessner/reek/blob/master/docs/Instance-Variable-Assumption.md]
Here is my code:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new user_params
if #user.save
flash[:success] = t "welcome_to_app"
redirect_to #user
else
render "new"
end
end
def show
#user = User.find_by id: params[:id]
return if #user
flash[:danger] = t "not_exist_user"
redirect_to root_path
end
private
def user_params
params.require(:user).permit :name, :email, :password,
:password_confirmation
end
end
Kindly explain why I got this error InstanceVariableAssumption and how to fix this.
That looks like one of the smells that reek recommends disabling on the GitHub page. According to this bug report filed on the project, it appears this is just due to rails making use/encouraging patterns that reek doesn't like by default.
Instance Variable Assumption says:
Classes should not assume that instance variables are set or present outside of the current class definition.
Well, this is where Convention over configuration comes in Rails. Rails has its own way of doing things, and the class: UsersController has a dependency upon the class: User.
The variable: #user is an instance variable defined in UsersController, but this is an object of class User. And this is how the things are done in Rails, and you will find it everywhere.
Just assume that this isn't perfect according to Reek gem, but this is how the things are done in Ruby on Rails.
We could use like this:
attr_reader :user, :users
Then all #user, #users can be written like user or users.
I have a devise model called user. When a user signs up, they will be directed to fill out form called "userinfo". I have a model called userinfo. As soon as a new userinfo is created, I give each userinfo a unique token. I permit "token" in my userinfo controller. It works, but everytime I edit the form and update, the unique token changes too. I was thinking I should only show the first created token on the userinfo#show page. But if a user updates their userinfo form 5 times, 5 tokens will be created and 4 will be wasted.
So actual problem: Create unique token when userinfo#new happens and show it on the userinfo#show page. Unique token should not be updated when userinfo#edit and userinfo#update happens.
My userinfo model:
class Userinfo < ActiveRecord::Base
belongs_to :user
before_save :set_token
def set_token
self.token = rand(100000..999999)
end
end
Userinfo controller:
class UserinfosController < ApplicationController
before_action :find_userinfo, only: [:show, :edit, :update, :destroy, :log_impression]
before_action :authenticate_user!
def index
#userinfors = Userinfo.search(params[:search])
end
def show
end
def new
#userinformation = current_user.build_userinfo
end
def create
#userinformation = current_user.build_userinfo(userinfo_params)
if #userinformation.save
redirect_to userinfo_path(#userinformation)
else
render 'new'
end
end
def edit
end
def update
if #userinformation.update(userinfo_params)
redirect_to userinfo_path(#userinformation)
else
render 'edit'
end
end
def destroy
#userinformation.destroy
redirect_to root_path
end
private
def userinfo_params
params.require(:userinfo).permit(:name, :email, :college, :gpa, :major, :token, :skills, :user_img)
end
def find_userinfo
#userinformation = Userinfo.friendly.find(params[:id])
end
end
View:
<%= #userinformation.token %>
Try something like this:
def set_token
self.token ||= rand(100000..999999)
end
The ||= says, "set token to a random number unless token already has a value" (roughly).
BTW, in response to the comments below and on your original question, it is true that using:
rand(100000..999999)
is not such a good idea. Two problems were identified:
There is a chance that the generated number will not be unique, and
The probability that you attempt to assign non-unique numbers increases with the number of users and goes to 100% once you have 999,999 users.
As mentioned in the comments, using SecureRandom.uuid is a good thing if you don't mind the format of the UUID which is something like this:
ad9ed387-ec8e-4091-84b1-fe2ce2bbfcd4
In which case you would do something like:
def set_token
self.token ||= SecureRandom.uuid
end
This is, by the way, what I do in my code.
With SecureRandom.uuid, the probability that you will generate duplicate tokens is vanishingly small. However, if you are worried about that very small chance, you could also enforce uniqueness at the DB level and in your model. Those are separate questions that you may want to post if you are interested in the answers.
If I have a user
def user_params
params.require(:user).permit(:name, :age)
end
I got that down. I want to batch create users. So a user can fill out a list (theoretically endless) of users, they would come in as:
[{name: "name", age: 12},{name: "name", age: 22},{name: "name", age: 32}]
Question is, how do I use strong parameters for that? I know that I can just loop through the array and create the records, I get that. My understanding is that strong params are a generally good idea, safety wise.
What are strong params protecting me from? What would I be opening myself up to here, if I just looped over the array of users? How can I do it properly, either with strong params, or an alternate method?
The entire point of strong parameters (introduce in rails 4) with the goal of protecting applications from mass assignment vulnerabilities. Like for example, lets say you had a User model and it had a admin attribute. If you were using mass assignment in theory someone could slip in a value for the admin attribute if you did not filter it out some how; see below
class UserController < ApplicationController
def create
#{name: 'Joe', score: 7, title: 'Mr', admin: true} params hash
User.create(params)
end
end
Now if some how a user of your app passed in these values they just made themselves and admin and can do as they please. So thats why you would use strong params to do this.
class UserController < ApplicationController
def create
User.create(user_params)
end
def user_params
params.require(:name).permit(:title, :score) #noticed admin is not allowed
end
end
Now to create multiple records with strong params you could do this
class UserController < ApplicationController
def create
user_params[:users].each do |u|
User.create(u)
end
end
def user_params
params.permit(:users, array: [:name, :age])
end
end
I'm having trouble implementing strong parameters, receiving the Undefined Method attr_accessible error locally.
Could anyone explain what exactly I have done wrong here.
users_controller.rb:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to root_url, :notice => "Signed up!"
else
render "new"
end
end
def user_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
end
And in user.rb:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
has_secure_password
validates_presence_of :password, :on => :create
end
And perhaps a foolproof fix for this...I've tried a number of attempts but I just can't seem to get this right.
strong_params are usually done in the controller, not in the model. it's also described like this in the api. so, there's also no need for you to set attr_accesible. this way different controllers can also set different fields on a model, e.g. a backend users controller could be allowed to set an admin flag, while the users controller on the frontend is not allowed to do that.
so, your user_params method belongs in your UsersController, and the create and update action use user_params to filter out the params you don't allow to be set. e.g.:
#user = User.new(user_params)
Rails 4 uses strong params by default, and you don't need attr_accessible. Also in rails 4 you permit params in the controller instead of the model.
How is attr_accessible used in Rails 4?
I am trying my hand on rails (4). I have done some Sinatra earlier.
I have a signup form, in which user can fill out his organization name, address and his own name, password etc. I have two tables - Users and Organizations, those table get populated with Signup data. So, I have two active records model users and organizations. My controllers looks as follows:
class SignupsController < ApplicationController
# GET /signup
def new
# show html form
end
# POST /signup
def create
#signup = Registration.register(signup_params)
respond_to do |format|
if #signup.save
format.html { redirect_to #signup, notice: 'Signup was successfully created.' }
else
format.html { render action: 'new' }
end
end
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def signup_params
params[:signup]
end
end
I do not have any Registration model (table in db). What I am looking for such Registration model (should I call it model?) where I can have something like:
class Registration
def self.register params
o = Organization.new params
u = User.new o.id, params
self.send_welcome_email params[:email]
end
def send_welcome_email email
#send email here
end
end
1) So where should I keep this Registration class?
2) Is this a correct approach for such situation? If not, then what is the best way to do it?
3) Having such class will effect running any unit tests?
4) I see there is file, signup_helper.rb what is the use of that in SignupsController
You can do include ActiveModel::Model in your model, and it will behave as a normal Model. You will be able to do validations, callbacks. Anything other than persisting to a database.
Have a look at this for more info.
class Registration
include ActiveModel::Model
def self.register params
o = Organization.new params
u = User.new o.id, params
self.send_welcome_email params[:email]
end
def send_welcome_email email
#send email here
end
end
To answer your questions:
1) I would move the Registration class to a Signup module (as it relates to the signup use case) in app/models:
# apps/models/signup/registration.rb
module Signup
class Registration
...
end
end
2) I like your approach. It's something I would do. It is not the "Rails way" but in my opinion it is superior.
3) No. Having a PORO (plain old ruby object) is currently fine for any unit tests and you can easily test it.
4) Helpers are an (in my opinion) old and obsolete way to share functionality between views (and controllers). Don't use them unless there is absolutely no other way (maybe some Library demands it ... ). It is alway better to use POROs like you did.
You don't have to include ActiveModel::Model unless you need it's functionality. E.g. validations. You can include individual pieces of its functionality in this case. E.g
include ActiveModel::Validations