Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Context aware authorization using CanCan

I want to use CanCan to handle my permissions. My site has many different permissions levels, and most of them are context aware. For instance, Here are the relations in my 3 main models:

class User < ActiveRecord::Base
  has_many :league_relations
  has_many :leagues, :through => :league_relations
end

class League < ActiveRecord::Base
  has_many :league_relations
  has_many :users, :through => :league_relations
end

class LeagueRelation < ActiveRecord::Base
  belongs_to :user
  belongs_to :league
end

Note, LeagueRelations is a nested resource of Leagues. What I want to do is allow a user to modify leagues, and gauge each user's authorization based off of data stored in league_relation. I would then like a user to modify league relation, based only the data stored in the user model.

To be succinct: I basically want LeagueRelations to be used to authorize League actions, and Users to be used to authorize LeagueRelations actions. i.e. league_relation.owner = true to delete a League, but user.owner? must be true to delete a LeagueRelation. How can I authorize based on the attributes of league_relation when inside the league controller, and authorize other actions in other controllers on other models. Please leave a comment if you need more clarification.

Thanks.

like image 705
Max Avatar asked Aug 18 '11 04:08

Max


People also ask

What is CanCan authorization?

CanCan is an authorization library for Ruby on Rails which restricts what resources a given user is allowed to access. All permissions are defined in a single location (the Ability class) and not duplicated across controllers, views, and database queries.

Can can can in Rails?

CanCanCan is an authorization library for Ruby and Ruby on Rails which restricts what resources a given user is allowed to access.


1 Answers

Ok, I solved the problem. My use case is briefly mentioned in the beginning of the CanCan README and I missed it. You can define new Ability classes in app/models/ that take in a different parameter other than current_user. To do so, you put the following in your controller:

def current_ability 
  if params[:controller] == 'leagues'
    @current_ability = LeagueAbility.new(current_user_league_relation)
  elsif params[:controller] == 'league_relations'
    @current_ability = LeagueRelationAbility.new(current_user_league_relation)
  else
    @current_ability = Ability.new(current_user)
  end
end

Now you can create league_ability.rb in app/models/.

class LeagueAbility
  include CanCan::Ability

  def initialize(league_relation)
    league_relation ||= LeagueRelation.new

    if league_relation.owner?
      can :manage, League, :id => league_relation.league_id
    elsif league_relation.moderator?
      can :manage, League, :id => league_relation.league_id
      cannot [:delete, :destroy], League
    else
      can :read, League
      can :create, League
    end    
  end
end

One thing to note is that this relies on your application controller calling a method in a child class. Hope that helps!

like image 111
Max Avatar answered Oct 05 '22 16:10

Max