This is a guest post by our friends over at makandra, a cool Ruby on Rails development shop. Today they announce a great new Ruby gem for dealing with role-based permissions.
You know the game!
Each time you start a new application the same procedure starts over again: You set up your tools like git, trac & co. and prepare to get going. Then you look into your backlog and plan your first iteration:
- “As a moderator I should be able to edit and delete all posts in case somebody …”
- “As a superuser I want to create, edit and delete moderators and users.”
- “As a department director I should be able to allow or deny requests for leave for employees in my department.”
In almost every project we at makandra were involved with during the past year, some kind of role-based permission-system was required.
Over and over and over again…
After we had implemented a custom role system for the third time, it was enough – and we have learned some things about how complex those kinds of permissions can get. We extracted everything necessary and turned it into a gem. Today we proudly present Aegis – role-based permissions for your user models.
We put it all on github so you can use, modify and fork our little gem: http://github.com/makandra/aegis
To install Aegis into your project, add the following to your
Initializer.run block in your environment.rb:
config.gem 'makandra-aegis', :lib => 'aegis', :source => 'http://gems.github.com'
sudo rake gems:install to fetch all gems including Aegis.
Defining your permissions
Permissions-model in app/models, make it inherit from
Aegis::Permissions and define the roles and permissions you need:
# app/models/permissions.rb class Permissions :allow permission :edit_post do |user, post| allow :registered_user do post.creator == user # registered users may only edit their own posts end allow :moderator # moderators may edit any post end permission :read_post do |user, post| allow :everyone deny :guest do post.private? # guests may not read private posts end end end
See how nicely all permission related logic is gathered in one central place?
No more need to scatter a million
ifs all over your application.
To tell Aegis which models are equipped with roles, you add a string column
role_name to the
users table. Then open the
User model and add
has_role to it:
# app/models/user.rb class User < ActiveRecord::Base has_role end
Checking and asserting permissions
In your views and controllers you can now call
may_read_post! on instances of the user model. The soft
may_read_post? simply returns
false while the less forgiving
may_read_post! throws an exception when the user is missing the required permission.
In views you will often use the soft check to decide whether to show or hide a GUI element:
# app/views/posts/index.html.erb @posts.each do |post|
current_user is a helper method we’re using to point to the currently signed in user. If you are using Clearance for authentication you already have it.)
You rarely want those soft checks in controllers.
What you want is to simply assert that the user has sufficient permissions at a given
point in your code, and raise an error otherwise:
# app/controllers/posts_controller.rb class PostsController def update @post = Post.find(params[:id]) current_user.may_edit_post! @post # raises an Aegis::PermissionError for unauthorized access # ... end end
Presenting permission errors to the world
We often use an around filter to convert
Aegis::PermissionError to a 403 forbidden status code to be
a good citizen of HTTP and make Webrat see failures in our integration tests:
around_filter :convert_permission_error def convert_permission_error yield rescue Aegis::PermissionError => e render :text => e.message, :status => :forbidden end
The same around filter can be used to show a nicer “access denied” message
to your users.
If you find Aegis useful, have comments or need help with your Ruby on Rails projects,
do not hesitate to drop us a line at
20 thoughts on “Aegis: Role-based Permissions for your Ruby on Rails application”
Good stuff! Wondering if this depends on ActiveRecord or if it can work with other model-managing libraries like DataMapper or some of the more experimental ones?
thanks for your comment – at the moment Aegis indeed depends on AR.
In case you couple Aegis with DataMapper, drop us a line, we’re also interested too see it!
Greetings from Cercedilla
I really like the approach you guys have chosen. I am a beginner with Ruby on Rails, and haven taken a look at all the http://ruby-toolbox.com/categories/rails_authorization.html.
Instead of Clearance, I’m using Authlogic for authentication. Do you know any problems with this paring?
And another noob question that is a bit off topic. Do you know a good way to assign users to a specific role at registration? Thank you!
Thanks for this gem! I’m working on a site which will be mostly accessible to non-registered users as well as registered users. I’m using
current_user.may_edit_page?checks in my views, but I keep having to check
current_user.nil?first so I don’t get a
NoMethoderror. Is there a more efficient way to use permissions such that when nobody is logged in, permissions are set to
:everyone? I’m thinking about using a wrapper method around
current_user, which will return a new user if
@Chris: The best way is to always have a
current_user. When no particular user is signed in, use an unsaved User instance with a role name like “guest” or “anonymous”. Here is a typical
before_filterin our ApplicationControllers:
@Shan: Aegis should work just fine with Clearance or Authlogic.
A simple way to have a default role name for new user’s is to use fnando’s excellent has_defaults plugin and then change your User model like this:
Looks nice. Simple and powerful, exactly what I was looking for.
But it doesn’t seem to be working when I define roles like “role :guest”. I changed the role names to strings (role ‘guest’) instead of the symbols and it helped.
@Yuriy: You should be able to use symbols to refer to roles.
Make sure that the role_name attribute in your user model receives the role name as a string though. ActiveRecord can only store strings since the database has no concept of Ruby symbols.
Has anyone used this with devise? I’ve started looking at devise and I like the idea of a rack based auth solution. It has roles *kindof* in that you can use authentication with any model you like (admin, user etc) but that’s not ideal for users with multiple roles. I’d be interested to see how this stacks up in devise vs, say, clearance
@Brad: Using devise’s multiple-model-authentication to manage access rules would be rather inconvenient. I don’t think it was meant for fine-grained permission management.
I agree, I think one User model with access rules is much better than a model per role. So do you know how this would work with one Devise User model? I’m assuming it’s fine?
@Brad: Just use the “has_role” directive in the same user model where you set up Devise. Like Clearance and Authlogic, Devise provides a “current_user” getter for your controller on which you can invoke aegis methods like “current_user.may_create_orders?”.
Aegis really doesn’t care which authentication plugin you’re using.
i am completly new to ruby on rails and just experimenting with it at the moment to check if i wanna go future with it, so i am trying out some basics every application need kinda so installed me clearance which is working fine and also found your gem which seems to fit my needs fine, other are either too simple or too complex.
But i just dont get this to work i kinda made the stuff as in the sample but i keep getting all the time
undefined local variable or method `has_role’ for #
I guess its something really simple not mentioned in the guide which isnt a problem for a experienced rails user but for newbs like me.
@Enduras make sure you restart your server after installing the gem, I had the same problem and this resolved it.
can you elaborate a bit on the code you gave to Chris about setting up a guest user:
def find_current_user @current_user = User.find_by_id(session[“user_id”]) || User.new(:role_name => “guest”) end
I am using Authlogic but having trouble finding where I should create the temporary guest user
@Dave: When you implement authentication in Rails, it is useful to define a helper method in your ApplicationController that returns the user currently signed in. This method is often called
My suggestion to Chris was to enhance his current_user method, so it returns a new, unsaved guest user in case no user is signed in for this session.
I dig the post. I use the Devise + CanCan combo and wrote about it at the following two links if you or anyone is interested. It’s certainly worth evaluating before making your choice:
We’re using Aegis in our new app and it works like a charm. Very cool. Thanks again, makandra.
But what if the user wants to configure the permissions on each role. With this hard coded into a .rb file… how can it be modified by anyone other than a developer doing a redployment?
@S.: You can grant a user permission under the condition that a piece of Ruby code yields true. You do this by calling `allow` with a block. Inside that block you can look at anything you like, including configuration settings that live in the database and can be configured in the UI.