Wagner Braga
Wagner Braga

Reputation: 497

Multiple levels of authorization with cancancan

I am facing a challenge. A multi level authorization system and a friend told me cancancan could help me out.

I took a read of documentation but couldn't figure it out. So I'll explain here. My goal is to create a sys were:

Also

Now, each user can see only records made by themselves, or from users that are below than, but not above or from other departments.

How should I start with the models and everything?

Upvotes: 1

Views: 243

Answers (1)

Bustikiller
Bustikiller

Reputation: 2498

First of all, you will need to keep track of this tree at database level. In order to do that, you can add a parent_id reference to the users table that will reference the user that created that particular user. This column will be empty for those users that were not created by anyone, and would hold the id of the parent user otherwise. I would highly recommend using a foreign key too, in order to guarantee referential integrity.

Then you will need to define your CanCanCan abilities in a way that this tree structure is traversed properly. You can take as a reference this example derived from the docs:

class Ability
  def initialize(user)    
    can :read, Photo, Book do |resource|
      resource.creator.descendant_of? user
    end
  end
end

I am assuming the User model has a descendant_of? method that traverses the tree from the current user towards the root checking whether the given user is a parent of the creator of the resource. A possible implementation could look like this:

# app/models/user.rb

def descendant_of?(target_user)
  parent_id == target_user.id || parent&.descendant_of?(target_user)
end

Depending on the characteristics of your users tree, this proposal can quickly become poor in terms of efficiency, as all users from the owner to the root may be loaded from database in order to perform these checks. Here a couple of ideas that could be worth to explore in case you start facing these issues:

  1. Use an array-like type for the parent_ids column to store the whole list of parent ids. The main drawbacks I see with this proposal is that it adds some overhead if there are changes in the tree, and referential integrity constraints gets lost (AFAIK it is not possible to add a foreign key in this scenario).
  2. In addition to the proposed above, modify the CanCanCan ability to run the query directly against the database instead of doing that check in memory (executing ruby code).

Upvotes: 1

Related Questions