I am getting an unexpected behaviour for a simple cancancan authorization.
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
# Define abilities for the passed in user here. For example:
#
user ||= User.new # guest user (not logged in)
if user.is_admin?
can :manage, :all
elsif user.is_standard?
can :manage, ServiceOrder, {user_id: user.id}
can :manage, ServiceOrderDetail, :service_order => { :user_id => user.id }
end
service_order.rb controller (partially shown)
class ServiceOrdersController < ApplicationController
authorize_resource
def show
@service_order = ServiceOrder.includes(:service_order_details).find(params[:id])
end
end
This does not work, as it lets the controller show ANY service_order record, instead of just those owned by the current_user.
The only way that this works is if I manually authorize the controller adding:
authorize! :show, @service_order
like this:
def show
@service_order = ServiceOrder.includes(:service_order_details).find(params[:id])
authorize! :show, @service_order
end
which makes no sense since authorize_resource
is supposed to be doing that.
What is happening is the authorize_resource
is happening before the show action, and since the @service_order
is not set yet, it is checking against the class, and the user does have access to show a ServiceOrder
just under constraint.
Adding authorize_resource will install a before_action callback that calls authorize!, passing the resource instance variable if it exists. If the instance variable isn't set (such as in the index action) it will pass in the class name. For example, if we have a ProductsController it will do this before each action.
authorize!(params[:action].to_sym, @product || Product)
What you will need to do is load_and_authorize_resource
as suggested by widjajayd. or (if you do not want to use the cancancan default load action) do a before_filter
that loads the resource manually using your custom method before the authorize_resource
call.