Monday, January 21, 2008

Rail Plugin: param_protected

It is a Ruby on Rails plugin that provides param_protected and param_accessible methods on controllers analogous to the attr_protected and attr_accessible methods for models.

It is a very simple -- all it does it filter out specified parameters from a request.

Why?


Good question... you can ready about why attr_protected sucks here, or you can just read my following little rant...

What's the goal of attr_protected? To protect us from user input, not from ourselves. When I used attr_protected, I had to refactor tons of code in models, controllers and tests (that already worked well) to not use the mass attribute setters.

Was this massive code refactoring really worth the protection from the very few places were I do something like:

User.new(params[:user])

or

User.update_attributes(params[:user])

Truth of the matter, I was hardly ever passing params (or a subset thereof) to a mass attribute setter. So no, it wasn't worth the massive refactoring job.

Installation


git clone git://github.com/cjbottaro/param_protected.git vendor/plugins/param_protected

Usage


class UsersController < ApplicationController
  param_protected :user_id
end

class AccountController < ApplicationController
  param_accessible :account_id
end

param_protected is used to blacklist and param_accessible is used to whitelist.

You can give it an array of param names to filter:

param_protected [:user_id, :some_other_param]

param_protected and param_accessible are both just before filters, so the usual :only and :except arguments can be used:

param_protected :user_id, :only => :some_action
param_protected :user_id, :only => [:some_action, :another_action]
param_protected :user_id, :except => :some_action
param_protected :user_id, :except => [:some_action, :another_action]

You can protect nested params also (removes params[:user][:user_id]):

param_protected 'user/user_id'

param_protected is aware of array params and handles them properly.

Caveats (IMPORTANT!!!!)


Because param_protected is really a before filter (uses prepend_before_filter), you must take special care to ensure that it runs before any of your other before filters!! If it is not, some of your before filters might have access to some params they shouldn't.

Tests


rake test should work (from the plugin's root dir), though it's far from comprehensive.

Documentation


Please see the README for usage instructions and examples.

4 comments:

Christopher J. Bottaro said...

Alright, it now works with nested params.

param_protected 'user/user_id'

will now remove params[:user][:user_id].

This has not been extensively tested, so please let me know about any bugs.

Mina said...

Very cool work Christopher (especially with the nested stuff). I completely agree with your sentiments. I still think there's still a place for attr_protected in the model for truly immutable attributes like :id, but the rest of the filtering is more suited as you've mentioned in the controller.

My beef though with attr_protect (and param_protected) is that they're blacklists which, in case of filtering, are much less effective security-wise compared to whitelists.

Do you have plans for a param_allowed ;-) ?

Christopher J. Bottaro said...

Hello mina,

Yes, param_accessible is in the works. I'm also working on cleaning up the code and making it more efficient, but the big feature is having it work with arrays...

For example you have a form with inputs like this:
<input name="names[][first]" />
<input name="names[][middle]" />
<input name="names[][last]" />

And thus params that come across like:
{ :names => [ {:first => Jane, :middle => Rae, :last => Doe}, {:first => John, :middle => Rambo, :last => Doe} ] }

Then you can say:
param_protected 'names/middle'

And it will trim the params to:
{ :names => [ {:first => Jane, :last => Doe}, {:first => John, :last => Doe} ] }

Hopefully I can knock that out in a couple weeks.

Christopher J. Bottaro said...

Ok, I implemented whitelisting (param_accessible) and also the features I talked about in the previous comment.

Please see the README for documentation.