4
votes

I have a nested resource for which I'm using Cancan to do authorization. I need to be able to access the parent object in order to be able to authorize the :index action of the child (since no child instance is passed for an :index action).

# memberships_controller.rb
class MembershipsController < ApplicationController
  ...
  load_and_authorize_resource :org
  load_and_authorize_resource :membership, through: :org
  ..
end

ability.rb

can [:read, :write], Membership do |membership|
  membership.org.has_member? user
end

This doesn't work for the :index action

Unfortunately the index action doesn't have any membership instance associated with it and so you can't work your way back up to check permissions.

In order to check the permissions, I need to interrogate the parent object (the org) and ask it whether the current user is a member e.g.

# ability.rb
...
can :index, Membership, org: { self.has_member? user }

Cancan almost lets me do this...

Cancan states that you can access the parent's attributes using the following mechanism: https://github.com/ryanb/cancan/wiki/Nested-Resources#wiki-accessing-parent-in-ability

# in Ability
can :manage, Task, :project => { :user_id => user.id }

However this just works by comparing attributes which doesn't work for my case.

How can I access the parent object itself though?

Is there any way to access the parent object itself within the permissions?

2

2 Answers

5
votes

I recently faced the same problem and ended up with the following (assuming you have Org model):

class MembershipsController < ApplicationController
  before_action :set_org, only: [:index, :new, :create] # if shallow nesting is enabled (see link at the bottom)
  before_action :authorize_org, only: :index

  load_and_authorize_resource except: :index

  # GET orgs/1/memberships
  def index
    @memberships = @org.memberships
  end

  # ...

private

  def set_org
    @org = Org.find(params[:org_id])
  end

  def authorize_org
    authorize! :access_memberships, @org
  end

end

ability.rb:

can :access_memberships, Org do |org|
  org.has_member? user
end

Useful links

https://github.com/ryanb/cancan/issues/301

http://guides.rubyonrails.org/routing.html#shallow-nesting

1
votes

Can't you do something like this?

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)
    can :index, Membership, org: {id: user.memberships.map(&:org_id)}
  end
end