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!.

Ubuntu 10.04, Nginx with Upload and Upload Progress Modules, RVM, Postgresql

Filed under: Uncategorized — Tags: , , — yasi8h @ 2:02 am

Had to setup a staging server recently. Thought of putting down the steps i followed in to a article. This thing is getting fun.

General Stuff

We need these libraries in order to compile the following stuff

sudo apt-get install build-essential libssl-dev libpcre3-dev git-core

These are some nice to have tools, install only if you use them.

sudo aptitude install htop

You might also want to run update-db so that locate would work.

sudo update-db

Nginx from Source

We are going to get nginx 0.7.65 with upload and upload progress modules and compile them from source. Please note that additional command line options passed in to some commands bellow via two ‘-‘ character might get jumbled up by blog/editor. So if you get any unusual errors while running the following commands check whether you are typing in the correct chars (if you do a copy paste you might be trying to execute some html chars that make no sense for the command you are running).

wget http://nginx.org/download/nginx-0.7.65.tar.gz
tar xzf nginx-0.7.65.tar.gz

wget https://github.com/masterzen/nginx-upload-progress-module/tarball/v0.8.2 –no-check-certificate
tar zxf masterzen-nginx-upload-progress-module-v0.8.2-0-g8b55a34.tar.gz

wget http://www.grid.net.ru/nginx/download/nginx_upload_module-2.2.0.tar.gz
tar zxf nginx_upload_module-2.2.0.tar.gz

cd nginx-0.7.65
./configure –add-module=../masterzen-nginx-upload-progress-module-8b55a34/ –add-module=../nginx_upload_module-2.2.0/
make
sudo make install

Ruby 1.8.7 with RVM

bash < <( curl http://rvm.beginrescueend.com/releases/rvm-install-head )
nano ~/.bashrc
source ~/.rvm/scripts/rvm
rvm install 1.8.7
rvm use 1.8.7 –default

Rails 2.3.5

We want to install an older version of rails so

gem install rails -v 2.3.5

Postgresql 8.4

sudo apt-get install postgresql-8.4
sudo nano /etc/postgresql/8.4/main/pg_hba.conf

Towards the end of the file look for a line like

local all postgres ident

and replace it with this line

local all all trust

The above line would allow full access to the db from the localhost WITHOUT any PASSWORD. So yeah be sure you know what you are doing. You could replace trust keyword with the md5 to enable password authentication.

sudo /etc/init.d/postgresql restart

Postgresql installation have already added a user called postgres. And this user can can connect to the db and have admin rights. So connect as postgres and add a user account and create a db. Be sure to give all the needed privileges to the user account you are creating. So that it can create new dbs…etc.

createuser builder -U postgres
createdb builder_development -U builder

Ruby Gems needed for postgresql – ruby integration

sudo apt-get install postgresql-server-dev-8.4
gem install pg

Thats it for me. Hope this helps.

November 10, 2010

Retrieving DISTINCT results with Active Record on Complex Queries

Filed under: Uncategorized — Tags: — yasi8h @ 8:53 am

If you want get distinct results with Active Record, there are some ways to go about it. The simplest is to just use SQL (ie: by using find_by_sql…).

But if you have a query like the following and do not want to switch to pure SQL. You could try the following.

Tables

User,
Publications,
PublicationsUsers – This helps maintain a ‘Users can have one or more Publications’ relationship between Publications and Users.

The following query gets all the users associated with all the publications of a given user.

@my_publication_ids is a array of publication ids like [0,5,1,21] and role is a string like ‘Admin’.

But the following statement will give you duplicate results. As in the collection returned can include the same user more than once. This is not the expected behavior.

User.all(:joins => :publications, :conditions => {:publications_users => {:publication_id => @my_publication_ids}, :users => {:role => role}})

Adding a :select => ‘DISTINCT “users”.*’ clause will solve the problem. Here note that we are interested in retrieving distinct users. Hence we have to apply the select distinct on users columns. Which looks like “users”.*

User.all(:select => ‘DISTINCT “users”.*’, :joins => :publications, :conditions => {:publications_users => {:publication_id => @my_publication_ids}, :users => {:role => role}})

Create a free website or blog at WordPress.com.