AccessChecker

AccessChecker is a simple replacement for ACL9, a role-based authentication gem. AccessChecker is primarily oriented for functioning as a before_action role authentication scheme for Rails controllers.

Basic concepts

Authentication is often called for on a controller-by-controller basis, restricting actions to users who possess certain roles. AccessChecker (current version) assumes only one role per user. AccessChecker requires a current_user method accessible at the controller level and which returns a User object.

AccessChecker adds, to the User model, role accessor methods: has_role?, has_role!, get_role

Structure

  • necessary models: user, roles, roles_users (join table)
  • necessary migrations (for access_checker): roles, roles_users (join table)

Defaults assumed

User is the subject model: indicate by inserting the following macro after the class definition:

    acts_as_authorization_subject

Role is the role model: indicate by inserting the following macro after the class definition:

   acts_as_authorization_role

Note: the gem allows the default model/table names to be changed, but I haven't built tests for verifying that the changes will work.

   class Role < ActiveRecord::Base
     acts_as_authorization_role
   end

   class User < ActiveRecord::Base
     acts_as_authorization_subject
   end

Migrations required

The roles table must be created (sorry, no generator yet) Some of the fields are legacy from acl9 and currently are not used.

  create_table "roles", :force => true do |t|
    t.string   "name",              :limit => 40
    t.string   "authorizable_type", :limit => 40
    t.string  "authorizable_id"
    t.boolean "system", :default=>false
    t.datetime "created_at"
    t.datetime "updated_at"
  end

and the join table

    create_table :roles_users, :id => false, :force => true do |t|
      t.column  :user_id, :integer
      t.column  :role_id, :integer
    end
      add_index :roles_users, :user_id

Usage

At the head of every controller for which you wish to control user access, you'll need to place an access_control macro which is used to generate a before_action for the authorization checks. The macro takes a hash of role specification parameters. These must be specified prior to the macro and then referenced as the macro's parameter parameter. I cannot be specified in-line because Rails is treating it as a before_action and so expects a before_action type of ( :only =>, :except => ) hash which would then apply to the before_action itself and thus bypass any explicit checking.

Unauthorized access yields an exception: AccessChecker::AccessDenied . Syntax errors in formulating the control parameters will also raise an exception: AccessChecker::SyntaxError . You'll probably want to catch those exceptions and handle them gracefully, probably in your ApplicationController.

Notice that roles, limit_types, and controller actions are all expected to be symbols.

You have complete freedom to define roles to be whatever you want: except for the reserved words: :all, :anonymous. The usage of :all, :anonymous is explained with the following logic.

  • if current_user.nil?, then proceed as :anonymous if :anonymous is referenced in the role_control_hash
  • if user's role is not referenced, then proceed as :all if :all is referenced in the role_control_hash
  • else proceed and evaluate the role_control_hash with user's role

This means that :all will only be invoked if a user has a role which is NOT specified in the role_control_hash and if :all is specified in the hash. In that sense :unspecified would be more accurate than :all, but :all is shorter and handier to work with. And it means that :anonymous will only be invoked if current_user.nil? is true and if :anonymous is specified in the hash.

The access limitation types are:

  • :allow, :to, :only -- control what access is allowed; all else will be denied
  • :deny, :except -- control what is denied; all else will be permitted

The action list can be empty; in which case, for :allow, all actions are permitted; and for :deny, no actions are permitted. If both limit_type and action_list are missing, then :allow => [] will be assumed.

class AnyController < ApplicationController

   control_parameters = {
       :admin   => { :allow => [] },
       :manager => { :deny  => [ :delete, :edit ] },
       :member  => { :allow => [ :index, :show  ] },
       :anonymous => { :allow => [:index ] },
       :all       => { :deny  => [:edit, :update] }
   }

   access_control control_parameters

Catch exceptions:

class ApplicationController < ActionController::Base
   rescue_from AccessChecker::AccessDenied do |e|
     # do something here
   end

Acknowledgements

AccessChecker was influenced by the acl9 gem (https://github.com/be9/acl9).