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 'thelinuxlich-aegis', :lib => 'aegis'
Then do a
sudo rake gems:install
sudo gem install thelinuxlich-aegis
Changes in this fork
WARNING! Opinionated stuff! Now you can set the permission prefix, admin and crud verbs with your locale(default is 'may','admin','read','write','update' and 'destroy', respectively) Example: in locale/en.yml:
aegis: permission: 'should' admin: 'manage' read: 'access' write: 'insert' update: 'update' destroy: 'delete'
And then you can verify authorization with current_user.should_access_posts?
Also, there is a class method you can put on ApplicationController(or anything that extends ActionController::Base) to automatically add before_filter to all REST actions verifying authorization:
authorize_first!(:current_user, options) First parameter is a string containing the method to access the current user on your favorite authentication gem, second parameter accepts :except => [ARRAY_OF_CONTROLLER_NAMES]
The user class now can call has_role :special_permissions => true, and it will add a has_many association with special_permissions to really customize what every role can access.
In Permissions class, you can add restful_permissions!(:except => [ARRAY_OF_CONTROLLER_NAMES]) and it will add the 4 crud verbs to verify permission with every role, first verifying if the current user has admin access then verifying in SpecialPermission association. Example: Imagine we have a controller called Posts. It will add the permission methods may_read_posts?, may_write_posts?, may_update_posts?, may_destroy_posts?, all customizable with locales.
For special permissions, you'll also need a table for it. Create a migration like this:
class CreateSpecialPermissions < ActiveRecord::Migration def self.up create_table :special_permissions do |t| t.integer :user_id t.string :permission_module t.boolean :permission_read, :default => false t.boolean :permission_write, :default => false t.boolean :permission_destroy, :default => false t.boolean :permission_update, :default => false t.timestamps end end def self.down drop_table :special_permissions end end
Until I fix the generator, the migration part is manual, unfortunately :(
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
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.
You can define a default role for a model by saying
class User < ActiveRecord::Base has_role :default => :admin end
All this will do, is initialize the role_name with the given default when User.new is called.
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