Visiting users/1/edit when I'm signed in as different user does not raise an AccessDenied error, and I have no idea why:
authorize_resource only: [:edit, :update]
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
redirect_to #user
else
render 'edit'
end
end
Ability class:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :read, :all
can :create, User
can :create, Group
can :update, User, id: user.id
end
end
If I change authorize_resource to load_and_authorize_resource then it works as expected. But this should not be relevant, surely?
Your code only authorize the user to access edit and update action not the #user object
you have to manually authorize the object like this
Try this,
def edit
#user = User.find(params[:id])
authorize! :update, #user
end
def update
#user = User.find(params[:id])
authorize! :update, #user
if #user.update_attributes(params[:user])
redirect_to #user
else
render 'edit'
end
end
I'm facing the same issues like you,but for me,I'm using devise with cancan. Therefore ,in my controller, i will put
before_filter :authenticate_user!, :except=>[:create]
it will authenticate user except the create.
def index
#user = User.all
authorize! :index, #user
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #user }
end
end
each of your controller function that you want to authorize the access of user, you can do like this, it seems you have to do lots of works by putting every single in the function that you need to authorize instead just using load_and_authorize_resource, but hope can help u a little from what i have completed. here is the resource:https://github.com/ryanb/cancan/wiki/authorizing-controller-actions. If you get an answer and why the load_and_authorize_resource is not working, post to here too :)
I don't have an answer for you (yet) on why this happens, but I encountered essentially the same issue. My situation was only different in that manually authorizing each action (instead of relying on either "authorize resource" or "load_and_authorize" was the key.
I am running into this issue as well, and here is what I have found.
If I'm reading the source code right, during an :update action, load_and_authorize does a find_by to load the resource, then calls authorize! on it. However, I don't see where it authorizes it after the incoming parameters have been applied. (Someone please correct me if I'm reading this wrong.)
The use case I am seeing this is when someone edits a resource, and in the edit, updates a value in the resource that makes it no longer eligible to pass authorization on save. (Granted, I am setting up the UI to help avoid this situation, but obviously I still want to protect the resource.) Running a functional test, I was able to set attributes that I expected not to pass authorization on the controller :update action, presumably because the check happens before the attributes are parsed.
So far, the way I have worked around it is to call authorize! again after I have set the attributes, which means I can't use update_attributes since I want to authorize before saving:
class FooController < ApplicationControlller
load_and_authorize_resource
def update
# slurp the mass assignable params
#foo.attributes = params[:foo]
# set some other params
#foo.some_other_attr = 'bar'
# authorize again, now that we have updated the params
authorize! :update, #foo
if #foo.save!
flash[:notice] = I18n.t(...)
respond_with(#foo)
# ...
end
end
end
An alternative is to create a before_filter, load the #foo instance yourself, then call authorize as above, but that doesn't really make things much cleaner, IMHO. It would just save on one authorize! call.
I'm curious as to how others are handling this. I am fairly new to CanCan, so I am presuming I am doing something wrong. :)
Related
i have a rails app with sorcery
everything work .
the problem is when edit a user like :
http://localhost:3000/users/1/edit
its work fine , but when i change the user id to 2 or 3 ..
i can update all users data
how can i restrict the edit page only if the current user is the one that logged in
here is my controller :
skip_before_action :require_login, only: [:new, :create, :show]
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
auto_login(#user)
flash[:info] = "Welcome."
redirect_to root_url
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
you can also do something like this
before_action :edit_rights?, only: [:update, :edit]
private
def edit_rights?
#user = User.find(params[:id])
redirect_to(root_path) unless current_user == #user
end
you won't need #user = User.find(params[:id]) in both update and edit actions then
There are (at least) two ways to do that. First and straightforward is detailed in another answer, fine-tune your controller.
A less obvious way is to create a singular resource and its own controller. In routes that could look like:
resource :profile, only: [:show, :edit, :update]
# generates:
# /profile (GET, PATCH, PUT)
# /profile/edit (GET)
Then create a controller that is responible solely for user's own profile and operates only on current_user.
Yes, it's okay for one model to have multiple controllers, if your model should behave really differently in different parts of your app.
Why would you do that?
User's own profile could show much more information than is available publicly, you can lay it out in a separate view
No "access denied" errors, as the resource is auto-selected via current_user, all you need is ensure the user is logged in in the entire controller.
Example:
User A (id=10) has created a photo resource
photo: (id: 1 user_id = 10, url: "http://...")
Now, if User B (id=20) go to this url: /photos/1/edit it can edit photo of user A!!!
Rails+Devise provides something for this by default? It seems it's a very common issue
I just need to allow that any user can edit/delete ONLY resource it has created (where current_user == resource.user)
Using: Rails 4, Devise
Update:
I think CanCan it's something too advanced. I don't need roles or restrict some actions to certain users
In your PhotosController:
before_filter :require_permission, only: :edit
def require_permission
if current_user != Photo.find(params[:id]).user
redirect_to root_path
#Or do something else here
end
end
You can make use of Rails' associations and write it like this:
def edit
#photo = current_user.photos.find(params[:id])
# ... do everything else
end
This will only find a record when the photo with the supplied ID belongs to the current user. If it doesn't, Rails will raise a ActiveRecord::RecordNotFound exception.
Of course, I'm assuming the current_user method is available and your User model contains the statement has_many :photos.
Check this railscasts,
http://railscasts.com/episodes/192-authorization-with-cancan
Complications you will run into,
When you want cancan authorization on User Model that Devise gem is using for authentication
When you want to store your Roles in the Database
When you want to assign Permissions to the Roles as an Admin from the webUI
and more ..
Please comment if you want any of those features, I will be happy to help, because I recently did them with great help from others and its always amazing to pass it on.
A sample Ability for your resources can be like as follows,
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest users
send(user.role.name)
if user.role.blank?
can :read, User #for guest without roles
end
end
def man
can :manage, Photo
end
def boy
can :read, Photo
end
def kid
can :read, Article
end
end
I captured the exception from within a before_filter action:
before_action :set_photo, only: [:edit, :update, :destroy]
def set_photo
#photo = current_user.photos.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to(root_url, :notice => 'Record not found')
end
Hope this helps someone. I'm using Rails 4 and Ruby 2.
So you are using gem devise.
This gem provides the current_user for the currently logged in user.
In your PhotosController#edit method. I'd do something like below.
def edit
#photo = Photo.find(params[:id])
redirect_to root_path, notice: 'Thou Shalt Nought duuu dat :(' unless current_user.id == #photo.user_id
...
end
This method is cheaper because you already have 2 objects to compare instead of running a query in the comparison.
The simplest would be to to modify routes.rb.
Assign photos to live in the current_user path.
For example,
devise_for :users
resources 'users' do
resources 'photos'
end
cancan is difficult and complicate
i have coding is_onwer method
it's very simple, easy
https://gist.github.com/x1wins/0d3f0058270cef37b2d3f25a56a3745d
application controller
def is_owner user_id
unless user_id == current_user.id
render json: nil, status: :forbidden
return
end
end
def is_owner_object data
if data.nil? or data.user_id.nil?
return render status: :not_found
else
is_owner data.user_id
end
end
your controller
before_action only: [:edit, :update, :destroy] do
is_owner_object #article ##your object
end
If CanCan is too advanced, you should loon into checking the id of the accessor in the controller using...
if #user.id == #photo.user_id
# edit photo details
else
redirect_to root_path, notice: "You! Shall! Not! Edit!"
...or something like that
Write another before_filter in application_controller:
before_filter :has_permission?
has_permission?
controllers=["articles", "photos", "..."]
actions=["edit", "destroy", "..."]
id = params[:id] if (controllers.include?(params[:controller] && actions.include?(params[:action]) end
if id && (current_user.id==(params[:controller][0...1].capitalize!+params[:controller].singularize[1...-1] + ".find(#{id}).user_id").send)
return true
else
redirect_to root_url, :notice=>"no permission for this action"
end
helper_method :has_permission?
And you can use it in views, not to show users link they can't follow.
Some kind of this, of course you need to modify it to suit your needs.
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
Is it a good practice to hide instance variable initialization in private methods?
For example, I have a user controller with some actions:
class UsersController < ApplicationController
before_filter :get_user, only: [:show, :edit, :update, :destroy]
before_filter :set_user, only: [:new, :create]
def index
#users = User.all
end
def show
end
def new
end
def edit
end
def create
if #user.save
redirect_to #user, notice: 'User was successfully created.'
else
render action: 'new'
end
end
def update
if #user.update_attributes(params[:user])
redirect_to #user, notice: 'User was successfully updated.'
else
render action: 'edit'
end
end
def destroy
#user.destroy
redirect_to users_path
end
private
def get_user
#user = User.find(params[:id])
end
def set_user
#user = User.new(params[:user])
end
end
Some people say that it seems like a magic, but it's DRY. What do you think?
They're not hidden, they're right there.
Personally, when it comes to DRY, there's a rule I like to follow (I read it somewhere but I don't remember where, forgive me) - the first time you want to duplicate content, you copy and paste it with a frown, but if you want to duplicate it again, that's when you extract it out into one place.
Your :load_user example is fine, but I wouldn't bother with :set_user.
This is too DRY for me.
before_filter for routine stuff like instance variable initialization drives me insane because the method appears blank, yet stuff is happening. Not a big deal if the method is otherwise empty, but large methods may obscure the filter, or you might overlook it altogether. You then have to hunt down the filter method and mentally reconstruct the workflow. It makes maintenance more difficult than it needs to be.
I would forego filters and call getter/setter methods in place:
def show
get_user
end
That way you can see where initialization is happening. If you insist on using filters, put a comment in the method advising that filters are being applied.
Personally, I reserve before_filter for conditional logic only.
Hey guys I created some custom authentication thanks to railscasts.com but I'm somewhat stuck as I need to restrict my users from editing other users' profiles.
Here's my authenticate_user and current_user methods:
private
def current_user
#current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
end
def authenticate_user!
if current_user.nil?
redirect_to login_url, :alert => "You must first log in to access this page"
end
end
Here's the before_filter in my UsersController:
before_filter :authenticate_user!, :only => [:edit, :update, :destroy]`
EDIT: Fixed it thanks to alock27.
I had to edit my users_controller and modify the edit action as follows:
#user = User.find(params[:id]
redirect_to root_url unless current_user == #user
I think you want this:
Adding security on routes in Rails
you need to find the User by :id and check if current_user = #user
You don't have to provide an id for edit, update and destroy: you already have current_user.
Instead of editing #user = User.find(id), edit current_user. Thus, your authentication functions ensure the user will only edit its own profile.