06.01.07
15:29

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

This system turns out being pretty powerful as well as staying flexible enough to add permission types in the future, which I had to design for. Also, the caching of permissions on the instance of the user model that is sitting in the session was a definite necessity with this sort of system. It would just be way too expensive to do all of that fancy stuff every time an action was hit. Especially the little part where I call .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.

Rss - Subscribe to the Feed
© Copyright 2005-2007 Sliced Software, Inc.