Rails Authorization Plugin
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
acts_as_authorized_user
...
end
class Account < ActiveRecord::Base
# Authorization plugin
acts_as_authorizable
...
end
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:
def index
if current_user.permit? 'site_admin'
# show all accounts
@account = Account.find(:all)
else
@account = current_user.is_admin_for_what(Account)
end
end
class AccountController < ApplicationController
permit "site_admin and admin", :except => public_page
…
def secret_info
permit “site_admin” do
render :text => “The Answer = 42″
end
end
end
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.
on June 11th, 2008 at 8:56 pm
What need is there for the “Account” part of the paradigm? Why not just have “Users” and “Roles” and allow or deny access based on that? Is the “Account” truly just “Roles” on steroids? The “Account” aspect seems a bit confusing”.
on June 12th, 2008 at 10:40 am
Hi Mike,
Thanks for pointing out that I didn’t really describe what I was doing. The “Account” is and example model that I used for describing how to make use of the users and their roles. In your application I’m sure you will want to use some other model to restrict user access based on roles. Accounts is my example.
Hope this helps.
on June 12th, 2008 at 2:32 pm
I see. So is it possible to have a User Role without the need to specify an object? For instance, I have an amazingly simple application that calls for 3 roles: Admin, Supervisor, Clerk. I have 2 controllers: UserContoller, InformationController.
Based on the user’s role, I will allow or deny access to creation, deletion, edit, etc. I have no need to place permissions on objects, rather, I need to allow/disallow access to specific controller functionality. No more, no less.
Can this plugin easily handle that or is it overkill?
Thanks.
on June 12th, 2008 at 2:41 pm
As mentioned in the article, 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)
So, yes, you can give users specific roles application-wide - just leave away the model name in most of the above commands and you will be set. In your controllers, you can then use the permit command to get your methods. See http://www.billkatz.com/authorization for more information on how to use the permit command.
Whether this is overkill, I cannot tell you. It’s more of a question whether you’d like to use and trust other people’s plugin code or prefer to write your own. IMO, in the long run, if you choose a supported plugin, it will be worth the time spent on it.
on July 2nd, 2008 at 7:49 pm
To developer:
Would you like to extend this great plugin folowed string:
private
def get_role
[.......] include => :roles_user
end
on July 12th, 2008 at 2:33 am
Mike -
I think this plugin, great as it is, is indeed overkill for your situation. If you use restful_authentication plugin to generate your auth system, all you’ll have to do is add an :auth attribute to the user model and a few lines of code to the generated lib/authenticated_system.rb to end up with:
.admin?, .super? .clerk? methods on user model (to be used anywhere needed)
admin_required, super_required, clerk_required methods (to be used in before_filters)
c.
on July 15th, 2008 at 7:33 pm
Hi Sylvia
Thanks for your efforts to throw more clarity on the use of this plugin. I am still trying to wrap my mind around this. For my application I am particulary interested in authorisation for specific model objects and would appreciate your advice on how to go about it.
To start off with I have the following scenario:
School Model:
has_many : teaching_posts #teachers employed
TeachingPost model: #users with ‘teacher’ and/or ’school_admin’ role
has_many: registration_classes # a teacher responsible for a specific classroom of students
belongs_to :school, :user
# how do you restrict a teacher to his or her school only?
# how do you allow a ’school_admin’ for school A only?
RegistrationClass Model #attributes name, teaching_post_id, start_date, end_date
has_many :student_admissions
belongs_to : teaching_post
# how do you restrict a teacher to his or her own class only etc. ?
StudentAdmission model: #users with ’student’ role
belongs_to :user, :registration_class
A user may have one or more of the following roles
admin #super admin
’school_admin’ #admin for a specific school only
‘teacher’ # for a specific school only
’student’ # for a specific school only
I hope you can see where I am going with this. I would appreciate any help.
Thanks