Rails controllers are just super nice. My MVC experience predates STRUTS by several years. So having done it by hand several times, and seen pretty much everything out there, I just find that Rails has a lot of nice features that make it super convenient to use: controller inheritance, before_filter, default routes, and much more.
But this week I got in a real jam with security.
I am inheriting a bunch of controllers from a plugin. All of the plugin controllers extend BaseController, which the plugin also supplies. In order to add or change some functions, I just create a controller by the same name, do what I want, extend BaseController and everything just works great.
Except when the controller in the plugin has a line like this:
before_filter :login_required, :except => [:edit, :destroy]
If in my version of the controller I need to change it so that the edit and destroy actions are also protected by the login_required filter, well that gets tricky real fast. If you think you can just add
before_filter :login_required
and you are covered, think again. If you want to try
before_filter :login_required, :only => [:edit, :destroy]
well that doesn’t work either. Some people have a proposed (with diff and tests no less) that an :exclude and :include tag. This seems sensible and would be a solution to the problem I had.
What I chose to do is to wrap the filter I wanted to apply more broadly in a new name.
class BaseController < ApplicationController
before_filter :my_login_required
protected
def my_login_required
login_required
end
end
What that does is allow me to use my filter without regard to where it might collide with specifications from the inherited controllers. This makes it much easier to audit security in my application, is much less verbose, and pretty easy to understand.
In this case the filter that I wrapped was pretty lightweight. It didn’t do any database lookups for normal cases. That’s good because rails now doesn’t know that login_required and my_login_required are really the same filter. So I’m paying the overhead of running it twice when I require my_login_required and the super class uses login_required. If it was a heavyweight method that would have caused a problem I would have to hack a little more to make sure that one of them could bail out early.