ActiveRbac is a Ruby On Rails plugin that supports you in implementing an Role Based Access Control (RBAC) system for your application.
It does so by defining a standard schema (FIXME: link) and providing you with mixins to
You might see multiple different spellings for ActiveRbac. The author of the plugin likes the following:
The author formerly also referenced it as ActiveRBAC since RBAC is the abbreviation for Role Based Access Control but is not using it any more.
We will define two schemas for objects supporting an Role Based Access Control (RBAC) system:
Users and roles are required in any RBAC schema: In an RBAC system, users represent humans or computers which can access objects. Permissions to access objects are granted to roles which are assigned to users.
The following image shows the methods, your user and role objects must provide. In classic Java speech: Your user and role classes have to implement the interfaces to support RBAC schema level 1.

Note that this behaviour only describes methods that are used ot read attributes. This means you can store your users and roles in any way and extend them however you want as long as you provide the methods in the interface.
Your role class must provide the the method identifier which returns a string identifying this role. The string should match the regular expression /^[a-z.]+$/ and look like “roles.admin”, “roles.user” or “roles.anonymous”.
Your user class must provide the method has_role?, roles.
If you have several roles and do not want to hardcode the role names in your permission checks as follows then the StaticPermission interface might come in handy.
if current_user.has_role?('roles.admin', 'roles.user', 'roles.foo') then
# ...
else
raise "You are not allowed to do this!"
end
RBAC schema level 2 extends level 1 by a StaticPermission class and adds some methods to the User and Role class. Basically, StaticPermissions are strings like “permissions.reboot_machine” that can be granted to roles.
A user is granted the role through his roles he has been assigned.
The schema is shown in the image below.

