I'm using CanCan 1.6.1 in a Rails 3.2 application. I use authorize_resource
in my controllers to limit what users can :read, :create, :update: and :delete
.
In my V2Posts controller
, the authorize_resource
call isn't working for my :show
action.
In my abilities file, I'm trying to limit the user to only read their own posts. There's no error, but unfortunately, they're able to view other users posts.
class V2PostsController < ApplicationController
layout 'panel_layout', only: :show
before_filter :redirect_if_existing, only: [:new, :create]
authorize_resource
def show
@v2_post = V2Post.find(params[:id], include: :user_updates)
@organization = @v2_post.organization
respond_to do |format|
format.html
format.js
end
end
...
end
ability.rb:
class Ability
include CanCan::Ability
@user = nil
def initialize(user)
alias_action :taco,
:sandwich,
to: :read
@user = user
if user.is_in_role?(:admin)
can :manage, :all
else
default_rules
user.roles_list.each do |role|
meth = :"#{role}_rules"
send(meth) if respond_to? meth
end
end
end
def default_rules
cannot [:read, :create, :update, :destroy], :all
...
end
def pro_user_rules
can :read, V2Post, proid: @user.id
end
...
end
models/v2_post.rb:
class V2Post < ActiveRecord::Base
attr_accessible :proid, :user_updates_attributes, :organization
belongs_to :user, :foreign_key => "proid"
has_many :user_updates, as: :pro_post, dependent: :destroy
accepts_nested_attributes_for :user_updates
end
load_and_authorize_resource
works for keeping the users from viewing other users' posts. But, it loads the resource for me and also adds additional database calls to other actions. Shouldn't authorize_resource
work for my case? I've explicitly defined @v2_posts in the show action (loading the resource myself). Why isn't it getting passed to the authorize_resource
call?
Given that, which of load_and_authorize_resource
and authorize_resource
works better, and what are the benefits/drawbacks of each? I've read the documentation, I'm confused.
The issue is that authorize_resource
is being run as a before filter, and the instance isn't being set until inside your action--after authorize_resource
is called. When the instance is nil, the authorize_resource
method defaults to authorizing the class V2Report
. Authorizing the class ignores the attribute conditions (e.g. can :read V2Report, user_id: @v2_post.user_id).
You can use load_and_authorize
resource, and have CanCan set the instance variable in your controller before each action. For actions that load a resource in a more complicated way, like your V2Post.find(params[:id], include: :user_updates)
, you can create a custom prepend_before_filter
method that loads the resource for certain actions. As long as that's prepended, that loaded instance will be passed to the authorize_resource
method. The load_and_authorize_resource
will not make an additional call to the database if the instance is already loaded.
class V2PostsController < ApplicationController
prepend_before_filter :custom_load_resource, only: [:show, :edit]
load_and_authorize_resource
def show
render @v2_post
end
private
def custom_load_resource
@v2_post = V2Post.find(params[:id], include: :user_updates)
end
end
You can also forget the load_resource
altogether, and use your own before_filter 100% of the time. Or, you can ditch the filters and call authorize!
from within the actions. In my recent experience, the approach detailed above works nicely for RESTful controllers.