Mass Assignment Protection with DataMapper and Rails 3

Background

Mass assignment refers to the practice of assigning or updating a model’s attributes via the #attributes= method. First, an overview of mass assignment (adapted for DataMapper from the Rails Guides). For example, let’s say we have the following User model:

app/models/user.rb

class User
  include DataMapper::Resource

  property :id, Serial
  property :username, String
  property :admin, Boolean
end

If we want to take advantage of the convenience & cleanliness afforded by mass-assignment, we would create a new user like so:

app/controllers/users_controller.rb

class UsersController < ApplicationController
  def create
    @user = User.new(params[:user])
    if @user.save
      flash[:notice] = "New user created!"
    else
      flash[:alert] = "Invalid user!"
    end
  end
end

Side note: I’m a fan of DRY controllers. To avoid duplicating basic controller logic, I highly recommend José Valim’s excellent Inherited Resources library.

By creating or updating a user using the simple User.new or User#update_attributes methods, we are exposing ourselves to a simple attack where a malicious user can feed in parameters not included in the signup form:

params[:user] # => {:username => “ow3ned”, :admin => true}

The Rails Way

Rails has an easy method for dealing with this security hole: Mass assignment protection. If our User model used ActiveRecord, we would include the following:

app/models/user.rb

class User < ActiveRecord::Base
  attr_accessible :username
end

And just like that, only the attribute username could be set with mass assignment.

DataMapper

DataMapper’s philosophy, in order to keep things as lean as possible, is to extract as much behavior as possible into separate libraries & plugins. The only real option I was able to find was snusnu’s dm-is-protectable. Dm-is-protectable is well-written and very powerful, but I wanted something simpler.

Upon speaking with a few DataMapper developers, I learned that they prefer to handle this type of security at the controller level using Hash#only. There’s definitely nothing wrong with this approach, but for certain circumstances the Rails way makes more sense to me. I think of it as setting up a safety mechanism to prevent me from accidentally shooting myself in the foot. Also, to take full advantage of Inherited Resources, I needed to follow Rails conventions.

The Solution

In Rails 2, the mass assignment security was part of ActiveRecord, and the only real way to bring it to DataMapper would have been to duplicate the existing Rails code. Rails 3, however, extracted this logic into a submodule of ActiveModel so that it could be easily reused with other libraries. I love Rails 3!

The solution was easy to implement. The only part that made it somewhat complicated was that DataMapper allows you to update attributes using DataMapper::Property or DataMapper::Relationship objects as keys instead of just symbols. Check out my pending pull request here. Using this new functionality, our User model from above could be protected like this:

app/models/user.rb

require 'dm-rails/mass_assignment_security'
class User
  include DataMapper::Resource
  include Rails::DataMapper::MassAssignmentSecurity

  property :id, Serial
  property :username, String
  property :admin, Boolean

  attr_accessible :username
end

You could also include this functionality in all of your models:

config/application.rb

require 'dm-rails/mass_assignment_security'
DataMapper::Model.append_inclusions(Rails::DataMapper::MassAssignmentSecurity)

Enjoy!