jokklan
jokklan

Reputation: 3550

Best ability based authorization

I have a Rails 3 app, where i use CanCan for authorization, but i am looking for something that suits my needs better. My authorizations is not very role based, but really more situational.

The application is for managing daily dinner clubs at a college/dorm, where the people takes turn making dinner for the rest. A chef have therefore more permissions for the dinner club he is responsible for (like changing the menu), and a participant have more permissions (like adding guests) then one who is not participating in that single dinner club.

What i need is therefore some authorization system that depends on the relationship between the user and the dinner club or kitchen (a kitchen have many dinner clubs), not the users role.

If it helps, are my current cancan abilities here (as you see they are pretty complex)

# In ability.rb

def initialize(user)
  # edit (like changing time or menu) for a dinner club
  can :edit, DinnerClub do |dinner_club|
    dinner_club.is_chef?(user) &&
    !dinner_club.canceled?
  end

  # open or close a dinner club
  can [:open, :close], DinnerClub do |dinner_club|
    !dinner_club.passed? &&
    dinner_club.is_chef?(user)
  end

  # cancel or resume a dinner club
  can [:cancel, :resume], DinnerClub do |dinner_club|
    !dinner_club.passed? &&
    dinner_club.is_chef?(user)
  end

  # register or unregister for at specific dinner club
  can [:register, :unregister], DinnerClub do |dinner_club|
    dinner_club.open? &&
    (dinner_club.kitchen_id == user.kitchen_id || dinner_club.kitchen.open_registrations?) &&
    !dinner_club.is_chef?(user)
  end

  # add guests to a dinner club
  can [:add_guests], DinnerClub do |dinner_club|
    dinner_club.open? &&
    (dinner_club.kitchen_id == user.kitchen_id || dinner_club.kitchen.open_registrations?) &&
    dinner_club.registered?(user)
  end

  # take the dinner club from another use if the dinner club is canceled
  can [:take], DinnerClub do |dinner_club|
    dinner_club.canceled? &&
    (dinner_club.kitchen_id == user.kitchen_id || dinner_club.kitchen.open_registrations?) &&
    !dinner_club.passed? &&
    !dinner_club.is_chef?(user)
  end

  # create a new dinner club
  can [:create], DinnerClub do |dinner_club|
    (dinner_club.kitchen_id == user.kitchen_id || dinner_club.kitchen.open_registrations?)
  end

  # comment on existing dinner clubs
  can [:comment], DinnerClub do |dinner_club|
    dinner_club.registered?(user)
  end

  # can see dinner clubs for this kitchen
  can :read, Kitchen do |kitchen|
    (kitchen.id == user.kitchen_id || kitchen.open_registrations?)
  end

  # can manage the kitchen, like changing name and configuration options
  can :manage, Kitchen, admin_id: user.id
end

Upvotes: 0

Views: 113

Answers (1)

Arjan
Arjan

Reputation: 6274

You can rewrite most of these blocks in CanCan to make them a lot less complex.

can :edit, DinnerClub, cancelled: false, chef_id: user.id

# open or close a dinner club
can [:open, :close], DinnerClub, passed: false, chef_id: user.id

# take the dinner club from another use if the dinner club is canceled
can [:take], DinnerClub, cancelled: true, kitchen_id: user.kitched_id, passed: false  
can [:take], DinnerClub, cancelled: true, kitchen: { open_registrations: true }, passed: false
cannnot [:take], DinnerClub, chef_id: user.id

This is assuming the cancelled? and passed? methods are derived from boolean attributes on the DinnerClub model and that the is_chef? method checks a chef_id. But it illustrates the idea.

CanCan is mostly used to define abilities based on resources, so I would go with that.

Upvotes: 1

Related Questions