0
votes

How can I correctly authorize a resource that is nested with devise and cancan? I have implemented the suggested procedure from the documentation but without success.

This issue involves the ability model not getting the user id from the tertiary layer of a nested resource using devise and cancan gems. I am able to get the user id from the secondary layer, however. Any help is greatly appreciated!

I have a nested resource like so:

  resources :users do
    resources :clients do
      resources :positions
    end
  end

  resources :clients do
    resources :positions
  end

  resources :users do
    resources :positions
  end

  resources :users
  resources :clients
  resources :positions 

With the position model controller using the following:

class PositionsController < ApplicationController
  before_filter :grab_client_from_client_id
  load_and_authorize_resource :user
  load_and_authorize_resource :client, through: :user, shallow: true
  load_and_authorize_resource :position, through: :client, except: [:index], shallow: true
  ...
end

The ability.rb file:

class Ability
  include CanCan::Ability

  def initialize(user)

    user ||= User.new # guest user (not logged in)

    if user.has_role? :admin
      can :manage, :all
    elsif user.has_role? :management
      can [:create, :read, :update], :all
    else
      can :read, :all, user_id: user.id
    end

  end
end

This results in a non-admin/non-management user receiving the following error:

undefined method 'user_id' for #<User:0x5227d40>

Clearly something is not set up correctly. I have gone through the documentation over and over for each gem, as well as searching everywhere looking for a solution.

I will also provide my model relationships below.


class User < ActiveRecord::Base
  has_many :clients
  has_many :positions, through: :clients
  resourcify
  ...
end


class Client < ActiveRecord::Base
  resourcify
  has_many :checklogs
  has_many :positions
  belongs_to :user
end


class Position < ActiveRecord::Base
  resourcify
  belongs_to :client
  delegate :user, to: :client, allow_nil: true
end
1

1 Answers

4
votes

The problem lies in this line:

can :read, :all, user_id: user.id

When you are checking whether a user can read something it checks what you are trying to read.

Since you have this line in your controller:

load_and_authorize_resource :user

The resource that you are trying to authorize is a user.

Your ability will compare user.user_id with current_user.id. A user doesn't have a user_id, so that is where the error comes from.

Based on your code I suspect you want a user only be able to read things that are his unless he is a manager or an admin.

You can achieve this in the following manner:

if user.has_role? :admin
  can :manage, :all
elsif user.has_role? :management
  can [:create, :read, :update], :all
else
  can :read, User, id: user.id
  can :read, Client, user_id: client.id
  can :read, Position, client: { user_id: user.id }
end

This way a user can only access those models that he has a relation to.