diff options
-rw-r--r-- | actionpack/lib/action_view.rb | 5 | ||||
-rw-r--r-- | activemodel/CHANGELOG | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/mass_assignment_security.rb | 36 | ||||
-rw-r--r-- | activemodel/lib/active_model/mass_assignment_security/sanitizer.rb | 33 | ||||
-rw-r--r-- | activemodel/test/cases/mass_assignment_security/sanitizer_test.rb | 20 |
5 files changed, 66 insertions, 30 deletions
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index a67b61c1ef..78eddb7530 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -71,11 +71,6 @@ module ActionView autoload :TemplateError autoload :WrongEncodingError end - - autoload_at "action_view/template" do - autoload :TemplateHandler - autoload :TemplateHandlers - end end ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG index be4de2e53c..b1ad315c46 100644 --- a/activemodel/CHANGELOG +++ b/activemodel/CHANGELOG @@ -1,3 +1,5 @@ +* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior [Bogdan Gusiev] + *Rails 3.1.0 (unreleased)* * attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis] diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index cc30609f2b..c97420bc03 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -11,7 +11,9 @@ module ActiveModel class_attribute :_accessible_attributes class_attribute :_protected_attributes class_attribute :_active_authorizer - class_attribute :mass_assignment_sanitizer + + class_attribute :_mass_assignment_sanitizer + self.mass_assignment_sanitizer = :logger end # Mass assignment security provides an interface for protecting attributes @@ -43,6 +45,16 @@ module ActiveModel # # end # + # = Configuration options + # + # * <tt>mass_assignment_sanitizer</tt> - Defines sanitize method. Possible values are: + # * <tt>:logger</tt> (default) - writes filtered attributes to logger + # * <tt>:strict</tt> - raise <tt>ActiveModel::MassAssignmentSecurity::Error</tt> on any protected attribute update + # + # You can specify your own sanitizer object eg. MySanitizer.new. + # See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for example implementation. + # + # module ClassMethods # Attributes named in this macro are protected from mass-assignment # whenever attributes are sanitized before assignment. A role for the @@ -156,7 +168,7 @@ module ActiveModel options = args.extract_options! role = options[:as] || :default - self._accessible_attributes = accessible_attributes_configs.dup + self._accessible_attributes = accessible_attributes_configs.dup self._accessible_attributes[role] = self.accessible_attributes(role) + args self._active_authorizer = self._accessible_attributes @@ -179,19 +191,25 @@ module ActiveModel [] end + def mass_assignment_sanitizer=(value) + self._mass_assignment_sanitizer = if value.is_a?(Symbol) + const_get(:"#{value.to_s.camelize}Sanitizer").new(self) + else + value + end + end + private def protected_attributes_configs self._protected_attributes ||= begin - default_black_list = BlackList.new(attributes_protected_by_default) - Hash.new(default_black_list) + Hash.new { |h,k| h[k] = BlackList.new(attributes_protected_by_default) } end end def accessible_attributes_configs self._accessible_attributes ||= begin - default_white_list = WhiteList.new - Hash.new(default_white_list) + Hash.new { |h,k| h[k] = WhiteList.new } end end end @@ -199,11 +217,7 @@ module ActiveModel protected def sanitize_for_mass_assignment(attributes, role = :default) - (mass_assignment_sanitizer || default_mass_assignment_sanitizer).sanitize(attributes, mass_assignment_authorizer(role)) - end - - def default_mass_assignment_sanitizer - DefaultSanitizer.new(self.respond_to?(:logger) && self.logger) + _mass_assignment_sanitizer.sanitize(attributes, mass_assignment_authorizer(role)) end def mass_assignment_authorizer(role = :default) diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb index 5dbcf473bd..ee43a6694f 100644 --- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb +++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb @@ -1,6 +1,11 @@ +require 'active_support/core_ext/module/delegation' + module ActiveModel module MassAssignmentSecurity class Sanitizer + def initialize(target=nil) + end + # Returns all attributes not denied by the authorizer. def sanitize(attributes, authorizer) sanitized_attributes = attributes.reject { |key, value| authorizer.deny?(key) } @@ -18,20 +23,32 @@ module ActiveModel def process_removed_attributes(attrs) raise NotImplementedError, "#process_removed_attributes(attrs) suppose to be overwritten" end - end - class DefaultSanitizer < Sanitizer - attr_accessor :logger + class LoggerSanitizer < Sanitizer + delegate :logger, :to => :@target - def initialize(logger = nil) - self.logger = logger - super() + def initialize(target) + @target = target + super end - + + def logger? + @target.respond_to?(:logger) && @target.logger + end + def process_removed_attributes(attrs) - self.logger.debug "WARNING: Can't mass-assign protected attributes: #{attrs.join(', ')}" if self.logger + logger.debug "WARNING: Can't mass-assign protected attributes: #{attrs.join(', ')}" if logger? end end + + class StrictSanitizer < Sanitizer + def process_removed_attributes(attrs) + raise ActiveModel::MassAssignmentSecurity::Error, "Can't mass-assign protected attributes: #{attrs.join(', ')}" + end + end + + class Error < StandardError + end end end diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb index 8547694c24..62a6ec9c9b 100644 --- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb +++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb @@ -3,7 +3,7 @@ require 'logger' require 'active_support/core_ext/object/inclusion' class SanitizerTest < ActiveModel::TestCase - + attr_accessor :logger class Authorizer < ActiveModel::MassAssignmentSecurity::PermissionSet def deny?(key) @@ -12,24 +12,32 @@ class SanitizerTest < ActiveModel::TestCase end def setup - @sanitizer = ActiveModel::MassAssignmentSecurity::DefaultSanitizer.new + @logger_sanitizer = ActiveModel::MassAssignmentSecurity::LoggerSanitizer.new(self) + @strict_sanitizer = ActiveModel::MassAssignmentSecurity::StrictSanitizer.new(self) @authorizer = Authorizer.new end test "sanitize attributes" do original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } - attributes = @sanitizer.sanitize(original_attributes, @authorizer) + attributes = @logger_sanitizer.sanitize(original_attributes, @authorizer) assert attributes.key?('first_name'), "Allowed key shouldn't be rejected" assert !attributes.key?('admin'), "Denied key should be rejected" end - test "debug mass assignment removal" do + test "debug mass assignment removal with LoggerSanitizer" do original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } log = StringIO.new - @sanitizer.logger = Logger.new(log) - @sanitizer.sanitize(original_attributes, @authorizer) + self.logger = Logger.new(log) + @logger_sanitizer.sanitize(original_attributes, @authorizer) assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}") end + test "debug mass assignment removal with StrictSanitizer" do + original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } + assert_raise ActiveModel::MassAssignmentSecurity::Error do + @strict_sanitizer.sanitize(original_attributes, @authorizer) + end + end + end |