V2 - Installing and Using Authorisation Schema
Introduction
This pages includes tips and tricks for installing and using the V2 authorisation schema on Ruby on Rails Development by utilizing Declarative Authorization and Rolify.
Modules
Declarative Authorization
The Declarative Authorization plugin offers an authorization mechanism inspired by RBAC (Role Based Access Control):
Once the rdoc documentation is generated, it is available under:
It is worth to view this good railscast:
Rolify
Very simple Roles library without any authorization enforcement supporting scope on resource object:
Once the rdoc documentation is generated, it is available under:
Installation
Add the gems to the Gemfile:
# Authorisation gem 'declarative_authorization' gem 'rolify'
- Install the gems
bundle install
- Generate rdoc documentation
su gem rdoc --all --no-ri
Create model Role and database table users_roles
rails generate rolify:role rake db:migrate
Implementation
Requirements
Change app/controllers/application_controller.rb to
provide current user in both controllers and models
catch all Permission Denied error
class ApplicationController < ActionController::Base protect_from_forgery before_filter :set_current_user # for model security include SessionsHelper # Force signout to prevent CSRF attacks def handle_unverified_request sign_out super end # declarative_authorization callback on permission violation def permission_denied flash[:error] = "Sorry, you are not allowed to access that page." redirect_to root_url end protected # needed for access control in model for declarative_authorization def set_current_user Authorization.current_user = current_user end end
Change app/models/user.rb to
allow using add_role, remove_role, etc. of Rolify
- provide method User.role_symbols for Declarative Authorization
class User < ActiveRecord::Base rolify # Rolify gem: add_role, remove_role, etc. . has_and_belongs_to_many :roles, :join_table => :users_roles . # -- Roles for authorisation def role_symbols (roles || []).map {|r| r.name.underscore.to_sym} end . ,
Access Control in Controllers
Use filter_resource_access method in every controller, where access control required. No option is needed if no other actions are used as the 7 RESTFul ones. See options:
Filter records for list in similar way:
def index @documents = Document.with_permissions_to(:index).revisions.paginate(:page => params[:page]) end
If the model-controller-database naming follows the standard Rails conventions, the lines reading the model based on params[:id] can be removed, filter_resource_access does it for us:
def show @document = Item.find(params[:id]) # CAN BE REMOVED end
Access Control in Models
Use using_access_control method in every controller, where access control required. Generally no option is needed. See options:
Access Control in Views
Use permitted_to? method in every views, where access control required on instance or model basis. E.g. in app/views/documents/_revision_file_list_block.html.erb:
<% permitted_to? :update, revision do %> <td colspan='4'> <%= link_to( rev_files_new_path( revision.id ), class: 'btn btn-mini btn-info' ) do %> <i class='icon-upload-alt'></i> New <% end %> </td> <% end %>
Use has_role_with_hierarchy? in every views, where access control required on role base. E.g. in app/views/layouts/_main_menu.html.erb:
<% has_role_with_hierarchy?(:admin) do %> <li class='divider-vertical'></li> <li class='dropdown'> <a class='dropdown-toggle' data-toggle='dropdown' href='#'>Folders<b class='caret'></b></a> <ul class='dropdown-menu'> <li><%= link_to 'List', manage_folders_path %></li> <li><%= link_to 'New', new_folder_path %></li> </ul> </li> <% end %>
Users & Roles
We are using the authorization not in the standard way of the couple Declarative Authorization - Rolify.
For the critical objects, as documents, revisions, tasks we are using folder based authorization. Therefore we utilize the resource context of Rolify for the Folder model.
V2 authorization model contains the following roles:
root - has omnipotence, can do everything, also configuration of system critical data
admin - can configure users, can configure reference data
update - can create, update documents, revisions, tasks
read - can list, read documents, revisions, tasks
login - can login into the system
Some hints:
Every role above contains all roles below itself, e.g. update role includes read and login roles.
The roles root and login can be granted only generally, i.e. not connected to a Folder
A user can have only one general role
A user must have one general role to allow to log in into the system. This role can be the login role.
A user can grant/remove roles to/from other users what he has also. This is valid also for roles connected to a Folder, the user can grant the role in that Folder and in its children. This rule should be applied also at invitation.
Example Configuration
The following commands in lib/tasks/sample_data.rake grants
User(1) general root role,
User(2) general admin role,
User(3) document_update role in Folder(3), Folder(6) and in their descendants,
User(3) document_read role in Folder(2), Folder(7) and in their descendants.
def make_permissions User.find(1).add_role :root User.find(2).add_role :admin User.find(3).add_role :login User.find(3).add_role :document_update, Folder.find(3) User.find(3).add_role :document_update, Folder.find(6) User.find(4).add_role :login User.find(4).add_role :document_read, Folder.find(2) User.find(4).add_role :document_read, Folder.find(7) end
The result in the database is self-explanatory:
mysql> select * from roles; +----+-----------------+-------------+---------------+---------------------+---------------------+ | id | name | resource_id | resource_type | created_at | updated_at | +----+-----------------+-------------+---------------+---------------------+---------------------+ | 1 | root | NULL | NULL | 2013-04-04 13:32:41 | 2013-04-04 13:32:41 | | 2 | admin | NULL | NULL | 2013-04-04 13:32:41 | 2013-04-04 13:32:41 | | 3 | login | NULL | NULL | 2013-04-04 13:32:41 | 2013-04-04 13:32:41 | | 4 | document_update | 3 | Folder | 2013-04-04 13:32:41 | 2013-04-04 13:32:41 | | 5 | document_update | 6 | Folder | 2013-04-04 13:32:41 | 2013-04-04 13:32:41 | | 6 | document_read | 2 | Folder | 2013-04-04 13:32:41 | 2013-04-04 13:32:41 | | 7 | document_read | 7 | Folder | 2013-04-04 13:32:41 | 2013-04-04 13:32:41 | +----+-----------------+-------------+---------------+---------------------+---------------------+ 7 rows in set (0.00 sec) mysql> select * from users_roles; +---------+---------+ | user_id | role_id | +---------+---------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | | 3 | 4 | | 3 | 5 | | 4 | 3 | | 4 | 6 | | 4 | 7 | +---------+---------+ 8 rows in set (0.03 sec)
Note that role names are not predefined anywhere. Any roles can be added by add_role.
Authorization Rules
See examples on github:
See reference guide of the DSL in rdoc:
V2P0 example:
authorization do role :root do includes :admin end role :admin do includes :login end role :login do includes :guest has_permission_on [:documents, :revisions] :to => [:manage, :view] do if_attribute :folder_id => is_in { Folder.get_folder_ids_permitted( Authorization.current_user, [:root, :admin, :document_update]) } end has_permission_on [:documents, :revisions] :to => :view do if_attribute :folder_id => is_in { Folder.get_folder_ids_permitted( Authorization.current_user, :document_read) } end end role :guest do end end privileges do privilege :manage do includes :new, :create, :edit, :update, :destroy end privilege :view do includes :index, :show end end
Note the role hierarchy ( root->admin->login->guest ), the privilege hierarchy(manage->new,create,edit,update,destroy, etc.). Note also that everywhere where a role or a privilege is use in the configuration, also an array can be used.
Tests and System Preloading
It can be necessary to bypass the authorization in test scripts and system preloading. It can be done with the method without_access_control. E.g. in lib/tasks/sample_data.rake used by seed_fu:
require 'declarative_authorization/maintenance' # needed for without_access_control include Authorization::TestHelper . . without_access_control do # bypass declarative_authorization's model access control make_documents_and_revisions end . .
Open points
The V2 system allows currently one root folder, so the general roles mean the same access rights as the roles connected to the root folder.
- Should we allow to use more than one root folder?
Should we remove the general roles?
Leave the system as it is? (To allow more than one root folder later needs only the removing one validation in the Folder model.)
- At the invitation the inviting user could grant his own roles to the invitee.
The inviting user should have to possibility to select lower roles as he has, e.g. if he has general admin role, he should be able to grant update role in a specific folder.