Plugins - Acts As Authorizable
Add to favoritesActs As Authorizable is a part solution for providing pseudo-hierarchical role based authorizations on ActiveRecord model instances using your existing database table relationships. It is designed to eliminate the need for deep nesting controllers due to authorization. That being said, it is only part of a total authorization mechanism. Essentially it takes an authorized? call and passes it to the appropriate Role.allows? methods. You still have to define filters in controllers that call ActiveRecord models authorized? method and write the allows? method in your role class.
Deep Nesting & Authorization
One reason why developers deeply nest controllers is to allow hierarchical permission. Hence you see the following…
example.com/forums/forum_id/threads/thread_id/posts/post_id/edit
With deep nesting you can check a current user’s permissions against the post,thread, and forum. So a moderator for a specific form instance would be able to modify a post using the permissions present on the forum instance. Yet this is no longer possible with…
example.com/posts/post_id/edit
This is due to the fact that we don’t have explicit information about where the post falls in an authorization hierarchy. This is where ActsAsAuthorizable provides an improvement. With the plugin you can specify where to go to get further authorization information on a post. So the forum moderator could access the page, even though he doesn’t have direct permissions on the post.
Installation
script/plugin install git://github.com/mleventi/acts_as_authorizable.git
Your going to need ActiveRecord 2.1 or greater.
Usage
Acts As Authorizable works within your models by piggybacking on your has_many, has_one, and belongs_to associations to create a graph of model instances that represents authorization inheritance routes. For each ActiveRecord class you define which associations could yield permissions on the current class.
Prerequisites
You need a model which most likely would be a Role. It has to have an instance method "allows?" that takes in a permission object and returns a boolean. Two examples below are fine…
#Ex1
class Role < ActiveRecord::Base
def allows?(permission)
#do something and return true or false
end
end
#Ex2
class Moo < ActiveRecord::Base
def allows?(permission)
#do something and return true or false
end
end
The permission parameter is a black box. It can be anything because ActsAsAuthorizable doesn’t use it, just passes it around. You also need a ActiveRecord model that represents a user.
Example
#ForumMembership, holds how the user is related to a forum
class ForumMemberships < ActiveRecord::Base
acts_as_authorizable
belongs_to :user
belongs_to :forum
belongs_to :role
named_scope :with_user, lambda { |user| {:conditions => {:user_id => user}}}
auth_belongs_to_user :user, :role_association => :role
end
#Forum
class Forum < ActiveRecord::Base
acts_as_authorizable
has_many :forum_memberships
has_many :threads
auth_has_many_parents :forum_memberships, :user_scope => :with_user
end
#Thread
class Forum < ActiveRecord::Base
acts_as_authorizable
belongs_to :forum
has_many :posts
auth_belongs_to_parent :forum
end
#Post
class Post < ActiveRecord::Base
acts_as_authorizable
belongs_to :forum
belongs_to :user
auth_belongs_to_user :user, :role => 'Post Owner'
auth_belongs_to_parent :thread
end
So the following models represent an authorization hierarchy. The ForumMembership model should allow authorizations for the particular Forum, Thread, & Post. Also the user association on a post, should allow some authorizations for that particular post.
Each of the four classes gets an injected authorized? instance method.
model_instance.authorized?(user_instance,permission)
Now if you want to find out if a user has a permission on a certain post you simply call the post’s authorized? method. This call uses the auth_* methods to load in additional models until the permission is found or we have looked through the associations.
Note: models are searched in depth first order, with the order within a model determined lexically by how auth_* are called. Hence you almost always want to have auth_belongs_to_user calls first. Then scoped auth_has_many_parents, auth_belongs_to_parent, and finallly not-scoped, auth_belongs_to_parent.
Class Methods
acts_as_authorized
This is included in ActiveRecord models that are part of the authorization graph. If you use any of the other class methods this needs to be included in your class definition. It takes two optional parameters
- :role_class_name => A string representing the Role class name. Defaults to ‘Role’
- :role_locate_method => A string representing a method on the Role class that is used to find roles. Defaults to ‘find_by_name’.
Example:
acts_as_authorized :role_class_name => 'Role', :role_locate_method => 'find_by_name'
auth_belongs_to_user
This method takes several options…
- association REQUIRED, What association to use to fetch the user instance, defaults to :user
- :role_association => What association defines the role for the corresponding user instance
- :role => What the user instance should be treated as assuming the :role_association option is not defined. Passed as a parameter to the role_locate_method.
Example:
#Association belongs_to :user #Auth Piggyback using a set role auth_belongs_to_user, :user, :role => 'Grand Poo Bah'
Or:
#Association belongs_to :user belongs_to :role #Auth Piggyback using an associated role auth_belongs_to_user, :user, :role_association => :role
auth_belongs_to_parent & auth_has_one_parent
- association REQUIRED, What association to follow.
Example:
#Association belongs_to :forum #Auth Piggyback auth_belongs_to_parent :forum
auth_has_many_parents
- association REQUIRED, What association to follow.
- :user_scope => A symbol representing a named_scope that takes in a user object to condition the association. It is recommended that you use this for performance reasons.
Example:
#In Membership class
named_scope :with_user, lambda { |user| {:conditions => {:user_id => user}}}
#Association
has_many :memberships
#Auth Piggyback
auth_has_many_parents :memberships, :user_scope => :with_user
Instance Methods
Into every acts_as_authorizable model one instance method is injected called authorized?(user_instance,permission)
Advanced
You can have circular authorization dependencies. They will not result in infinite loops because the plugin colors the authorization graph while it searches for new permissions. Performance of the plugin is highly dependent on how you use it. Putting the obvious belongs_to_user permissions lexically first will result in better performance. Minimizing the number of auth_parents is also a good way of ensuring higher performance. The queries that result from the authorization plugins work are based on the associations passed. Hence ActiveRecords caching will work just fine as well.
Copyright © 2008 Matthew Leventi, released under the MIT license
http://github.com/mleventi/acts_as_authorizable/tree/master
git://github.com/mleventi/acts_as_authorizable.git
Rails' (MIT)
Model
