Reputation: 35339
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?
Upvotes: 2
Views: 420
Reputation: 718
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. :)
Upvotes: 1
Reputation: 1240
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.
Upvotes: 1
Reputation: 1102
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 :)
Upvotes: 1
Reputation: 460
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
Upvotes: 1