require 'active_support/core_ext/class/attribute.rb' require 'active_model/mass_assignment_security/permission_set' module ActiveModel # = Active Model Mass-Assignment Security module MassAssignmentSecurity extend ActiveSupport::Concern included do class_attribute :_accessible_attributes class_attribute :_protected_attributes class_attribute :_active_authorizer end # 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 # include ActiveModel::MassAssignmentSecurity # # attr_accessible :first_name, :last_name # attr_accessible :first_name, :last_name, :plan_id, :as => :admin # # def update # ... # @account.update_attributes(account_params) # ... # end # # protected # # def account_params # scope = admin ? :admin : :default # sanitize_for_mass_assignment(params[:account], scope) # end # # end # module ClassMethods # Attributes named in this macro are protected from mass-assignment # whenever attributes are sanitized before assignment. A scope for the # attributes is optional, if no scope is provided then :default is used. # A scope can be defined by using the :as option. # # 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. Example: # # class Customer # include ActiveModel::MassAssignmentSecurity # # attr_accessor :name, :credit_rating # # attr_protected :credit_rating, :last_login # attr_protected :last_login, :as => :admin # # def assign_attributes(values, options = {}) # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| # send("#{k}=", v) # end # end # end # # When using a :default scope : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) # customer.name # => "David" # customer.credit_rating # => nil # customer.last_login # => nil # # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # # And using the :admin scope : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) # customer.name # => "David" # customer.credit_rating # => "Excellent" # customer.last_login # => nil # # 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(*args) options = args.extract_options! scope = options[:as] || :default self._protected_attributes = protected_attributes_configs.dup self._protected_attributes[scope] = self.protected_attributes(scope) + args self._active_authorizer = self._protected_attributes end # Specifies a white list of model attributes that can be set via # mass-assignment. # # Like +attr_protected+, a scope for the attributes is optional, # if no scope is provided then :default is used. A scope can be defined by # using the :as option. # # 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 # include ActiveModel::MassAssignmentSecurity # # attr_accessor :name, :credit_rating # # attr_accessible :name # attr_accessible :name, :credit_rating, :as => :admin # # def assign_attributes(values, options = {}) # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| # send("#{k}=", v) # end # end # end # # When using a :default scope : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) # customer.name # => "David" # customer.credit_rating # => nil # # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # # And using the :admin scope : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) # customer.name # => "David" # customer.credit_rating # => "Excellent" # # Note that using Hash#except or Hash#slice in place of +attr_accessible+ # to sanitize attributes won't provide sufficient protection. def attr_accessible(*args) options = args.extract_options! scope = options[:as] || :default self._accessible_attributes = accessible_attributes_configs.dup self._accessible_attributes[scope] = self.accessible_attributes(scope) + args self._active_authorizer = self._accessible_attributes end def protected_attributes(scope = :default) protected_attributes_configs[scope] end def accessible_attributes(scope = :default) accessible_attributes_configs[scope] end def active_authorizers self._active_authorizer ||= protected_attributes_configs end alias active_authorizer active_authorizers def attributes_protected_by_default [] end private def protected_attributes_configs self._protected_attributes ||= begin default_black_list = BlackList.new(attributes_protected_by_default).tap do |w| w.logger = self.logger if self.respond_to?(:logger) end Hash.new(default_black_list) end end def accessible_attributes_configs self._accessible_attributes ||= begin default_white_list = WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) } Hash.new(default_white_list) end end end protected def sanitize_for_mass_assignment(attributes, scope = :default) mass_assignment_authorizer(scope).sanitize(attributes) end def mass_assignment_authorizer(scope = :default) self.class.active_authorizer[scope] end end end