Easy attribute assignment (in Rails)

Update: It seems like I reinvented Hash#slice which is already in the Rails API

recently less everything blogged about the need to secure your code against mass-assignment through forms.

Easy to hack! (As taken from the Less Everything blog)

  class Users < ApplicationController
    def create
      @user = User.create params[:user]
    end
  end

This is actually very old news, less everything caught some attention by threatening to hack people.

As they point out, there are two ways to prevent this problem. One is to use attr_protected or attr_accessible in the model. By the way, always use attr_accessible!

The other way to secure your code is to select the correct input in the controller. The code they showed for doing this was:

  class Users < ApplicationController
    def create
        @user = User.new
        @user.login = params[:user][:login]
        @user.password = params[:user][:password]
        @user.password_confirmation = params[:user][:password_confirmation]
        @user.save
  #user is created, but the is_admin flag is not set
    end
  end

It is suggested that you are stuck with writing 5x the code to be more secure. But obviously there is a pattern here, so lets abstract it out. All we really need to know is which paramaters to sign over

  class Users < ApplicationController
    def create
      @user = User.create(
        params.split_off(:login, :password, :password_confirmation) )
    end
  end

There, nice in DRY. Here is the helper:

  class Hash
    def split_off(*keys)
      h = {}
      keys.each { |key| h[key] = self[key] }
      h
    end

Destructive version:

    def split_off!(*keys)
      h = {}
      keys.each { |key| h[key] = delete key }
      h
    end
  end

allow a different action (with a block) if there is no value for the key:

  class Hash
    def split_off(*keys)
      h = {}
      if block_given?
        keys.each do |key|
          val = self[key]
          h[key] = val.nil? ? yield(key) : val
        end
      else
        keys.each { |key| h[key] = self[key] }
      end
      h
    end

    def split_off!(*keys)
      h = {}
      if block_given?
        keys.each do |key|
          val = delete(key)
          h[key] = val.nil? ? yield(key) : val
        end
      else
        keys.each { |key| h[key] = delete(key) }
      end
      h
    end
  end

© Greg Weber. All original code snippets are placed in the public domain.
Written on Fri Mar 21 17:34:39 2008.
Tagged as rails, ruby.
Send author an email.