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

11 thoughts on “Rails Authorization Plugin

  1. 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”.

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

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

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

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

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

  7. Hi
    silvia
    Nice tutorial as you pointed out for both the model User and Account, and usage of acts_as_authorized_user and acts_as_authorizable as both of this will create some methods by which authorization can be done and even can be set in a model class, Now one thing that baffling me is after db:migrate the tables roles and roles_user becomes empty, so whether i need to manually enter the roles in the database or if not is there any GUI by which i can assign roles for each user ?

    DJ

  8. Hi Archie

    ups, I just noticed I never replied to your post.

    # how do you restrict a teacher to his or her school only?
    This should work:
    teacher.has_role

  9. Hi DJ,

    initially, the tables are indeed empty. I have created an insert into the table in my migration. It looks something like this:

    def self.up
    create_table :roles_users, :id => false do |t|
    t.integer :user_id, :role_id
    t.timestamps # creates updated_at and created_at
    end

    create_table :roles do |t|
    t.string :name, :authorizable_type, :limit => 40
    t.integer :authorizable_id
    t.timestamps
    end

    # insert a siteadmin user for admins with role site_admin
    siteadmin = User.new({:nick => ‘siteadmin’,
    :email => ‘admin@example.com’,
    :password => ‘example’,
    :firstname => “Site”,
    :lastname => “admin”})
    siteadmin.has_role ‘site_admin’
    end

    Hope this helps.

Leave a Reply

Your email address will not be published. Required fields are marked *