The Role interface is extended by the following methods:
The User interface is extended by the following methods:
Your static permission class must provide the the method identifier which returns a string identifying this permission. The string should match the regular expression /^[a-z.]+$/ and look like “permission.create_article”, “permissions.stop_server” or “permission.foo”.
The code below shows a simple sample implementation of static user, role and static permission classes for RBAC schema level 2.
class User
attr_reader :roles
alias_method :all_roles, :roles
attr_reader :login
def initialize(login, roles)
@login = login
@roles = role.dup
end
end
class Role
attr_reader :identifier
attr_reader :permissions
def initialize(identifier, permissions)
@identifier = identifier.dup
@permissions = permissions.dup
end
def has_static_permission?(identifier)
@permissions.any? { |perm| perm.identifier == identifier }
end
end
class StaticPermission
attr_reader :identifier
def initialize(identifier)
@identifier = identifier.dup
end
end
Now, we could do something like this:
>> permissions = Array.new
>> permissions << StaticPermission.new("permissions.create_article")
>> permissions << StaticPermission.new("permissions.shutdown_server")
>>
>> role = Role.new("roles.admin", permission)
>>
>> user = User.new("root", [ role ])
>>
>> user.has_role?("roles.admin")
=> true
>> user.has_role?("roles.anonymous")
=> false
>> user.has_static_permission?("permissions.create_article")
=> true
>> user.has_static_permission?("permissions.rm -rf /")
=> false
acts_as_current_user_container is a mixin for ActionController::Base classes.
Note that you have to call current_user= (the setter) with self.current_user=. Otherwise, the local variable current_user will be set. We we will write self.current_user below to stress this. Note that with self.current_user we mean the object method not a class method!
All Rails applications featuring access control must store the currently logged in user in the session. Sessions are only available from your controllers so you will either have a lot of session[:current_user_id] calls in your code or write a method to access the current user via this method.
This method is very simple and thus it can perfectly be refactored into a mixin for your controllers. We have done just that and wrote acts_as_current_user_container.
Let have a quick look on how it works before diving into detail (you could also call this section “for the impatient”). If you want to access the current user in multiple locations then you will want to put the call to acts_as_current_user_container into your ApplicationController:
class ApplicationController < ActionController::Base
acts_as_current_user_container
end
Afterwards, you can get access to the current user in your controllers by the methods current_user and self.current_user. For example:
class SecretController < ActionController::Base
before_filter :protect_me
protected
def protect_me
if current_user.nil? then
session[:errors] = 'Keep out!'
redirect_to login_url()
return false
end
true
end
end
As you would guess by the example above, the current_user returns nil if no user has previously been set. This makes for some checks whether the result is nil or is not nil. You can circumvent this problem by passing in a symbol specifying a class (or the class itself):
class AnonymousUser
acts_as_anonymous_user
end
class ApplicationController < ActionController::Base
acts_as_current_user_container :anonymous_user => :anonymous_user
end
Then, current_user creates an instance of AnonymousUser and returns it if no user has been set explicitely. See the documentation on acts_as_anonymous_user for more information about this mixin.
The acts_as_current_user_container mixin works with any ActionController::Base class (in fact the only dependency is the existence of a session method returning a Hash).
You must not use the session variable :aacuc_data, i.e. session[:aacuc_data] should not be accessed from your code.
When you use the mixin in your ActionController::Base class then the two methods current_user and self.current_user are added to this controller class. You can set an object that represents the current user by using the second helper method. The first one returns the object earlier set.
If you call current_user without calling self.current_user before in the current session then the result depends on the parameters you passed to the acts_as_current_user_container method. At the moment, this method accepts but one parameter - :anonymous_user. The following are valid values for :anonymous_user
Examples:
class ApplicationController < ActionController::Base
acts_as_current_user_container :anonymous_user => nil
end
class ApplicationController < ActionController::Base
acts_as_current_user_container :anonymous_user => :anonymous_user
# Results in AnonymousUser.new to be returned by current_user
# unless something has explicitely set within this session.
end
class ApplicationController < ActionController::Base
acts_as_current_user_container :anonymous_user => AnonymousUser
# Results in AnonymousUser.new to be returned by current_user
# unless something has explicitely set within this session.
end
If the value of current_user is an ActiveRecord::Base object then the id and the class will be stored in the session. If the value of current_user implements the informal PersistableInSession interface (see below) then the methods of the interface will be used to store the class and the data necessary for unpersisting in the session.
In all other cases only the class will be stored in the session and {previous current_user}.class.new will be returned on the next call to current_user. No state is preserved in this case.
If an object that is to be written into the session data via current_user is not an ActiveRecord::Base object and needs to preserve its state over requests in the session then the object and its class must provide the following methods. We refer to them as the “informal PersistableInSession interface”. In a language like Java they would be collected in an interface but since Ruby does not have (and only arguably needs) interfaces it is only informal:
The information to be stored in the session should be as few as possible: For example, a numeric user id, a login name or a pair of the login name and the Windows Domain.
If your user data is comes from an LDAP server then your User class could look like the following:
File app/model/user.rb:
class User
attr_accessor :login
def persist
{ :login => self.login }
end
def self.unpersist(hash)
user = User.new
user.login = hash[:login]
return user
end
end
You should be careful only to persist the absolutely minimal information that is required to identify a user into the session. The name of the class of the object to unpersists and the object id for ActiveRecord objects is a good example of this.
If you store a password hash then this information will be written into the session file - if you use file based sessons - in a temporary directory.
Storing information like permissions or information about related ActiveRecord::Base objects could lead to incoherent data: The user object you store in the session has permissions and roles that have been revoked from the given user from the database.
Other problem scenarious could be inexplicable crashes because your user object tries to reference no longer existing articles.
Note that calling @controller.current_user will not work in your tests before you called either post or get. You can set the current user in @controller in tests by using the Test Helpers described below.
ActiveRbac comes with a helper for your controller tests. You can use it by including active_rbac/test_helper in your tests.
The helper adds the method set_current_user to Test::Unit::Test. This method works exactly as the current_user= method in your acts_as_current_user controllers.
File app/controllers/application.rb:
class ApplicationController < ActionController::Base
acts_as_current_user_container :anonymous_user => AnonymousUser
end
File app/controller/test_controller.rb:
class TestController < ApplicationController
def action_that_requires_login
render :text => current_user.inspect
end
end
File app/test/functional/test_helper.rb:
# ...
require 'active_rbac/test_helper'
# ...
File app/test/functional/test_controller_test.rb:
# ...
def test_action_that_requires_login
set_current_user(users(:first))
get :action_that_requires_login
end
# ...
act_as_encrypts_password is a ActiveRecord::Base mixin that allows automatic encryption of passwords.
When implementing a user model then a mandatory requirement is to encrypt the user’s password. This functionality is orthogonal to all other parts of the application: There is no interdependency between the encryption functionality of your user model and any other part of your system.
ActiveRBAC provides the mixin act_as_encrypts_password for ActiveRecord::Base. This mixin implements automatic and transparent password encryption: Whenever you set the password and store the record, the generated methods salt the password and encrypt the salted password with a cryptographic hash function. The salt is randomly generated to provide protection against Rainbow Table attacks.
Let’s have a really quick glance over how to use act_as_encrypts_password. Create a new database table users and a User model and drop in the mixin:
class User < ActiveRecord::Base
acts_as_encrypts_password
end
And you have password encryption on the fly.
The encrypted password mixin can only be used with ActiveRecord::Base classes. The database table of the ActiveRecord must have the following columns:
Note that in a later release you will be able to replace “password” by any other string like “passwd” by passing a parameter to act_as_encrypts_password.
When you mark an ActiveRecord::Base class with this mixin then nifty meta programming Ruby magic will modify the class in class in the following way:
Additionally, the following validations will be added to the ActiveRecord:
File db/migrations/001create_users.rb:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
# a user needs a login, right?
t.column :login :string, :limit => 20,
:null => false
# columns for acts_as_encrypts_password
t.column :password, :string, :limit => 128, # sha-512 ready
:null => false
t.column :password_salt, :string, :limit => 100,
:null => false
t.column :password_hash_type, :string, :limit => 10,
:null => false
end
# ...
end
File app/model/user.rb:
class User < ActiveRecord::Base
acts_as_encrypts_password
validates_presence_of :login
end
Now we could use this User class in the following way:
$> cd rbac_using_project
$> ./script/console
Loading development environment.
>> user = User.new
=> <#User...
>> user.password_new?
=> true
>> user.login = 'me'
=> "me"
>> user.password = 'password'
=> "password"
>> user.password_confirmation = 'password'
=> "password"
>> user.save!
=> true
>> user.password_new?
=> false
>> user.password_salt
=> "rzTK3WRlPK"
>> user.password
=> "b5f8673fb23319c365e5f0968c55bc17db263e55"
>> require 'digest/sha1'
=> true
>> Digest::SHA1.hexdigst('password' + 'rzTK3WRlPK')
=> "b5f8673fb23319c365e5f0968c55bc17db263e55"
>> user.password_equals?('password')
=> true
>> user.password_equals?('passwd')
=> false
The ActiveRecord::Base mixins acts_as_user, acts_as_role and acts_as_static_permission allow for automatically registering ActiveRecord classes to adhere to the ActiveRecord
If you want to use a fairly simple Role Based Access Control (RBAC) system with Rails then you will implement Users, Roles and most propably something equivalent to StaticPermission - the ActiveRbac schema described in FIXME.
ActiveRbac ships with the mixins acts_as_user, acts_as_role and acts_as_static_permission which allow you to create the models for a RBAC quickly: You create the database schema and the model classes. Then, you use the mixins to mark the classes to work as ActiveRbac model classes and the relations between the classes are automatically added.
We create the model classes User, Role and StaticPermission and improve them using our acts_as mixins:
class User < ActiveRecord::Base
acts_as_user
end
class Role < ActiveRecord::Base
acts_as_role
end
class StaticPermission < ActiveRecord::Base
acts_as_static_permission
end
Now, we can do the following:
>>> Role.new({ :identifier => 'roles.admin' }).save!
>>> StaticPermission.new({ :identifier => 'permissions.all' }).save!
>>> user = User.new
>>> user.save!
>>> user.roles << Role.find_by_identifier('roles.admin')
>>> user.has_role?('roles.admin')
=> true
>>> user.has_permission?('permissions.all', 'foo')
=> true
>>> user.has_permission?('foo')
=> false
You can either implement ActiveRbac Schema level 1 or level 2 ith the acts_as model macros provided by ActiveRBAC. For level 1, you have to provide the database tables users and roles and the join table roles_users. For level 2, you also have to provide the database table static_permissions and the join table roles_static_permissions.
The roles and the static_permission tables both have to provide the string column identifier.
In Ruby code, your migrations must create at least the columns the following migration creates (of course you do not have to create the tables for static_permissions etc. if you do not want to implement level 2).
class AddUsers < ActiveRecord::Migration
def self.up
say "Creating table 'users' and indexes..."
ActiveRecord::Base.transaction do
say "Create table users.", true
create_table(:users) do |t|
end
say "Add indexes.", true
add_index :users, :email
say "Create table roles.", true
create_table(:roles) do |t|
t.column :identifier, :string, :limit => 100, :null => false
end
say "Add indexes on table 'roles'.", true
add_index :roles, :identifier
say "Add relation table roles <---> users", true
create_table(:roles_users, :id => false) do |t|
t.column :role_id, :integer, :null => false
t.column :user_id, :integer, :null => false
end
say "Add indexes on 'roles_users'", true
add_index :roles_users, [ :role_id, :user_id ], :unique => true
end
say "Create table 'static_permissions'.", true
create_table(:static_permissions) do |t|
t.column :identifier, :string, :limit => 100, :null => false
end
say "Add indexes on table 'static_permissions'.", true
add_index :static_permissions, :identifier
say "Add relation table roles <---> static_permissions", true
create_table(:roles_static_permissions, :id => false) do |t|
t.column :role_id, :integer, :null => false
t.column :static_permission_id, :integer, :null => false
end
say "Add indexes on 'roles_static_permissions'", true
end
end
Another requirement is that you require all the RBAC model files in your app/controllers/application.rb (see below: Gotchas).
At the moment, none of the acts_as_{user, role, static\permission} methods accepts any parameters. All the necessary information is taken from the classes you call the mixin method in via introspection.
Again, the required table columns:
As soon as the classes necessary for level 1 or 2 are registered, ActiveRbac will add the following to the user.
The method _is_anonymous? gets added to the user class and always returns false.
The relation has_and_belongs_to :roles gets added to the user class. The :class_name attribute of the macro will automatically be selected by the class that acts as the role class. The :uniq option is set to true.
The method has_role?(*identifiers) gets added to the user class. You can pass in one or more strings where each contains the identifier of a role. The method returns true iff one of the roles is assigned to the user object.
The method static_permissions gets added to the user class (level 2 only). The method returns all permissions received through the roles assigned to the user.
The method has_static_permission?(*identifiers) gets added to the user class. You can pass in one or more strings where each contains the identifier of a static permission. The method returns true iff one of the static permissions is granted to the user object through one of its role.
The role class will be augmented as follows.
The relation has_and_belongs_to :static_permission gets added to the role class. The :class_name attribute of the macro will automatically be selected by the class that acts as the static_permission class. The :uniq option is set to true.
The method has_static_permission?(*identifiers) gets added to the role class. You can pass in one or more strings where each contains the identifier of a static permission. The method returns true iff one of the static permissions is granted to the role object.
Validations for the identifier to be present and unique are added to the role class.
The static permission class is augmented as follows.
Validations for the identifier to be present and unique are added to the static permission class.
Make sure that you require all of your model files in app/controllers/application.rhtml with require_dependency. The order is not important.
require_dependency 'user'
require_dependency 'role'
require_dependency 'static_permission'
class ApplicationController < ActionController::Base
end
When you mark a controller with acts_as_current_user_container then you can access the current user object using current_user in your views.