require 'active_record/mass_assignment_security/permission_set'
module ActiveRecord
module MassAssignmentSecurity
# Mass assignment security provides an interface for protecting attributes
# from end-user assignment. For more complex permissions, mass assignment security
# may be handled outside the model by extending a non-ActiveRecord class,
# such as a controller, with this behavior.
#
# For example, a logged in user may need to assign additional attributes depending
# on their role:
#
# class AccountsController < ApplicationController
# extend ActiveRecord::MassAssignmentSecurity
#
# attr_accessible :first_name, :last_name
#
# def self.admin_accessible_attributes
# accessible_attributes + [ :plan_id ]
# end
#
# def update
# ...
# @account.update_attributes(account_params)
# ...
# end
#
# protected
#
# def account_params
# remove_attributes_protected_from_mass_assignment(params[:account])
# end
#
# def mass_assignment_authorizer
# admin ? admin_accessible_attributes : super
# end
#
# end
#
def self.extended(base)
base.send(:include, InstanceMethods)
end
module InstanceMethods
protected
def remove_attributes_protected_from_mass_assignment(attributes)
mass_assignment_authorizer.sanitize(attributes)
end
def mass_assignment_authorizer
self.class.mass_assignment_authorizer
end
end
# Attributes named in this macro are protected from mass-assignment,
# such as new(attributes),
# update_attributes(attributes), or
# attributes=(attributes).
#
# Mass-assignment to these attributes will simply be ignored, to assign
# to them you can use direct writer methods. This is meant to protect
# sensitive attributes from being overwritten by malicious users
# tampering with URLs or forms.
#
# class Customer < ActiveRecord::Base
# attr_protected :credit_rating
# end
#
# customer = Customer.new("name" => David, "credit_rating" => "Excellent")
# customer.credit_rating # => nil
# customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
# customer.credit_rating # => nil
#
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
# To start from an all-closed default and enable attributes as needed,
# have a look at +attr_accessible+.
#
# Note that using Hash#except or Hash#slice in place of +attr_protected+
# to sanitize attributes won't provide sufficient protection.
def attr_protected(*keys)
use_authorizer(:protected_attributes)
protected_attributes.merge(keys)
end
# Specifies a white list of model attributes that can be set via
# mass-assignment, such as new(attributes),
# update_attributes(attributes), or
# attributes=(attributes)
#
# This is the opposite of the +attr_protected+ macro: Mass-assignment
# will only set attributes in this list, to assign to the rest of
# attributes you can use direct writer methods. This is meant to protect
# sensitive attributes from being overwritten by malicious users
# tampering with URLs or forms. If you'd rather start from an all-open
# default and restrict attributes as needed, have a look at
# +attr_protected+.
#
# class Customer < ActiveRecord::Base
# attr_accessible :name, :nickname
# end
#
# customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent")
# customer.credit_rating # => nil
# customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
# customer.credit_rating # => nil
#
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
# Note that using Hash#except or Hash#slice in place of +attr_accessible+
# to sanitize attributes won't provide sufficient protection.
def attr_accessible(*keys)
use_authorizer(:accessible_attributes)
accessible_attributes.merge(keys)
end
# Returns an array of all the attributes that have been protected from mass-assignment.
def protected_attributes
read_inheritable_attribute(:protected_attributes) || begin
authorizer = BlackList.new
authorizer += attributes_protected_by_default
authorizer.logger = logger
write_inheritable_attribute(:protected_attributes, authorizer)
end
end
# Returns an array of all the attributes that have been made accessible to mass-assignment.
def accessible_attributes
read_inheritable_attribute(:accessible_attributes) || begin
authorizer = WhiteList.new
authorizer.logger = logger
write_inheritable_attribute(:accessible_attributes, authorizer)
end
end
def mass_assignment_authorizer
protected_attributes
end
private
# Sets the active authorizer, (attr_protected or attr_accessible). Subsequent calls
# will raise an exception when using a different authorizer_id.
def use_authorizer(authorizer_id) # :nodoc:
if active_authorizer_id = read_inheritable_attribute(:active_authorizer_id)
unless authorizer_id == active_authorizer_id
raise("Already using #{active_authorizer_id}, cannot use #{authorizer_id}")
end
else
write_inheritable_attribute(:active_authorizer_id, authorizer_id)
end
end
end
end