Most of the demos or plugins for rails that I've seen, talk about authorization like it's dead simple. You're logged in or you're not. That may work in a lot of cases, but in a system with a larger overall breadth, where users fill specific roles rather than all using the system in the same way, that just doesn't cut it. This is the scenario I dealt with yesterday. The system boiled down to four levels of permissions: view, modify, promote and delete. Each permission was more powerful than the previous, so I implemented these permission types like a tree; view's parent was modify, for example. I then looked at what types of objects would need to be secured and how permissions could be shared amongst these objects. Luckily, the system breaks down into logical 'documents', including quotes, sales orders, invoices, etc. Although each document comprised of many tables and thus many models and many controllers, each document was also a logical group and a permission for the entire group could be shared. Now that I had permission types and document types, I just had to add users into the mix. Once that was ready, adding a permissions table was a piece of cake. This table would create a relationship between a user, a permission type and a document type. Now comes the fun part. Securing actions in controllers. I secure each action with a single line at the start of the action. For example, to secure the index action in my customers contoller, I would add this line:
return no_access unless user_can?('view', 'customers')
The first part, return no_access sends the user back to the http reffer.
The second part eventually gets down to a method on the User model:
def can?(permission_type, document_type)
cache_key = permission_type + '_' + document_type
if cached_permission?(cache_key)
logger.debug "## CACHE HIT ##"
return self.cached_permissions[cache_key]
else
logger.debug "## CACHE MISS ##"
permission = Permission.find(:first, :include => [:document_type], :conditions => ["document_types.abv_name = ? and user_id = ?", document_type, self.id])
if !permission.nil?
required_permission_type = Permission::Type.find_by_full_name(permission_type)
if (required_permission_type.ancestors + [required_permission_type]).include?(permission.type)
cache_permission(cache_key, true)
return true
else
cache_permission(cache_key, false)
return false
end
else
cache_permission(cache_key, false)
return false
end
end
end
.ancestors and concatenate it with itself. The code needs to be cleaned up a bit, but that's the general idea, and it's working out pretty nicely.