5 RBAC Schema Mixins

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

5.1 Motivation

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.

5.2 First Glance

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

5.3 Requirements

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

5.4 Reference

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.

5.4.1 Required Columns

Again, the required table columns:

5.4.2 Metaprogramming Done By ActiveRbac

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.

5.5 Gotchas

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

5.6 TODO