बुधवार, एप्रिल २५, २०१२

Working With CanCan : Basic Implemtation

Starting with CanCan implementation :

For implementing cancan we just have to add below text in gemfile:
gem 'cancan'
and run
bundle install
Then we have to create a class 'Ability'.
rails g cancan:ability
In this new class we can define our authorization code just like:
class Ability
  include CanCan::Ability
    def initialize(user)
      user ||= User.new #guest user
      if user.role?("Admin")
        can :manage, :all
      else
        # Users other than admin have only read access.
        can :read, :all
      end
    end
 end

In my application I have has_many :through relationship for User and Role to manage different roles and users.
You can either have a boolean field admin in your users table, Through which you check whether the user is admin or not.

The authorization part is not handled for the application yet.

To restrict user from accessing the information needs to be handled in views.

Example:
You have edit, update and delete actions for your product. So we can restrict as follow .
<% if can? :update, @product %>
  <%= link_to "Edit", edit_product_path(product.id) %>
<% end %>

<% if can? :destroy, @product %>
  <%= link_to "Delete" , product_path(product.id), :confirm => "Delete this product?", :method => :delete %>
<% end %>
Authorization at view end is alway not enough, it is better to have it in controller part as well..

We can do in two ways
First, we can check in each action whether user has access to the action and unauthorize him.
def new
 @product = Product.new
end

def edit
 @product = Product.find(params[:id])
 unauthorized! if cannnot? :update, @product
end
Checking authorization for each action might be tedious task, but ofcourse depends on the requirement of the controller. If you just want to add it for several actions and not for the whole controller.

Second way is, CanCan provides a method load_and_authorize_resource which actually loads the resource and authorize it. This works as a before filter for your controller
This means we don't have to find our main object in our controller's action, if we are using restful architecture.
class ProductController < ApplicationController
  load_and_authorize_resource
  def new
  end

  def edit
  end
end

This will still work, though we haven't find our @product in new and edit action, load_and_authorize_resource with do it for us..

Now, even if the user tries to acces the unauthorized action through url, it will through error saying,
CanCan::AccessDenied in ProductsController#new
You are not authorized to access this page.

You can also display suitable message or 500 error page as you require.
You just have to define following in your Application controller.
rescue_from CanCan::AccessDenied do |exception|
  flash[:error] = "Access denied."
  redirect_to root_url
end
-------------------------------------------------------------------------------
CanCan implement करण्यासाठी आपल्याला फक्त हि line आपल्या Rails प्रोजेक्ट च्या GemFile मध्ये टाकावी लागते.
gem 'cancan'
नंतर
bundle install
Run करा
आत्ता एक 'Ability' class बनवा.
rails g cancan:ability
ह्या नवीन क्लास मध्ये आपण User ला access प्रमाणे अधिकार देऊ.
class Ability
  include CanCan::Ability
    def initialize(user)
      user ||= User.new #guest user
      if user.role?("Admin")
        can :manage, :all
      else
        # Users other than admin have only read access.
        can :read, :all
      end
    end
 end
माझ्या application मध्ये, मी User's आणि त्यांचे Role's manage करण्याकरिता has_many :through relationship वापरली आहे.
तुम्ही एकतर तुमच्या users table मध्ये admin साठी एक boolean field टाकू शकता, जेच्यात तुम्ही check करू शकता के user admin आहे के इतर user.
अजूनही आपण authorization handle केलेली नाही.

User ला अधिकार नसलेल्या माहिती ला access न होऊ म्हणून आपण view मध्ये check लावू शकतो.
Example:
आपल्या कडे product साठी edit, update आणि delete असे action आहेत. तर आपण user ला restrict अश्या प्रमाणे करू शकतो.
<% if can? :update, @product %>
  <%= link_to "Edit", edit_product_path(product.id) %>
<% end %>

<% if can? :destroy, @product %>
  <%= link_to "Delete" , product_path(product.id), :confirm => "Delete this product?", :method => :delete %>
<% end %>
Authorization फक्त view end ला enough नाही, हे Controller मध्ये असणे पण गरजेचं आहे.

आपण हे दोन पद्धतीने करू शकतो.
एक, आपण प्रत्येक controller च्या प्रत्येक action मध्ये check करू सक्तो के user authorize आहे के नाही आणि मग त्याला unauthorize करू शकतो.
def new
 @product = Product.new
end

def edit
 @product = Product.find(params[:id])
 unauthorized! if cannnot? :update, @product
end
प्रत्येक action मध्ये access check करणे खूप कंटाळवाणे होऊ शकते. दुसरी पद्धत आहे.
Cancan मध्ये आपल्या एक method available आहे load_and_authorize_resource म्हणून. हि method प्रत्येक action च्या आधी आपले user resource load करते आणि त्याला authorize करते. हे आपल्या controller मध्ये before filter सारखा काम करते.

ह्याचा अर्थ असा आहे क आपल्या प्रत्येक action च्यामध्ये आपले main object find करण्याची गरज नाही, जर आपण proper restful architecture use करत आहोत तर.
class ProductController < ApplicationController
  load_and_authorize_resource
  def new
  end

  def edit
  end
end
जरी आपण आपला main object @product new आणि edit action find नाही केले तरी हे सुद्धा एकदम व्यवस्थित work करेल. @product हे load_and_authorize_resource method आपल्यासाठी find करते.
आता जरी user unauthorized action ला url through access करायचे प्रयत्न केले तरी त्याला हा error दिसेल.
CanCan::AccessDenied in ProductsController#new
You are not authorized to access this page.
आपण ह्या error च्या ऐवजी 500 error page किंवा काही message दाखवू शकतो.

आपल्याला फक्त खालील code आपल्या Application controller मध्ये टाकावा लागेल.
rescue_from CanCan::AccessDenied do |exception|
  flash[:error] = "Access denied."
  redirect_to root_url
end