Acts 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.
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.
script/plugin install git://github.com/mleventi/acts_as_authorizable.git
Your going to need ActiveRecord 2.1 or greater.
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.
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.
#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.
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'
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
-
association REQUIRED, What association to follow.
Example:
#Association belongs_to :forum #Auth Piggyback auth_belongs_to_parent :forum
-
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
Into every acts_as_authorizable model one instance method is injected called authorized?(user_instance,permission)
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