Checkin
Checkin is an authorization gem for Ruby on Rails.
Features
- Handy DSL to define roles and permissions with a declarative approach
- Check authorization for CRUD operations automatically
- Standard way to rescue from Authorization errors
- Authorization subject decoupled from model (compatible with any autentication system)
- Role-based authorization decoupled from role system (compatible with any role system)
- Decorator for
current_userand other subject objects - Scoped authorization rules
- Cascading authorization rules
- Simple: even complex authorization behaviour is understandable at glimpse and easily predictable
- Support for controller based mass assignment protection (Coming Soon – already implemented but still not tested and not documented)
Installation
Put this in your Gemfile
gem 'checkin'
and update your bundle
bundle install
Generate a *Subject class
rails g checkin:subject SUBJECTNAME
eg.
rails g checkin:subject user
Usage
Define a subject class
Create a subject class in your load path (you can use the checkin:subject generator as described above). A subject class is a decorator
around the role and authentication system in which you can define roles and permissions for a given subject with a simple DSL.
ex.
class UserSubject < Checkin::Subject
role :guest, :alias => :anonymous do
!subject_model
end
role :logged_in, :alias => [:connected] do
!!subject_model
end
role :author, :require => :logged_in do
subject_model.has_role?(:author)
end
role :owner, :require => [:author], :method => :own do |object|
object && object.respond_to?(:author) && ( subject_model == object.author )
end
role :administrator, :require => :logged_in, :alias => :admin do
subject_model.has_role?(:administrator)
end
#
# Permissions
#
scope :admin do
permissions do
allow :administrators
allow :owners, :to => [:edit, :update]
deny
end
end
end
Now you can instantiate a UserSubject to decorate any subject model. NOTE: you won't need to do this directly since the checkin method
will do this for you (see the section below).
subject = UserSubject.new(User.first, :scope => :admin)
Checking role membership
According to the definitions included in the subject class the subject decorator will provide some methods to check role memberships.
ex.
subject.logged_in?
subject.guest?
subject.own?(Post.first)
Checking permissions
similarly the decorator allows you to check the permissions of the subject
subject.can_edit?(Post.first)
Subject DSL reference
Defining roles
A role can be defined through the method role.
role(name, options = {}, &block)
Define a role namednameand specify how to test the membership of a subject to this role with theblockprocedure. A role can be relative also to an object. eg.ownerdepends onobject.author. NOTE keep in mind that roles are tested also in situations wheresubjectandobjectarenil.
Options:require: Specify other memberships that are required to belong to this role:aliasor:aliases: Specify aliases that can be used in permissions to refer this role. egconnectedforlogged_in:method: Specify the method that will be defined in the decorator to check the membership to this role. egown?forowner
Defining permissions
To define a permission you can use the permissions block. Inside the permission block you can call allow to define a rule that if satisfied immediately allow the subject to perform the related action and deny to define a rule that if satisfied immediately deny the subject to perform the related action.
Permissions can be both scoped or global. To define a scope you can use the scope method. You can also define permissions for a specific resource or for a set of resources through passing a :for option to the permissions method.
Permissions blocks are evaluated in the same order they are written skipping them for scopes and resources that are not affected.
eg.
permissions :for => :comments do
allow :administrators, :mantainers
allow :logged_in, :to => [:create]
deny
end
Inside a permissions block allow and deny rules are checked consecutively. The checking process will immediately return if any rule is matched and the subject is allowed or denied according to the kind of the rule. If no rule are matched then the subject is allowed.
References
permissions(options = {}, &block)
Define a permissions block
Options
:for: specify a resource or a set of resources for which these permissions are applied. If no:foroption is specified the block is applied to any resource.
allow(*roles, options = {})
Allow a subject that has a mebership torolesto perform an action specified by the:tooption. If norolesare specified this rule any role is allowed. Similarly if no:tooption is specified any action can be performed.
Options
:to: specify an action or a set of actions that the subject is allowed to perform if belonging to*roles.
deny(*roles, options = {})
Deny a subject that has a mebership torolesto perform an action specified by the:tooption. If norolesare specified this rule no role is allowed. Similarly if no:tooption is specified no action can be performed.
Options:to: specify an action or a set of actions that the subject is allowed to perform if belonging to*roles.
scope(name)
Defines a scope in which the permissions are applied
Integration with the application
The checkin method define a before filter to a controller that is responsible to instantiate a subject decorator to the current subject model, to instantiate the current resource for crud actions that has one, and then to check permissions for the current subject to perform the current action on the current resource in the current scope.
If the check fails a Checkin::AccessDenied exception is raised.
The checkin method defines also some other methods available in controller and views to access the subject decorator and the resource object, by default #subject and #object (see the reference section below).
NOTE The resource object is instantiated and checked only if a params[:id] is present for the current action.
ex.
class ApplicationController < ActionController::Base
rescue_from Checkin::AccessDenied, :with => :rescue_access_denied
checkin(:scope => :admin)
protected
def rescue_access_denied
if subject.guest?
redirect_to new_user_session_path
else
render :text => "Not Authorized", :status => 403
end
end
end
References
checkin(options = {})
Define the checkin before filter in the controller in which is invoked Options:scope: Specify the current scope:subject: Specify the underscored subject class name. Default to :user_subject:as: Specify the method name for the generated accessor to the subject decorator, default to:subject:subject_model: Specify the method that return the current subject model, default to:current_user:object: Specify a method accessible in the controller scope that returns the current resource, if none is specified such method is automatically generated by thecheckinmethod:find_object: Specify a way to fetch the resource object passing aStringorSymbolcorresponding to a method name or aProcthat returns the resoruce. Default toProc.new { model_class.find(params[:id]) }:skip_authorization: Specify to skip the authorization process:rescue_with: Specify a procedure that is invoked to rescue fromCheckin::AccessDeniedexceptions
eg.
checkin(:subject => :user_subject, :scope => nil, :skip_authorization => false, :object => :object, rescue_with => lambda {
if subject.guest?
redirect_to new_user_session_path
else
render :text => "Not Authorized", :status => 403
end
})
Helpers
The subject and object methods (or what they are configured to be named) are also invokable from views. Since the #subject method returns the subject decorator it's safe to call test methods on it even if no subject is logged in. So you can replace current_user.try(:administrator?) with subject.administrator?. Also thanks to the dependencies expressed with the role definition DSL you can easily test complex situation with a single method. eg.
# user_subject.rb
role :owner, :require => :author, :method => :own do |object|
object && object.respond_to?(:author) && ( subject_model == object.author )
end
subject.own?(Post.new) is the same of current_user && current_user.has_role?(:author) && object && object.respond_to?(:author) && current_user == object.author
Permission to change attributes (in-controller mass assignment protection)
NOTE This feature is implemented but not tested yet (please help me to do that if you wish!)
You can define permission to set attributes through the method permissions_to_set
# user_subject
permissions_to_set :published do
allow :editors, :administrators
deny
end
editor.allowed_to_set?(:published, :on => post)
Denied params will be automatically removed from params hash in the checkin before filter
Explaining and logging
The subject decorator has two methods explain! and stop_explaining! to enable/disable the logging of the details of the authorization process. By default explain! is automatically called in the development runtime.
Copyright (c) 2012 mcasimir
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.