| Path: | README |
| Last Update: | Mon Aug 18 16:38:10 +0200 2008 |
The declarative authorization plugin offers an authorization mechanism inspired by RBAC. The most notable distinction to existing authorization plugins is the declarative authorization approach. That is, authorization rules are not programmatically in between business logic but in an authorization configuration.
Currently, Rails authorization plugins only provide for programmatic authorization rules. That is, the developer needs to specify which roles are allowed to access a specific controller action or a part of a view, which is not DRY. With a growing application code base and functions, as it happens especially in agile development processes, it may be decided to introduce new roles. Then, at several places of the source code the new group needs to be added, possibly leading to omissions and thus hard to test errors. Another aspect are changing authorization requirements in development or even after taking the application into production. Then, privileges of certain roles need to be easily adjusted when the original assumptions concerning access control prove unrealistic. In these situations, a declarative approach as offered by this plugin increases the development and maintenance efficiency.
Plugin features
Requirements
See below for installation instructions.
----- App domain ----|-------- Authorization conf ---------|------- App domain ------
includes includes
.--. .---.
| v | v
.------. can_play .------. has_permission .------------. requires .----------.
| User |----------->| Role |----------------->| Permission |<-----------| Activity |
'------' * * '------' * * '------------' 1 * '----------'
|
.-------+------.
1 / | 1 \ *
.-----------. .---------. .-----------.
| Privilege | | Context | | Attribute |
'-----------' '---------' '-----------'
In the application domain, each User may be assigned to Roles that should define the users’ job in the application, such as Administrator. On the right-hand side of this diagram, application developers specify which Permissions are necessary for users to perform activities, such as calling a controller action, viewing parts of a View or acting on records in the database. Note that Permissions consist of an Privilege that is to be performed, such as read, and a Context in that the Operation takes place, such as companies.
In the authorization configuration, Permissions are assigned to Roles and Role and Permission hierarchies are defined. Attributes may be employed to allow authorization according to dynamic information about the context and the current user, e.g. "only allow access on employees that belong to the current user‘s branch."
If authentication is in place, enabling user-specific access control may be as simple as one call to filter_access_to :all which simply requires the according privileges for present actions. E.g. the privilege index_users is required for action index. This works as a first default configuration for RESTful controllers, with these privileges easily handled in the authorization configuration, which will be described below.
class EmployeeController < ApplicationController
filter_access_to :all
def index
...
end
...
end
When custom actions are added to such a controller, it helps to define more clearly which privileges are the respective requirements. That is when the filter_access_to call may become more verbose:
class EmployeeController < ApplicationController
filter_access_to :all
# this one would be included in :all, but :read seems to be
# a more suitable privilege than :auto_complete_for_user_name
filter_access_to :auto_complete_for_employee_name, :require => :read
def auto_complete_for_employee_name
...
end
...
end
For some actions it might be necessary to check certain attributes of the object the action is to be acting on. Then, the object needs to be loaded before the action‘s access control is evaluated. On the other hand, some actions might prefer the authorization to ignore specific attribute checks as the object is unknown at checking time, so attribute checks and thus automatic loading of objects needs to be enabled explicitly.
class EmployeeController < ApplicationController
...
filter_access_to :update, :attribute_check => true
def update
# @employee is already loaded from param[:id]
@employee ||= Employee.find(param[:id])
...
end
...
end
If the access is denied, a permission_denied method is called on the current_controller, if defined and the issue is logged. For further customization of the filters and object loading, have a look at the complete API documentation of filter_access_to in Authorization::AuthorizationInController::ClassMethods.
In views, a simple permitted_to? helper makes showing blocks according to the current user‘s privileges easy:
<% permitted_to?(:create, :employees) do %>
<%= link_to 'New', new_employee_path %>
<% end %>
...
<% for employee in @employees %>
...
<%= link_to 'Edit', edit_employee_path(company) if permitted_to?(:update, employee) %>
<% end %>
See also Authorization::AuthorizationHelper.
There are two destinct features for model security built into this plugin: authorizing CRUD operations on objects as well as query rewriting to limit results according to certain privileges.
See also Authorization::AuthorizationInModel.
To activate model security, all it takes is an explicit enabling for each model that model security should be enforced on, i.e.
class Employee < ActiveRecord::Base
using_access_control
...
end
Thus,
Employee.create(...)
fails, if the current user is not allowed to :create :employees according to the authorization rules. For the application to find out about what happened if an operation is denied, the filters throw Authorization::NotAuthorized exceptions.
As access control on read are costly, with possibly lots of objects being loaded at a time in one query, checks on read need to be actived explicitly by adding the :include_read option.
When retrieving large sets of records from databases, any authorization needs to be integrated into the query in order to prevent inefficient filtering afterwards and to use LIMIT and OFFSET in SQL statements. To keep authorization rules out of the source code, this plugin offers query rewriting mechanisms through named scopes. Thus,
Employee.with_permissions_to(:read)
returns all employee records that the current user is authorized to read. In addition, just like normal named scopes, query rewriting may be chained with the usual find method:
Employee.with_permissions_to(:read).find(:all, :conditions => ...)
If the current user is completely missing the permissions, an Authorization::NotAuthorized exception is raised. Through Model.obligation_conditions, application developers may retrieve the conditions for manual rewrites.
Authorization rules are defined in config/authorization_rules.rb. E.g.
authorization do
role :admin do
has_permission_on :employees, :to => [:create, :read, :update, :delete]
end
end
There is a default role :guest that is used if a request is not associated with any user or with a user without any roles. So, if your application has public pages, :guest can be used to allow access for users that are not logged in. All other roles are application defined and need to be associated with users by the application.
Privileges, such as :create, may be put into hierarchies to simplify maintenance. So the example above has the same meaning as
authorization do
role :admin do
has_permission_on :employees, :to => :manage
end
end
privileges do
privilege :manage do
includes :create, :read, :update, :delete
end
end
Privilege hierarchies may be context-specific, e.g. applicable to :employees.
privileges do
privilege :manage, :employees, :includes => :increase_salary
end
For more complex use cases, authorizations need to be based on attributes. E.g. if a branch admin should manage only employees of his branch:
authorization do
role :branch_admin do
has_permission.on :employees do
to :manage
# user refers to the current_user when evaluating
if_attribute :branch => is {user.branch}
end
end
end
Lastly, not only privileges may be organized in a hierarchy but roles as well. Here, project manager inherit the permissions of employees.
role :project_manager do
includes :employee
end
See also Authorization::Reader.
To install simply execute in your applications root directory
cd vendor/plugins && git clone git://github.com/stffn/declarative_authorization.git
Then,
The requirements are
Of the various ways to provide these requirements, here is one way employing restful_authentication:
cd vendor/plugins && git clone git://github.com/technoweenie/restful-authentication.git restful_authentication ruby script/generate authenticated user sessions
before_filter :set_current_user protected def set_current_user Authorization.current_user = current_user end
def roles
(super || []).map {|r| r.title.to_sym}
end
Currently, the main means of debugging authorization decisions is logging and exceptions. Denied access to actions is logged to warn or info, including some hints about what went wrong.
All bang methods throw exceptions which may be used to retrieve more information about a denied access than a Boolean value.
Steffen Bartsch TZI, Universität Bremen, Germany sbartsch at tzi.org
Copyright (c) 2008 Steffen Bartsch, TZI, Universität Bremen, Germany released under the MIT license