I'm using Devise for authentication and CanCan for authorization.
the goal
I have two models: User and Sponsorship, where Sponsorship provides a has_many :through relationship between user-as-sponsor and user-as-client.
I want to configure CanCan so a user with sponsor?
privileges can manage his or her own Sponsorships, that is, only sponsorships for which Sponsorship#client_id == user.id
. A user can also have admin?
privileges, in which case he or she can manage any Sponsorship.
the models
class User < ActiveRecord::Base
has_many :sponsor_links, :class_name => 'Sponsorship', :foreign_key => 'client_id'
has_many :sponsors, :through => :sponsor_links, :class_name => 'User'
has_many :client_links, :class_name => 'Sponsorship', :foreign_key => 'sponsor_id'
has_many :clients, :through => :client_links, :class_name => 'User'
def has_role?(role)
... return true if this user has <role> privileges
end
end
class Sponsorship
belongs_to :sponsor, :class_name => 'User'
belongs_to :client, :class_name => 'User'
end
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # handle guest user (not logged in)
if user.has_role?(:admin)
can :manage, :all
elsif user.has_role?(:sponsor)
# not sure if the third argument is correct...
can :manage, Sponsorship, :sponsor => {:user_id => user.id}
end
end
end
the routes
I've set up nested routes to reflect the fact that a sponsoring user owns his or her clients:
resource :users, :only => [:index]
resource :sponsorships
end
the question
What's the right way to load and authorize the user and sponsorship resources in my SponsorshipsController?
what I've tried
This is similar to an ordinary nested resource, which CanCan handles easily. But the relations have non-standard names (e.g. :sponsor_links rather than :sponsorships), and I haven't figured out how to configure the load_and_authorize_resource
declarations in my SponsorshipsController.
Among the many things I've tried that don't work ;), here's one of the simpler versions. (Note also that my Abilities may not be set up properly -- see above):
class SponsorshipsController < ApplicationController
load_and_authorize_resource :sponsor_links, :class_name => "User"
load_and_authorize_resource :sponsorships, :through => :sponsor_links
respond_to :json
# GET /users/:user_id/sponsorships.json
def index
respond_to @sponsorships
end
# GET /users/:user_id/sponsorships/:id.json
def show
respond_to @sponsorship
end
end
By rescuing CanCan::AccessDenied errors, I know that:
- In
index
with a:sponsor
user, authentication fails for the User. - In
index
with an:admin
user, authentication fails for the Sponsorship. - In
show
, regardless of the role, authentication fails for the Sponsorship.