Rails: Can't get test to pass on create action - ruby-on-rails

I'm trying to make sure that people can't submit a create action if they submit an entry with an ID other than their own. For this, I have set up the test as following:
entries_controller_test.rb
def setup
#user = users(:thierry)
#other_user = users(:steve)
end
...
test "should redirect create action on entry with id that doesn't belong to you" do
log_in_as(#user)
assert_no_difference 'Entry.count' do
post :create, entry: { content: "Lorem Ipsum"*10, id: #other_user }
end
end
The outcome of the test is that Entry.count increases by one, therefore #user can create a post with ID #other_user (is the code correct to create an entry with ID of the other user?)
entries_controller.rb: My create action currently looks like this.
def create
#entry = #entries.build(entry_params)
if #entry.save
flash[:success] = "Your entry has been saved."
redirect_to root_path
else
flash.now[:danger] = "Your entry has not been saved."
render 'index'
end
end
The instance variable is being passed in to the action by calling before_action :correct_user on the action. Here's the correct_user method.
def correct_user
#entries = current_user.entries
redirect_to root_url if #entries.nil?
end
By the way, the create action is being called from the index page. I suspect the problem is indeed with authorization since my test can log in the user and create an actual entry.
Can anyone spot an issue?

Your code is only checking whether the current_user has some entries, but there is no validation on the user_id of the entry being submitted to the create action. Moreover, even if the user has no entries, the #entries variable will be [], which is not nil (so correct_user will never redirect to root). The correct check would have been #entries.empty?, but still the object would be created with an incorrect user, as long as the current_user already has some entries belonging to them.
The way I usually go about this is not to permit the user_id parameter (with strong_parameters), and by setting the ownership of new objects to the current_user. If you want to perform the check, your correct_user should look more like this:
def correct_user
unless current_user.id == params[:entry][:user_id]
flash[:alert] = "Some error message"
sign_out # This action looks like a hack attempt, thus it's better to destroy the session logging the user out
redirect_to root_url
end
end

I think this might work.
In your entries controller.
class EntriesController < ApplicationController
before_action :correct_user, only: [:edit, :update]
def correct_user
unless correct_user.id == params[:entry][:user_id]
else
redirect_to root_url
end
end
end

Related

How I Pass parameters from View to Controller In Ruby

#app/controllers/sessions_controller.rb
class SessionController < ApplicationController
def new
#session = Session.new
end
def fetch
##user = User.session(params [:user])
redirect_to "http://www.google.com"
end
def create
emai = params[:email]
puts emai
user = User.find_by(:email => session[:emai])
#user = User.find_by (params [:email])
#user = User.find_by email: 'abc#xyz.com'
#user = User.find_by(params[:Email])
#if (session[:Email] = user.email)
if (user)
redirect_to "http://www.yahoo.com"
flash[:notice] = "You signed up successfully"
flash[:color]= "valid"
else
flash[:notice] = "Form is invalid"
flash[:color]= "invalid"
redirect_to "http://www.google.com"
end
#redirect_to "http://www.yahoo.com"
end
end
every time i execute my view i get redirected to google.com even though i pass the parameters.
Edit by R Peck:
My logic should send people to Yahoo if the params are set, but still sends to Google, how can I fix this?
Try:
user = User.find_by(:email => params[:sessions][:emai])
You are not getting the value of email if you only call params[:email] you should call parent first before calling the child params[:sessions][:email].
Several things wrong with your code.
Here's what I'd write:
#app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
#session = Session.new
end
def create
email = params[:sessions][:email]
user = User.find_by email: email
url = user ? "google" : "yahoo"
colour = user ? "valid" : "invalid"
notice = user ? "You signed up successfully" : "Your form is invalid"
redirect_to "http://#{url}.com", notice: notice, color: colour
end
private
def session_params
params.require(:session).permit(:session, :params)
end
end
OOP
I think this may be a little advanced but I'll write it anyway, for my own benefit.
Rails is object orientated (it's built on Ruby which is an OOP language). This means that each time you create/call a controller, it should be centered around objects.
A good example for you would be the Devise controllers.
This has a sessions_controller which essentially allows you to CRUD (Create Read Update Destroy) a session. This is the correct way to use a controller.
Your implementation seems to be dealing with a user, rather than a session, and as such you'd be best using a users_controller to fix it:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new
#user.save
end
end
Having said that, it does seem that you're probably going to resolve the issue to make it so that you can use the User to build a new session.
I guess it's best to remember that you have to ensure you're able to appreciate a good structure for your application

Rails - Is the user the owner of the model?

What's the best way to check if the current object belongs to the current user?
I want to only allow the power of deletion to the owner, but I'm struggling to build a controller function to accomplish this.
before_filter :signed_in_user, only: [:create]
before_filter :correct_user, only: [:edit, :update, :destroy]
...
def destroy
#event = Event.find(params[:id])
if #event.present?
#event.destroy
end
redirect_to root_path
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to signin_path, notice: "Please sign in."
end
end
def correct_user
#event = current_user.events.find_by_id(params[:id])
rescue
redirect_to root_path
end
My current def correct_user allows any logged in user to make deletions.
Your correct_user method is already doing what you need to do: fetching the event with current_user.events.find. You can just delete the event-finding code from your destroy method and it should work correctly.
When I wrote my comment you had yet to added the destroy action code. Look carefully at the correct_user method:
def correct_user
#event = current_user.events.find_by_id(params[:id])
rescue
redirect_to root_path
end
Right there you are retrieving the event and storing it in an instance variable. More so, you are retrieving the event through the current_user. So it's scoped to the current_user, and only events that are owned by the current_user are exposed.
#event = current_user.events.find_by_id(params[:id])
Remember, correct_user is set in a before_filter, so it runs before the destroy action.
So, by the time the request gets to the destroy action, the event is already stored in an instance variable (#event). So there is already an event object for you to work with. You can reference that. There is no need to retrieve it again.
def destroy
#event = Event.find(params[:id]) #<- This line is redundant (and dangerous in this case)
... # snip
end
Change the destroy method to this.
def destroy
redirect_to root_path if #event.destroy
end
I was just about to add similar functionality! You have to check if the current user is signed in, and if their ID = the user_id of the object.
Something like
event = Event.find(params[:id])
if event.user_id == current_user.id
event.destroy
else
redirect_to :root, :notice => "You do not have permissions."
end

How do I restrict access to edit action and through URL entry?

I have a relationship user ("devise") that has many events.
I want to prevent users from editing events that do not belong to them and stop users from accessing the edit action by entering something like 'http://localhost:3000/events/65/edit' into the browser.
I also want to redirect the user back to the page they were on when clicking on the edit event link.
I tried the following two methods without success:
def edit
if current_user == #event.user_id
#event = Event.find(params[:id])
else
redirect_to events_path
end
def edit
#event = Event.find(params[:id])
unless session[:id] == #event.user_id
redirect_to events_path
return
end
end
If you only need this kind of authorization logic in this controller, something like this would be possible:
class User < ActiveRecord::Base
has_many :events
end
class EventsController < ApplicationController
def edit
#event = current_user.events.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to events_path, notice: "You cannot edit this event."
end
end
The rescue-block is really optional. If you remove it, the user will get a 404 Not found error message if she visits the edit URL for an event she didn't create,
If you expect to use authorization other places in your application, I would advise you to look into CanCan. It's a gem that sentralizes rules for authorization and access in an Ability class.
Try adding a before filter (it can be used for other actions as well if needed):
before_filter :check_user, :only => [:edit]
def check_user
#event = Event.find(params[:id])
unless current_user.id == #event.user_id
redirect_to (request.referrer || root_path)
return
end
end
The idea behind your first method is fine, but that comparison will always fail. current_user is a User object; #event.user_id is an integer (or possibly some form of UUID).
You need to either compare a User object to a User object:
if current_user == #event.user
Or an ID to an ID:
if current_user.id == #event.user_id

return redirect_to in private controller method

Preface: I'm using devise for authentication.
I'm trying to catch unauthorized users from being able to see, edit, or update another user's information. My biggest concern is a user modifying the form in the DOM to another user's ID, filling out the form, and clicking update. I've read specifically on SO that something like below should work, but it doesn't. A post on SO recommended moving the validate_current_user method into the public realm, but that didn't work either.
Is there something obvious I'm doing wrong? Or is there a better approach to what I'm trying to do, either using devise or something else?
My UsersController looks like this:
class UsersController < ApplicationController
before_filter :authenticate_admin!, :only => [:new, :create, :destroy]
before_filter :redirect_guests
def index
redirect_to current_user unless current_user.try(:admin?)
if params[:approved] == "false"
#users = User.find_all_by_approved(false)
else
#users = User.all
end
end
def show
#user = User.find(params[:id])
validate_current_user
#user
end
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
validate_current_user
#user
end
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to #user, :notice => 'User was successfully created.' }
else
format.html { render :action => "new" }
end
end
end
def update
#user = User.find(params[:id])
validate_current_user
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, :notice => 'User was successfully updated.' }
else
format.html { render :action => "edit" }
end
end
end
private
def redirect_guests
redirect_to new_user_session_path if current_user.nil?
end
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
return redirect_to(current_user)
end
end
end
The authenticate_admin! method looks like this:
def authenticate_admin!
return redirect_to new_user_session_path if current_user.nil?
unless current_user.try(:admin?)
flash[:error] = "Unauthorized access!"
redirect_to root_path
end
end
EDIT -- What do you mean "it doesn't work?"
To help clarify, I get this error when I try to "hack" another user's account:
Render and/or redirect were called multiple times in this action.
Please note that you may only call render OR redirect, and at most
once per action. Also note that neither redirect nor render terminate
execution of the action, so if you want to exit an action after
redirecting, you need to do something like "redirect_to(...) and
return".
If I put the method code inline in the individual controller actions, they do work. But, I don't want to do that because it isn't DRY.
I should also specify I've tried:
def validate_current_user
if current_user && current_user != #user && !current_user.try(:admin?)
redirect_to(current_user) and return
end
end
If you think about it, return in the private method just exits the method and passes control back to the controller - it doesn't quit the action. If you want to quit the action you have to return again
For example, you could have something like this:
class PostsController < ApplicationController
def show
return if redirect_guest_posts(params[:guest], params[:id])
...
end
private
def redirect_guest_post(author_is_guest, post_id)
redirect_to special_guest_post_path(post_id) if author_is_guest
end
end
If params[:guest] is present and not false, the private method returns something truthy and the #show action quits. If the condition fails then it returns nil, and the action continues.
You are trying and you want to authorize users before every action. I would suggest you to use standard gems like CanCan or declarative_authorization.
Going ahead with this approach you might end up reinventing the wheel.
In case you decide on using cancan, all you have to do is add permissions in the ability.rb file(generated by rails cancan:install)
can [:read,:write,:destroy], :role => "admin"
And in the controller just add load_and_authorize_resource (cancan filter). It will check if the user has permissions for the current action. If the user doesnt have persmissions, then it will throw a 403 forbidden expection, which can be caught in the ApplicationController and handled appropriately.
Try,
before_filter :redirect_guests, :except => [:new, :create, :destroy]
should work.
This is because you are using redirect twice, in authenticate_admin! and redirect_guests for new, create and destroy actions.
"Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action."
That's the reason of the error. In show method, if you are neither the owner of this account nor the admin, you are facing two actions: redirect_to and render
My suggestion is to put all of the redirect logic into before_filter

Rails: Keeping user spoofing checks DRY

In a fit of unoriginality, I'm writing a blog application using Ruby on Rails. My PostsController contains some code that ensures that the logged in user can only edit or delete their own posts.
I tried factoring this code out into a private method with a single argument for the flash message to display, but when I did this and tested it by editing another author's post, I got an ActionController::DoubleRenderError - "Can only render or redirect once per action".
How can I keep these checks DRY? The obvious approach is to use a before filter but the destroy method needs to display a different flash.
Here's the relevant controller code:
before_filter :find_post_by_slug!, :only => [:edit, :show]
def edit
# FIXME Refactor this into a separate method
if #post.user != current_user
flash[:notice] = "You cannot edit another author’s posts."
redirect_to root_path and return
end
...
end
def update
#post = Post.find(params[:id])
# FIXME Refactor this into a separate method
if #post.user != current_user
flash[:notice] = "You cannot edit another author’s posts."
redirect_to root_path and return
end
...
end
def destroy
#post = Post.find_by_slug(params[:slug])
# FIXME Refactor this into a separate method
if #post.user != current_user
flash[:notice] = "You cannot delete another author’s posts."
redirect_to root_path and return
end
...
end
private
def find_post_by_slug!
slug = params[:slug]
#post = Post.find_by_slug(slug) if slug
raise ActiveRecord::RecordNotFound if #post.nil?
end
The before filter approach is still an ok option. You can gain access to which action was requested using the controller's action_name method.
before_filter :check_authorization
...
protected
def check_authorization
#post = Post.find_by_slug(params[:slug])
if #post.user != current_user
flash[:notice] = (action_name == "destroy") ?
"You cannot delete another author’s posts." :
"You cannot edit another author’s posts."
redirect_to root_path and return false
end
end
Sorry for that ternary operator in the middle there. :) Naturally you can do whatever logic you like.
You can also use a method if you like, and avoid the double render by explicitly returning if it fails. The key here is to return so that you don't double render.
def destroy
#post = Post.find_by_slug(params[:slug])
return unless authorized_to('delete')
...
end
protected
def authorized_to(mess_with)
if #post.user != current_user
flash[:notice] = "You cannot #{mess_with} another author’s posts."
redirect_to root_path and return false
end
return true
end
You could simplify it more (in my opinion) by splitting out the different parts of behavior (authorization, handling bad authorization) like this:
def destroy
#post = Post.find_by_slug(params[:slug])
punt("You cannot mess with another author's post") and return unless author_of(#post)
...
end
protected
def author_of(post)
post.user == current_user
end
def punt(message)
flash[:notice] = message
redirect_to root_path
end
Personally, I prefer to offload all of this routine work to a plugin. My personal favorite authorization plugin is Authorization. I've used it with great success for the last several years.
That would refactor your controller to use variations on:
permit "author of :post"
The simple answer is to change the message to something that fits both: "You cannot mess with another author's posts."
If you don't like the ugly* return in that last solution, you can use an around filter and conditionally yield only if the user is authorized.
around_filter :check_authorization, :only => [:destroy, :update]
private
def check_authorization
#post = Post.find_by_slug(params[:slug])
if #post.user == current_user
yield
else
flash[:notice] = case action_name
when "destroy"
"You cannot delete another author's posts."
when "update"
"You cannot edit another author's posts."
end
redirect_to root_path
end
end
*-- that's my preference, though code-wise it's perfectly valid. I just find that style-wise, it tends to not fit.
I also should add I haven't tested this and am not 100% certain it would work, though it should be easy enough to try.

Resources