Aegis - role-based permissions for your user models¶ ↑
Aegis allows you to manage fine-grained, complex permission for user accounts in a central place.
Installation¶ ↑
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
Alternatively, use
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 :(
Example ¶ ↑
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
Details¶ ↑
Roles¶ ↑
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¶ ↑
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
.
Normalization¶ ↑
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.
Credits¶ ↑
Henning Koch, Tobias Kraze