ruby-on-railscancancancancan

cancancan authorize_resource not working as expected


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.


Solution

  • 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)

    from Cancancan documentations

    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.