Aegis - role-based permissions for your user models
Aegis allows you to manage fine-grained, complex permission for user accounts in a central place.
Add the following to your Initializer.run block in your environment.rb:
config.gem 'aegis', :source => 'http://gemcutter.org'
Then do a
sudo rake gems:install
sudo gem sources -a http://gemcutter.org sudo gem install aegis
First, let's define some roles:
# 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 # a registered_user can only edit his own posts end allow :moderator end permission :read_post do |post| allow :everyone deny :guest do post.private? # guests may not read private posts end end end
Now we assign roles to users. For this, the users table needs to have a string column role_name.
# app/models/user.rb class User has_role end
These permissions may be used in views and controllers:
# 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 %> # 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
You might want to specifiy a default role:
class Permissions < Aegis::Permissions default_role 'role_name' end
This role will be returned for objects that has nil as their role_name. This greatly reduces noise in your database (i.e. if you have 100 000 users, you don't have to store 'role_name' for each row, just for your non-default roles). default_role takes the same options as role.
To explicitly make sure that a given row won't have a permission object, set role_name to the empty string (“”).
To equip a (user) model with any permissions, you simply call has_role within the model:
class User < ActiveRecord::Base has_role end
Aegis assumes that the corresponding database table has a string-valued column called role_name. You may override the name with the :name_accessor => :my_role_column option.
The roles and permissions themselves are defined in a class inheriting from Aegis::Permissions. To define roles you create a model permissions.rb and use the role method:
class Permissions < Aegis::Permissions role 'role_name' end
By default, users belonging to this role are not permitted anything. You may override this with :default_permission => :allow, e.g.
role 'admin', :default_permission => :allow
Permissions are specified with the permission method and allow and deny
permission :do_something do allow :role_a, :role_b deny :role_c end
Your user model just received two methods called User#may_do_something? and User#may_do_something!. The first one with the ? returns true for users with role_a and role_b, and false for users with role_c. The second one with the ! raises an Aegis::PermissionError for role_c.
Aegis will perform some normalization. For example, the permissions edit_something and update_something will be the same, each granting both may_edit_something? and may_update_something?. The following normalizations are active:
edit = update
show = list = view = read
delete = remove = destroy
Complex permissions (with parameters)
allow and deny can also take a block that may return true or false indicating if this really applies. So
permission :pull_april_fools_prank do allow :everyone do Date.today.month == 4 and Date.today.day == 1 end end
will generate a may_pull_april_fools_prank? method that only returns true on April 1.
This becomes more useful if you pass parameters to a may_...? method, which are passed through to the permission block (together with the user object). This way you can define more complex permissions like
permission :edit_post do |current_user, post| allow :registered_user do post.owner == current_user end allow :admin end
which will permit admins and post owners to edit posts.
For your convenience
As a convenience, if you create a permission ending in a plural 's', this automatically includes the singular form. That is, after
permission :read_posts do allow :everyone end
.may_read_post? @post will return true, as well.
If you want to grant create_something, read_something, update_something and destroy_something permissions all at once, just use
permission :crud_something do allow :admin end
If several permission blocks (or several allow and denies) apply to a certain role, the later one always wins. That is
permission :do_something do deny :everyone allow :admin end
will work as expected.
Our stance on multiple roles per user
We believe that you should only distinguish roles that have different ways of resolving their permissions. A typical set of roles would be
anonymous guest (has access to nothing with some exceptions)
signed up user (has access to some things depending on its attributes and associations)
administrator (has access to everything)
We don't do multiple, parametrized roles like “leader for project #2” and “author of post #7”. That would be reinventing associations. Just use a single :user role and let your permission block query regular associations and attributes.
Henning Koch, Tobias Kraze