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:
- user model
- none
- role model
- identifier - string
- permission model
- identifier - string
- role to user join table
- user id
- role id
- role to static permission join table
- role id
- static permission id
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
- remove the require_dependency gotcha
- allow custom table names and custom join tables (simply reflect on the models)
- record specific permissions - maybe through a RoleInContext model?