Handling mass assignment with Active Admin

With the recent Github security vulnerability, a lot of people have been paying closer attention to security in their Rails applications, mainly when dealing with mass assignment.

However, locking down mass assignment can be very inconvenient when dealing with admin interfaces where you need to allow certain users to mass assign attributes that a regular user shouldn't be able to do. Fortunately, there's an easy way to get past this by using Rails 3.1's scoped mass assignment

Scoped Mass Assignment

Scoped mass assignment allows you to assign a set of attributes to a certain 'scope' that you can then specify when calling create or update_attributes. Essentially, it allows you to do something like this:

# app/models/post.rb
class Post < ActiveRecord::Base
  attr_accessible :title, :content
  attr_accessible :title, :content, :published, as: :admin
end

# app/controllers/posts_controller.rb
def create
  # If the current user is an admin, this will allow the `published` attribute
  # to be mass assigned.
  if current_user.admin?
    @post = Post.new(params[:post], as: :admin)
  else
    @post = Post.new(params[:post])
  end
  @post.save
  respond_with @post
end

The above code will only allow the published attribute to be mass assigned if the current user is an admin.

Scoped Mass Assignment with Active Admin

Scoped mass assignment works fine when we're writing the controllers ourselves, but how do we assign a scope when using active admin, where controllers are automatically created for us? Well, fortunately, active admin makes use of inherited_resources, which provides a class method called with_role.

By appending a few lines of code to our active admin initializer, we can assign the admin scope to all model calls while under the admin interface:

# config/initializers/active_admin.rb
module ActiveAdmin
  class BaseController
    with_role :admin
  end
end

Now, whenever a post is created or updated by a user using the admin interface, the :admin scope will be used, which will allow the published attribute to be mass assigned.

If we had a role field on our AdminUser model we could even take this a step further by overriding the role_given? and as_role methods:

# config/initializers/active_admin.rb
module ActiveAdmin
  class BaseController
    def role_given?
      current_admin_user.role
    end

    def as_role
      { as: current_admin_user.role.downcase.to_sym }
    end
  end
end

Now, we can easily set the security for these roles in our model:

# config/initializers/post.rb
class Post < ActiveRecord::Base
  attr_accessible :title, :content, as: :editor
  attr_accessible :published, as: :publisher
end

Related Resources