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).