Elvin Efendi's personal website

 

hierarchical user authorisation in rails using cancan

13 Mar 2012

Assume that we need a web application that should have users with different roles. Furthermore let’s say we need these roles to be hierarchic in other word suppose we have Admin, Manager, Seller, Buyer and Reporter roles. Admin can do everything that Manager, Seller, Buyer can, Manager can do everything that Seller and Buyer can. Seller, Buyer and Reporter are the last elements of this hierarchy. So there is an hierarchy between roles. Lets implement this authorisation system. I will use Mongoid ODM in my models. First we need to create User model as follows:

class User
  include Mongoid::Document

  field :name, type: String
  field :mobile, type: String
  field :phone, type: String
  field :address, type: String

  belongs_to :role

  def role?(base_role)
    self.role ||= Role.default
    role.is_parent_of? base_role
  end

end

Now let’s define Role model

class Role
  include Mongoid::Document
  
  field :name, type: String
  field :parent_id, type: BSON::ObjectId

  # validations
  validates_uniqueness_of :name
  
  has_many :users

  def is_parent_of? base_role
    while base_role && base_role != self
      begin
        base_role = Role.find base_role.parent_id || 0
      rescue Mongoid::Errors::DocumentNotFound => e
        base_role = nil
      end
    end

    base_role == self
  end

  def parent= parent_role
    self.parent_id = parent_role.id
  end

  def self.method_missing method_id
    where( name: method_id ).last # allow us get a role as Role.admin etc.
  end

  # return default role
  def self.default
    Role.buyer
  end

end

Defining User model and Role model in this way will allow us to make role hierarchical. That is, in the console we can try following: Before trying in console create sample models and users.

admin = User.find <admin user id>
admin.role? Role.admin # => True
admin.role? Role.manager # => True
manager = User.find <manager user id>
manager.role? Role.seller # => True
manager.role? Role.admin # => False

Now we have general relationship logic between roles that can be used either for creation hierarchical or non hierarchical roles. It is time to create abilities. I suppose you can use CanCan. First we need to create Ability model to define abilities. To do this CanCan has generator like this: rails g cancan:ability. After running this command edit the Ability model and define abilities according to your application’s business logic. Some samples:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new

    if user.role? Role.buyer
      can :read, Product
    end

    if user.role? Role.seller
      can :update, Product
    end

    if user.role? Role.manager
      can :manage, User
    end

    if user.role? Role.reporter
      can :manager, Report
    end
    
    # See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities
  end
end

In this case because of we have hierarchy so that admin is also manager admin will be able to read, update Product, manager User and Report. But reporter wil only be able to manage reports.


comments powered by Disqus