theKindOfMe

November 19, 2010

Rails Authorization wiht CanCan

Filed under: Uncategorized — Tags: , , — yasi8h @ 5:59 am

Intro

There are many authentication frameworks for rails. And a lot of people do use for getting basic authentication working in their apps. However most people tend to implement their own thing when it comes to authorization. I think this is because most people feel that their authorization needs are pretty simple and hence need no plugins. But when an application grows these home cooked solution may not scale well. After all why reinvent the wheel? (well i am sure that there is a time and a place where reinventing wheel is appropriate).

CanCan is a authorization framework that aims to be simple and provide you a central place to manage all your authentication logic. There are however more complex authorization frameworks out there. So if you do feel that CanCan doesn’t give you all the control you need you can do some googling.

You can get CanCan from https://github.com/ryanb/cancan.

Defining Authorization Logic

What i like the most about CanCan is its ability to define all the authentication logic in one single class. This is called Ability.

The following is an example from ASCII Casts episode 192 about CanCan. (Yes there are some railscasts and some asciicasts about CanCan).

class Ability
include CanCan::Ability

def initialize(user)
can :read, :all
end
end

A bit more complicated example from one of the projects that i am working on right now looks like this.

class Ability
include CanCan::Ability

def initialize(user)
if user && user.role == ‘Admin’
can :manage, :all
elsif user && user.role == ‘Producer’
#can :read, :all

can [:index, :new, :create], User # can view existing users and add new users to the system

can [:show, :edit, :update, :destroy], User do |user_o| # can edit/destroy any users/producers who are associated to the current_producer’s publications
user.users_related_to_my_active_publications([“Producer”, “User”]).include? user_o
end

# Catch all block that authorize doing anything on the rest of the Classes
#can :manage, User
can :manage, Asset

elsif user && user.role == ‘User’
#can :read, :all
can :index, User #as the root controller of the application is set to UsersController we need to let every role into UsersController’s index action.
#however this does not mean everyone can see a list of users. Non admins will be redirected.
can [:show, :update], User do |user_o| #let users update and view their own profiles, but not others
user_o.id == user.id
end

# Catch all block that authorize doing anything on the rest of the Classes
can :manage, Asset
can :manage, Audio
else
can :read, :all
end
end
end

CanCan is mostly independent from any authentication framework that you may use. It is not tightly coupled with any specific frameworks. But it does depend on the controllers having a method called ‘current_user’. This method should give the currently logged in user. So when CanCan wants to authorize some action it will grab the output from current_user and pass it in to the initialize method of the ability class. Inside the initialize method you put your custom logic and tell CanCan what and what not a certain role can do. This is accomplished by using the can method. With the can method you can define access levels for user roles per models.

When you say can:read, Asset. It means that the user role concerned can access controller actions that allows reading Assets. For example listing all the assets or viewing a asset through the show action. So in any can :x, :y statement, the x tells cancan what actions should be allowed and the y tell cancan for what objects (Users, Books…etc) the x actions are allowed for.

The actions available are what you have in any scaffold generated restful controller. However for ease cancan have grouped some of them in to some ‘actions’.

def default_alias_actions
{
:read => [:index, :show],
:create => [:new],
:update => [:edit],
}
end

The above method is from cancan’s source code and it defines the default alias for actions. So if you pass in :read it would mean that you are authorizing the index and show actions. The :manage action would basically authorize all actions.

If you want to check for custom actions you could just use those instead of the standard rest actions. For more info look at (https://github.com/ryanb/cancan/wiki/custom-actions).

You can pass in conditions to the can statements as follows (from https://github.com/ryanb/cancan).

can :read, Project, :active => true, :user_id => user.id

The conditions passed in as a hash in the third argument are tested against the user object on which the authorization is performed on.

Or as a code block.

can [:show, :update], User do |user_o| #let users update and view their own profiles, but not others
user_o.id == user.id
end

I want to let user’s update their own profiles but not others. So i have added a small check for that inside little block that is passed in as a argument to the can method.

Now you know how to write some basic authorization rules. But before cancan can get to work. It need to be ‘plugged in’ to your controllers. So we have to call the method

load_and_authorize_resource

in any controller on which we want out authorization rules applied. This method call will basically add a before filter that invokes the authorization. Or else you can put the authorization logic in separate controller actions, like:

def show
@article = Article.find(params[:id])
authorize! :read, @article
end

The above code (from https://github.com/ryanb/cancan) would check for the given ‘abilities’ for the current_user and throw a exception if the current_user does not have needed authorization.

You can also check the authorization logic in a adhoc manner.

<% if can? :update, @issue %>
<%= link_to “Edit Issue”, edit_issue_path(@issue) %>
<% end %>

The above code would check whether the current user have the right to access the edit action of the articles controller.

Handling Authorization Exceptions

When a user tries to do something that s/he is not authorized to (and given that you are invoking the correct cancan methods to check for authorization) cancan will throw a cancan access denied exception. This generates a ugly error message. So you can handle this in way you want by catching it like the following code (from https://github.com/ryanb/cancan) demonstrates.

class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
flash[:alert] = exception.message
redirect_to root_url
end
end

Authorization when fetching ActiveRecord records

CanCan does support doing authorization when retrieving records, however this have some limitations. For more info look at http://wiki.github.com/ryanb/cancan/fetching-records.

Drawbacks / Shortcomings

CanCan is a neat little gem. However it have its pros and cons. As for the cons i feel that its more easily used with restful controllers and not so easily with none restful controllers. This is not to say that you can’t use it with non restful controllers. I think CanCan is better suited for instances where you have some straight forward authorization work. As i have mentioned earlier in the article cancan tries to be simple and not too overly complicated. So it doesn’t have a lot of fancy features. But it does what it promises to do, and its pretty neat at it!.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: