is this a secure approach in ActiveRecords in Rails? - ruby-on-rails

I am using the following for my customers to unsubscribe from my mailing list;
def index
#user = User.find_by_salt(params[:subscribe_code])
if #user.nil?
flash[:notice] = "the link is not valid...."
render :action => 'index'
else
Notification.delete_all(:user_id => #user.id)
flash[:notice] = "you have been unsubscribed....."
redirect_to :controller => 'home'
end
end
my link looks like;
http://site.com/unsubscribe/32hj5h2j33j3h333
so the above compares the random string to a field in my user table and accordingly deletes data from the notification table.
My question; is this approach secure? is there a better/more efficient way for doing this?
All suggestions are welcome.

I don't see anything wrong in your solution if the action doesn't require authentication.
If the action requires authentication then I would make sure that the salt belongs to the current user
#user = User.find_by_id_and_salt(current_user.id, params[:subscribe_code])

Is it all that important that the user be informed if their unsubscribe link was wrong? What are the chances of that anyway? Wasn't it generated programmatically and that program is tested? If the answer is "yes" (hint: it should be) then my suggestion would be to always tell the user that they've unsubscribed, regardless of what happened.
def index
begin
#user = User.find_by_salt!(params[:subscribe_code])
rescue ActiveRecord::RecordNotFound
ensure
#user.notifications.delete_all if #user
flash[:notice] = "You have been unsubscribed."
redirect_to :action => "index"
end
end

I think this is safer:
#user = User.find :all, :conditions => { salt => params[:subscribe_code] }
That way you are sure that Rails knows params[:subscribe_code] is to be escaped. There are probably lots of ways to write it that would work though.

your link should be
http://site.com/unsubscribe/?subscribe_code=32hj5h2j33j3h333
otherwise "32hj5h2j33j3h333" will get as a params[:id]
else is fine. Assuming subscribe_code will be unique.

Related

Get any id to any user exists in the database

I am new to rails and have a task that asks me to send an invitation for any user to be admin in my magazine here is my piece of code
def invite
inviteUser = { 'user_id' => current_user.id, 'Magazine_id' => params[:id] }
CollaborationInvitation.create(inviteUser)
#magazine = Magazine.find(params[:id])
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to root_path
end
I need to replace current_user.id with something that refers to any user's id which exists in my database to send him an invitation to be admin with me I tried to add #User=Users.All and then pass it as a variable but it got me an error I tried a lot of things but every time I get an error except for adding current_user.id
ps: I am using devise for authentication
You asked a couple things, and it is kind of confusing what you want to do.
Here is how you get all ids of records in a model.
Rails4: User.ids
Rails3: User.all.map(&:id)
Or (not sure if #pluck is in Rails 3 or not)
User.pluck(:id)
If you want to get a random user (you mentioned "any user") you could do.
User.find(User.pluck(:id).sample)
Though I think what you really want to do is to pass the id or some other attribute of a user as a param to the action and send that user an invitation.
Presumably you either have a post or get route for "users#invite" (the action you wrote in your question). You can add a named parameter there or you can pass a url param or if you are using a post route, you could add the param to the post body.
Then in your contoller you can do something like this (I'll use email as an attribute):
def invite
#user = User.find_by(email: params[:user_email])
#Rails 3 like this
# #user = User.find_by_email(params[:user_email])
# now do stuff with user
end
User.all will return you the collection of users. So,
Find the user object to get an id...
Try this code....
def invite
inviteUser = { 'user_id' => User.find_by_email('user#example.com').id, 'Magazine_id' => params[:id] }
CollaborationInvitation.create(inviteUser)
#magazine = Magazine.find(params[:id])
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to root_path
end
You can try
User.last.id
or
User.find_by_email("xyz#test.com").id
or
User.where(email: "xyz#test.com").first.id
Replace xyz#test.com with desired user email address. To get more details on rails active record query interface, please read rails guides http://guides.rubyonrails.org/active_record_querying.html

Is there a way to get the visitors city and country in Rails controller?

In my current app, i use Geocoder gem to get the city and the country of the visitor. I use hidden fields in my view to get these details. When the login form is submitted, these details will be sent to the controller and the controller will save them to the database. When I try to get these details directly from the controller by using
request.location.city
It will assigning a blank value to the database. If I use hidden fields in the view, some one can temper with them right? So, how can I fix this?
You should store visitor information before you render any content:
class UsersController
def new
# I suspect that, for fast insert, you should probably use a NoSQL database
# to perform `store!` or even just write it to a log file
Visitor.store!(:city => request.location.city, :ip => request.ip)
end
def create
#user = User.build(params[:user].merge(:city => request.location.city))
if #user.valid?
#user.save
flash[:notice] = "You've been registered!"
redirect_to user_dashboard_path
else
flash[:notice] = "Couldn't register your account"
render action: "new"
end
end
end

Implementing a specific action for a model that respond_with(#object)

I have a Subscription model that users can create. Users needs to validate a PIN sent to them to confirm subscription after is created. I'm having a bit of trouble trying to figure it out the best way to implement this.
I implemented a confirms controller with two new and create actions.
def new
#confirm = Subscription.new
end
def create
#keyword = Keyword.joins(:shortcode).where("shortcodes.shortcode = ? and shortcodes.country = ?",params[:subscription][:shortcode],params[:subscription] [:country]).find_or_create_by_keyword(params[:subscription][:keyword])
if #confirm = Subscription.where(:phone => params[:subscription][:phone], :country => params[:subscription][:country], :keyword_id => #keyword.id).last
#confirm.check_subscription_pin(params[:subscription][:pin])
respond_with(#confirm)
elsif #confirm && #confirm.errors.any?
flash[:notice] = #confirm.errors
render :action => :new
else
flash[:notice] = "Subscription not found."
render :action => :new
end
end
This solution doesn't look very convincing since I would like to always respond_with(#confirm) to allow REST POST done via JSON.
There's no params[:subscription] received when you make your curl call. Phone and Pin are nested within subscription, hence the error.
<pre>undefined method `[]' for nil:NilClass</pre>
I don't know how to make a curl call with nested params by the way.

How to handle active record find params[:id] in controller?

I'm wondering what is best practive for handling this type of situation in ruby on rails.
users_controller.rb
def show
#user = User.find params[:id]
end
If user is not found it throws an exception which isn't catched anywhere so it will display some ugly things to enduser.
Solution would be pack it into begin...rescue...end block:
def show
begin
#user = User.find params[:id]
rescue
flash[:error] = "User not found"
redirect :action => :index
end
end
although I've never seen such code in any rails article or tutorial I've seen.
What is the proper way of handling this type of situations?
See docs rescue_from
It depends on your requirement.
But you want a general solution then you can have a rescue block in ApplicaionController which will handle the RecordNotFound exception.
You can do
def show
#user = User.find_by_id(params[:id])
unless #user
flash[:error] = "User not found"
redirect :action => :index
end
end
But you cant expect you will call a link with id which is not in the db from within the application. Please see the answer of the question
The development environment will show you ugly error messages, but a production environment will just give an HTTP 404 error (page not found) when the id is invalid.
I think that you may be able to fix this with
#user = User.find(params[:id] = current_user)

Conditional value for ActiveRecord create method only

I have a form where I have an administrator creating new users. The form uses the User model I created (login, password, first_name, etc...). For the last field on the form, I want to have a checkbox that doesn't need to be stored as part of the User record, but it is needed for the controller. This will control if the newly created user will receive a welcome email or not. This is in Rails 3.0.3.
def create
#user = User.new(params[:user])
if #user.save
if #user.send_welcome_email
UserMailer.welcome_email(#user).deliver
end
redirect_to(admin_users_url, :notice => "User #{#user.name} was successfully created.")
else
render :action => "new"
end
end
In my view (haml) I am trying to access it like this:
%p
Send Welcome Email?
= f.check_box :send_welcome_email
I tried to make this an attr_accessible: :send_welcome_email but the controller does not recognize it. I get an
undefined method 'send_welcome_email' for #<User:0x00000100d080a8>;
I would like it to look like this:
What is the best way to get this working?
What you want is not attr_accessible, but attr_accessor. That's it.
However, your code will look nicer if you move the email sending code to an observer.
Since you're not saving it on the user, you can use check_box_tag instead of f.check_box and access it with params[:send_welcome_email]. Although even the way you have it, I think you could access it as params[:user][:send_welcome_email].
As an alternative to attr_accessor, you can always remove it from the parameters first:
def create
send_welcome_email = params[:user].delete(:send_welcome_email)
#user = User.new(params[:user])
if #user.save
UserMailer.welcome_email(#user).deliver if send_welcome_email
redirect_to(admin_users_url, :notice => "User #{#user.name} was successfully created.")
else
render :action => "new"
end
end
You may have to make sure that the parameter is successfully transformed into a boolean; otherwise the condition will always be true (0 is true in Ruby).

Resources