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_user and 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 named name and specify how to test the membership of a subject to this role with the block procedure. A role can be relative also to an object. eg. owner depends on object.author. NOTE keep in mind that roles are tested also in situations where subject and object are nil.

    Options
    • :require : Specify other memberships that are required to belong to this role
    • :alias or :aliases : Specify aliases that can be used in permissions to refer this role. eg connected for logged_in
    • :method : Specify the method that will be defined in the decorator to check the membership to this role. eg own? for owner

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 :for option is specified the block is applied to any resource.
  • allow(*roles, options = {})
    Allow a subject that has a mebership to roles to perform an action specified by the :to option. If no roles are specified this rule any role is allowed. Similarly if no :to option 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 to roles to perform an action specified by the :to option. If no roles are specified this rule no role is allowed. Similarly if no :to option 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 the checkin method
    • :find_object : Specify a way to fetch the resource object passing a String or Symbol corresponding to a method name or a Proc that returns the resoruce. Default to Proc.new { model_class.find(params[:id]) }
    • :skip_authorization : Specify to skip the authorization process
    • :rescue_with : Specify a procedure that is invoked to rescue from Checkin::AccessDenied exceptions

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.