Quick & Easy attribute protecting per ActiveRecord instance
August 22nd, 2008
Here's an idea:
class Member < ActiveRecord::Base
ProtectedAttributes = [:is_admin]
def protect_attributes!
@protect_attributes = true
end
def attributes=(attributes)
if @protect_attributes
attributes.symbolize_keys!
attributes = attributes.delete_if {|key, value| ProtectedAttributes.include? key }
end
super
end
end
enabling:
>> member = Member.new
>> member.protect_attributes!
>> member.update_attributes(:is_admin => 1)
>> member.is_admin
=> 0
Meaning you can use mass-assignment methods on the front end without worrying about users spoofing form posts.
Thoughts?
8 Responses to “Quick & Easy attribute protecting per ActiveRecord instance”
Sorry, comments are closed for this article.

August 22nd, 2008 at 07:13 PM
I would rather it was the other way round - that you called some method to enable mass assignment of protected attributes. At the moment, you could easily forget to call the method.
August 22nd, 2008 at 07:45 PM
Wouldn't it be easier to use attrprotected :isadmin ?
Or better yet (what I like to do) is make all the attributes protected, and then enable only the ones users can assign to with attr_accessible.
August 22nd, 2008 at 08:08 PM
@luke: The trouble with that is that it attr_accessible or attr_protected protects assignment any time you need to update attributes - this means if you have an admin system, you'll have to manually get around the protection.
It's common to create methods to toggle something like 'is_admin', but that can be overkill in some cases.
Sometimes you just want to be able protect some attributes from updating by front end users.
August 22nd, 2008 at 09:28 PM
I dig the idea, and have certainly felt the same pain before. I'm a whitelist sorta guy though, so I'd rather have to make a call to *un*protect, rather than remember to protect.
What if the protection was enabled on a global basis, so you could ActiveRecord::Protection.enable! in an application-wide beforefilter, and then _mymodelinstance.unprotect! inside things like admin controllers?
You'd have to think about non-web flows and make sure you've protected yourself there, but this makes it easy and those places are usually at less at risk anyway.
You may be able to accomplish this with an aliasmethodchain on ActiveRecord::Base#attributes=
August 23rd, 2008 at 02:56 AM
Stephen,
In that case, I would use withunprotectedattributes:
http://henrik.nyh.se/2008/02/withunprotectedattributes-rails-plugin
Or possibly just bypass the protection with send:
http://deaddeadgood.com/2008/6/25/how-to-bypass-attraccessible-and-attrprotected-in-rails
August 25th, 2008 at 09:04 AM
I think this is an other step in the wrong way.
The concept of protected attributes to prevent HTTP post abuse is a violation of the MVC model. The controller is responsible to handle the data between the Model and the "views (or requests)".
Something simple as: User.new(params[:user].slice(:name, :email)) will prevent any abuse. It will even prevent the modification of unprotected attributes that aren't supposed to be edited with a particular form.
August 28th, 2008 at 01:37 PM
My first though is that if you're going to do something like this, there's really no reason to modify the attributes hash. It was sent to you so that you could do something with it, not _to_ it. So start by dup:ing it.
I can see that Bertg has a point too. Perhaps you could have a #protected_attributes= that you would use instead of #attributes= when you want to be on the safe side. That way the model doesn't need to have different states for protected and unprotected, and it would be pretty clear what you are trying to do. I don't think that #slice is the way to go, since it would be hard for a controller to know what the unprotected attributes are at all times. Or something like
August 28th, 2008 at 03:32 PM
To be honest, I've actually re-thought this based on on what Bertg said.
It's fairly trivial to just add a before_filter that you can apply to any actions that need to update a details you need to protect:
I think the issue is that putting details that are meant to be used in context of a controller - i.e. with a logged in user - is not strictly kosher MVC.