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 < Aegis::Permissions role :guest role :registered_user role :moderator role :administrator, :default_permission => :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| <% if current_user.may_read_post? post %> <%= render post %> <% if current_user.may_edit_post? post %> <%= link_to 'Edit', edit_post_path(post) %> <% end %> <% end %> <% end %>
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