Yesterday, I gave a talk at SLUG (the Sydney Linux Users Group) about the open source software that we’re using in Vquence. The talk was basically structured into three areas: open source software in business operations, in software development, and in system operations. John joined in for the harder questions on the systems operations. We also explained how we’ve set our systems up so we are scalable on data (in particular on video slices, images and video use statistics) at a minimum cost – which includes the use of Amazon’s EC2 and S3 services. We also use reverse proxies to be scalable with bandwidth cost. Here are the talk slides.
The Rails authorization plugin is a really nice way of providing role management and restrict access to specific features. It also works nicely with the RESTful authentication plugin which manages user login and authentication.
However, there is a serious lack of documentation of how to use this plugin – the README.txt inside the plugin and the source code is the best I have found. I also learnt some from the slides for the Railsconf 2006 on “Metaprogramming Writertopia”. This blog entry is collecting what I have learnt and also freely copies some text from the different sources.
Assuming you have installed the authorization plugin, you need to extend your models with the plugin. In particular the User model and the model(s) that you would like to use multiple user roles for.
class User < ActiveRecord::Base
# Authorization plugin
class Account < ActiveRecord::Base
# Authorization plugin
The acts_as_authorized_user part of the plugin creates the following methods for the User model:
- has_role? role_name [, authorizable_obj]: finds out if a user has a certain role (for a certain object)
- has_role role_name [, authorizable_obj]: creates the role if non-existant, and assigns the role to the user (for a certain object)
- has_no_role role_name [, authorizable_obj]: remove role from user (for a certain object), and the role if not in use any longer
As some background information, the plugin creates 2 tables - one for the roles (name, authorizable_type, authorizable_id, timestamps), and one that maps roles to users roles_users (user_id, role_id, timestamps). The authorizable_type and authorizable_id map the role to the authorizable_obj.
The acts_as_authorizable part of the plugin creates the following methods for the Account model:
- accepts_role? role_name, user: finds out if the user has the role on the model
- accepts_role role_name, user: sets the user to have the role on the model
- accepts_no_role role_name, user: removes the user from having the role on the model
In the code, you can now use the following methods to create roles for users and accounts. Assuming we have a user 'u' and an account 'a', we can do one of the following to create the role 'admin':
- u.has_role 'admin', a
- a.accepts_role 'admin', u
- u.is_admin_for a
- u.is_admin (gives user the role 'admin', not tied to a class or object)
To check on roles, you can use the following:
- u.has_role 'admin', a: return true/false if the user has the role 'admin' on the account
- u.is_admin? a: return true/false if the user has the role 'admin' on the account
- u.is_admin_of? a: return true/false if the user has the role 'admin' on the account
- u.has_role 'admin': return true/false if the user has the role 'admin' on anything
- u.is_admin?: return true/false if the user has the role 'admin' on anything
- u.is_admin_of_what Account: returns array of objects for which this user is a 'admin' (only 'Account' type)
- u.is_admin_of_what: returns array of objects for which this user is a 'admin' (any type)
- a.accepts_role? 'admin', u: return true/false if the account has the user with the role 'admin'
- a.has_admin(s)?: return true/false if the account has users with the role 'admin'
- a.has_admin(s): returns array of users which have role 'admin' on the account
There are more dynamically generated methods and they are created through the method_missing hook. There is a whole domain-specific language behind this creation of methods. Just about everything that sounds like proper English will work.
An interesting twist is that roles can also be set on model classes:
u.has_role 'admin', Account. So, roles can be set on one of the following three scopes:
- entire application (no class or object specified)
- model class
- an instance of a model (i.e., a model object)
In your controller, you can now use two methods to check authorization at the class, instance, or instance method level: permit and permit?. permit and permit? take an authorization expression and a hash of options that typically includes any objects that need to be queried:
if current_user.permit? 'site_admin'
# show all accounts
@account = Account.find(:all)
@account = current_user.is_admin_for_what(Account)
class AccountController < ApplicationController
permit "site_admin and admin", :except => public_page
permit "site_admin" do
render :text => "The Answer = 42"
The difference between permit and permit? is redirection.
permit is a declarative statement and redirects by default. It can also be used as a class or an instance method, gating the access to an entire controller in a before_filter fashion. permit? is only an instance method, that can be used within expressions. It does not redirect by default. You will find more information on the boolean expression of the permit or permit? methods here.