diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2012-09-18 12:33:13 -0700 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2012-09-18 12:33:13 -0700 |
commit | c49d959e9d40101f1712a452004695f4ce27d84c (patch) | |
tree | f87077668c14ed414e3d819212b0813e74551c8f /activemodel | |
parent | ade701045f0f80399d99151e5583d4f86c68678e (diff) | |
parent | 3919fcd61ef999aab9397332ce3017870b184766 (diff) | |
download | rails-c49d959e9d40101f1712a452004695f4ce27d84c.tar.gz rails-c49d959e9d40101f1712a452004695f4ce27d84c.tar.bz2 rails-c49d959e9d40101f1712a452004695f4ce27d84c.zip |
Merge pull request #7251 from rails/integrate-strong_parameters
Integrate strong_parameters in Rails 4
Diffstat (limited to 'activemodel')
19 files changed, 97 insertions, 801 deletions
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index d1cc19ec6b..f757ba9843 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -34,9 +34,10 @@ module ActiveModel autoload :Conversion autoload :Dirty autoload :EachValidator, 'active_model/validator' + autoload :ForbiddenAttributesProtection autoload :Lint - autoload :MassAssignmentSecurity autoload :Model + autoload :DeprecatedMassAssignmentSecurity autoload :Name, 'active_model/naming' autoload :Naming autoload :Observer, 'active_model/observing' diff --git a/activemodel/lib/active_model/deprecated_mass_assignment_security.rb b/activemodel/lib/active_model/deprecated_mass_assignment_security.rb new file mode 100644 index 0000000000..16b8466e55 --- /dev/null +++ b/activemodel/lib/active_model/deprecated_mass_assignment_security.rb @@ -0,0 +1,19 @@ +module ActiveModel + module DeprecatedMassAssignmentSecurity + extend ActiveSupport::Concern + + module ClassMethods + def attr_protected(*args) + raise "`attr_protected` is extracted out of Rails into a gem. " \ + "Please use new recommended protection model for params " \ + "or add `protected_attributes` to your Gemfile to use old one." + end + + def attr_accessible(*args) + raise "`attr_accessible` is extracted out of Rails into a gem. " \ + "Please use new recommended protection model for params " \ + "or add `protected_attributes` to your Gemfile to use old one." + end + end + end +end diff --git a/activemodel/lib/active_model/forbidden_attributes_protection.rb b/activemodel/lib/active_model/forbidden_attributes_protection.rb new file mode 100644 index 0000000000..a5e4c4f650 --- /dev/null +++ b/activemodel/lib/active_model/forbidden_attributes_protection.rb @@ -0,0 +1,14 @@ +module ActiveModel + class ForbiddenAttributesError < StandardError + end + + module ForbiddenAttributesProtection + def sanitize_for_mass_assignment(attributes, options = {}) + if attributes.respond_to?(:permitted?) && !attributes.permitted? + raise ActiveModel::ForbiddenAttributesError + else + attributes + end + end + end +end diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb deleted file mode 100644 index f9841abcb0..0000000000 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ /dev/null @@ -1,350 +0,0 @@ -require 'active_support/core_ext/string/inflections' -require 'active_model/mass_assignment_security/permission_set' -require 'active_model/mass_assignment_security/sanitizer' - -module ActiveModel - # == Active Model Mass-Assignment Security - # - # 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 - # role = admin ? :admin : :default - # sanitize_for_mass_assignment(params[:account], role) - # end - # - # 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. <tt>MySanitizer.new</tt>. - # See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for - # example implementation. - module MassAssignmentSecurity - extend ActiveSupport::Concern - - included do - class_attribute :_accessible_attributes, instance_writer: false - class_attribute :_protected_attributes, instance_writer: false - class_attribute :_active_authorizer, instance_writer: false - - class_attribute :_mass_assignment_sanitizer, instance_writer: false - self.mass_assignment_sanitizer = :logger - end - - module ClassMethods - # Attributes named in this macro are protected from mass-assignment - # whenever attributes are sanitized before assignment. A role for the - # attributes is optional, if no role is provided then <tt>:default</tt> - # is used. A role can be defined by using the <tt>:as</tt> option with a - # symbol or an array of symbols as the value. - # - # 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 - # include ActiveModel::MassAssignmentSecurity - # - # attr_accessor :name, :email, :logins_count - # - # attr_protected :logins_count - # # Suppose that admin can not change email for customer - # attr_protected :logins_count, :email, 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 the <tt>:default</tt> role: - # - # customer = Customer.new - # customer.assign_attributes({ name: 'David', email: 'a@b.com', logins_count: 5 }, as: :default) - # customer.name # => "David" - # customer.email # => "a@b.com" - # customer.logins_count # => nil - # - # And using the <tt>:admin</tt> role: - # - # customer = Customer.new - # customer.assign_attributes({ name: 'David', email: 'a@b.com', logins_count: 5}, as: :admin) - # customer.name # => "David" - # customer.email # => nil - # customer.logins_count # => nil - # - # customer.email = 'c@d.com' - # customer.email # => "c@d.com" - # - # To start from an all-closed default and enable attributes as needed, - # have a look at +attr_accessible+. - # - # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of - # +attr_protected+ to sanitize attributes provides basically the same - # functionality, but it makes a bit tricky to deal with nested attributes. - def attr_protected(*args) - options = args.extract_options! - role = options[:as] || :default - - self._protected_attributes = protected_attributes_configs.dup - - Array(role).each do |name| - self._protected_attributes[name] = self.protected_attributes(name) + args - end - - 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 role for the attributes is optional, - # if no role is provided then <tt>:default</tt> is used. A role can be - # defined by using the <tt>:as</tt> option with a symbol or an array of - # symbols as the value. - # - # 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 - # - # # Both admin and default user can change name of a customer - # attr_accessible :name, as: [:admin, :default] - # # Only admin can change credit rating of a customer - # attr_accessible :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 the <tt>:default</tt> role: - # - # 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 <tt>:admin</tt> role: - # - # 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 <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of - # +attr_accessible+ to sanitize attributes provides basically the same - # functionality, but it makes a bit tricky to deal with nested attributes. - def attr_accessible(*args) - options = args.extract_options! - role = options[:as] || :default - - self._accessible_attributes = accessible_attributes_configs.dup - - Array(role).each do |name| - self._accessible_attributes[name] = self.accessible_attributes(name) + args - end - - self._active_authorizer = self._accessible_attributes - end - - # Returns an instance of <tt>ActiveModel::MassAssignmentSecurity::BlackList</tt> - # with the attributes protected by #attr_protected method. If no +role+ - # is provided, then <tt>:default</tt> is used. - # - # class Customer - # include ActiveModel::MassAssignmentSecurity - # - # attr_accessor :name, :email, :logins_count - # - # attr_protected :logins_count - # attr_protected :logins_count, :email, as: :admin - # end - # - # Customer.protected_attributes - # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count"}> - # - # Customer.protected_attributes(:default) - # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count"}> - # - # Customer.protected_attributes(:admin) - # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count", "email"}> - def protected_attributes(role = :default) - protected_attributes_configs[role] - end - - # Returns an instance of <tt>ActiveModel::MassAssignmentSecurity::WhiteList</tt> - # with the attributes protected by #attr_accessible method. If no +role+ - # is provided, then <tt>:default</tt> is used. - # - # class Customer - # include ActiveModel::MassAssignmentSecurity - # - # attr_accessor :name, :credit_rating - # - # attr_accessible :name, as: [:admin, :default] - # attr_accessible :credit_rating, as: :admin - # end - # - # Customer.accessible_attributes - # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}> - # - # Customer.accessible_attributes(:default) - # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}> - # - # Customer.accessible_attributes(:admin) - # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name", "credit_rating"}> - def accessible_attributes(role = :default) - accessible_attributes_configs[role] - end - - # Returns a hash with the protected attributes (by #attr_accessible or - # #attr_protected) per role. - # - # class Customer - # include ActiveModel::MassAssignmentSecurity - # - # attr_accessor :name, :credit_rating - # - # attr_accessible :name, as: [:admin, :default] - # attr_accessible :credit_rating, as: :admin - # end - # - # Customer.active_authorizers - # # => { - # # :admin=> #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name", "credit_rating"}>, - # # :default=>#<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}> - # # } - def active_authorizers - self._active_authorizer ||= protected_attributes_configs - end - alias active_authorizer active_authorizers - - # Returns an empty array by default. You can still override this to define - # the default attributes protected by #attr_protected method. - # - # class Customer - # include ActiveModel::MassAssignmentSecurity - # - # def self.attributes_protected_by_default - # [:name] - # end - # end - # - # Customer.protected_attributes - # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {:name}> - def attributes_protected_by_default - [] - end - - # Defines sanitize method. - # - # class Customer - # include ActiveModel::MassAssignmentSecurity - # - # attr_accessor :name - # - # attr_protected :name - # - # def assign_attributes(values) - # sanitize_for_mass_assignment(values).each do |k, v| - # send("#{k}=", v) - # end - # end - # end - # - # # See ActiveModel::MassAssignmentSecurity::StrictSanitizer for more information. - # Customer.mass_assignment_sanitizer = :strict - # - # customer = Customer.new - # customer.assign_attributes(name: 'David') - # # => ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes for Customer: name - # - # Also, you can specify your own sanitizer object. - # - # class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer - # def process_removed_attributes(klass, attrs) - # raise StandardError - # end - # end - # - # Customer.mass_assignment_sanitizer = CustomSanitizer.new - # - # customer = Customer.new - # customer.assign_attributes(name: 'David') - # # => StandardError: StandardError - 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 - Hash.new { |h,k| h[k] = BlackList.new(attributes_protected_by_default) } - end - end - - def accessible_attributes_configs - self._accessible_attributes ||= begin - Hash.new { |h,k| h[k] = WhiteList.new } - end - end - end - - protected - - def sanitize_for_mass_assignment(attributes, role = nil) #:nodoc: - _mass_assignment_sanitizer.sanitize(self.class, attributes, mass_assignment_authorizer(role)) - end - - def mass_assignment_authorizer(role) #:nodoc: - self.class.active_authorizer[role || :default] - end - end -end diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb deleted file mode 100644 index f104d0306c..0000000000 --- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'set' - -module ActiveModel - module MassAssignmentSecurity - class PermissionSet < Set #:nodoc: - - def +(values) - super(values.compact.map(&:to_s)) - end - - def include?(key) - super(remove_multiparameter_id(key)) - end - - def deny?(key) - raise NotImplementedError, "#deny?(key) supposed to be overwritten" - end - - protected - - def remove_multiparameter_id(key) - key.to_s.gsub(/\(.+/, '') - end - end - - class WhiteList < PermissionSet #:nodoc: - - def deny?(key) - !include?(key) - end - end - - class BlackList < PermissionSet #:nodoc: - - def deny?(key) - include?(key) - end - end - end -end diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb deleted file mode 100644 index dafb7cdff3..0000000000 --- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb +++ /dev/null @@ -1,74 +0,0 @@ -module ActiveModel - module MassAssignmentSecurity - class Sanitizer #:nodoc: - # Returns all attributes not denied by the authorizer. - def sanitize(klass, attributes, authorizer) - rejected = [] - sanitized_attributes = attributes.reject do |key, value| - rejected << key if authorizer.deny?(key) - end - process_removed_attributes(klass, rejected) unless rejected.empty? - sanitized_attributes - end - - protected - - def process_removed_attributes(klass, attrs) - raise NotImplementedError, "#process_removed_attributes(attrs) suppose to be overwritten" - end - end - - class LoggerSanitizer < Sanitizer #:nodoc: - def initialize(target) - @target = target - super() - end - - def logger - @target.logger - end - - def logger? - @target.respond_to?(:logger) && @target.logger - end - - def backtrace - if defined? Rails - Rails.backtrace_cleaner.clean(caller) - else - caller - end - end - - def process_removed_attributes(klass, attrs) - if logger? - logger.warn do - "WARNING: Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}\n" + - backtrace.map { |trace| "\t#{trace}" }.join("\n") - end - end - end - end - - class StrictSanitizer < Sanitizer #:nodoc: - def initialize(target = nil) - super() - end - - def process_removed_attributes(klass, attrs) - return if (attrs - insensitive_attributes).empty? - raise ActiveModel::MassAssignmentSecurity::Error.new(klass, attrs) - end - - def insensitive_attributes - ['id'] - end - end - - class Error < StandardError #:nodoc: - def initialize(klass, attrs) - super("Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}") - end - end - end -end diff --git a/activemodel/test/cases/deprecated_mass_assignment_security_test.rb b/activemodel/test/cases/deprecated_mass_assignment_security_test.rb new file mode 100644 index 0000000000..c1fe8822cd --- /dev/null +++ b/activemodel/test/cases/deprecated_mass_assignment_security_test.rb @@ -0,0 +1,16 @@ +require 'cases/helper' +require 'models/project' + +class DeprecatedMassAssignmentSecurityTest < ActiveModel::TestCase + def test_attr_accessible_raise_error + assert_raise RuntimeError, /protected_attributes/ do + Project.attr_accessible :username + end + end + + def test_attr_protected_raise_error + assert_raise RuntimeError, /protected_attributes/ do + Project.attr_protected :username + end + end +end diff --git a/activemodel/test/cases/forbidden_attributes_protection_test.rb b/activemodel/test/cases/forbidden_attributes_protection_test.rb new file mode 100644 index 0000000000..3cb204a2c5 --- /dev/null +++ b/activemodel/test/cases/forbidden_attributes_protection_test.rb @@ -0,0 +1,36 @@ +require 'cases/helper' +require 'active_support/core_ext/hash/indifferent_access' +require 'models/account' + +class ProtectedParams < ActiveSupport::HashWithIndifferentAccess + attr_accessor :permitted + alias :permitted? :permitted + + def initialize(attributes) + super(attributes) + @permitted = false + end + + def permit! + @permitted = true + self + end +end + +class ActiveModelMassUpdateProtectionTest < ActiveSupport::TestCase + test "forbidden attributes cannot be used for mass updating" do + params = ProtectedParams.new({ "a" => "b" }) + assert_raises(ActiveModel::ForbiddenAttributesError) do + Account.new.sanitize_for_mass_assignment(params) + end + end + + test "permitted attributes can be used for mass updating" do + params = ProtectedParams.new({ "a" => "b" }).permit! + assert_equal({ "a" => "b" }, Account.new.sanitize_for_mass_assignment(params)) + end + + test "regular attributes should still be allowed" do + assert_equal({ a: "b" }, Account.new.sanitize_for_mass_assignment(a: "b")) + end +end diff --git a/activemodel/test/cases/mass_assignment_security/black_list_test.rb b/activemodel/test/cases/mass_assignment_security/black_list_test.rb deleted file mode 100644 index 0ec7f8719c..0000000000 --- a/activemodel/test/cases/mass_assignment_security/black_list_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require "cases/helper" - -class BlackListTest < ActiveModel::TestCase - - def setup - @black_list = ActiveModel::MassAssignmentSecurity::BlackList.new - @included_key = 'admin' - @black_list += [ @included_key ] - end - - test "deny? is true for included items" do - assert_equal true, @black_list.deny?(@included_key) - end - - test "deny? is false for non-included items" do - assert_equal false, @black_list.deny?('first_name') - end - - -end diff --git a/activemodel/test/cases/mass_assignment_security/permission_set_test.rb b/activemodel/test/cases/mass_assignment_security/permission_set_test.rb deleted file mode 100644 index 8082c49852..0000000000 --- a/activemodel/test/cases/mass_assignment_security/permission_set_test.rb +++ /dev/null @@ -1,36 +0,0 @@ -require "cases/helper" - -class PermissionSetTest < ActiveModel::TestCase - - def setup - @permission_list = ActiveModel::MassAssignmentSecurity::PermissionSet.new - end - - test "+ stringifies added collection values" do - symbol_collection = [ :admin ] - new_list = @permission_list += symbol_collection - - assert new_list.include?('admin'), "did not add collection to #{@permission_list.inspect}}" - end - - test "+ compacts added collection values" do - added_collection = [ nil ] - new_list = @permission_list + added_collection - assert_equal new_list, @permission_list, "did not add collection to #{@permission_list.inspect}}" - end - - test "include? normalizes multi-parameter keys" do - multi_param_key = 'admin(1)' - new_list = @permission_list += [ 'admin' ] - - assert new_list.include?(multi_param_key), "#{multi_param_key} not found in #{@permission_list.inspect}" - end - - test "include? normal keys" do - normal_key = 'admin' - new_list = @permission_list += [ normal_key ] - - assert new_list.include?(normal_key), "#{normal_key} not found in #{@permission_list.inspect}" - end - -end diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb deleted file mode 100644 index b141cec059..0000000000 --- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb +++ /dev/null @@ -1,50 +0,0 @@ -require "cases/helper" -require 'active_support/logger' - -class SanitizerTest < ActiveModel::TestCase - attr_accessor :logger - - class Authorizer < ActiveModel::MassAssignmentSecurity::PermissionSet - def deny?(key) - ['admin', 'id'].include?(key) - end - end - - def setup - @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 = @logger_sanitizer.sanitize(self.class, 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 with LoggerSanitizer" do - original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } - log = StringIO.new - self.logger = ActiveSupport::Logger.new(log) - @logger_sanitizer.sanitize(self.class, 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(self.class, original_attributes, @authorizer) - end - end - - test "mass assignment insensitive attributes" do - original_attributes = {'id' => 1, 'first_name' => 'allowed'} - - assert_nothing_raised do - @strict_sanitizer.sanitize(self.class, original_attributes, @authorizer) - end - end - -end diff --git a/activemodel/test/cases/mass_assignment_security/white_list_test.rb b/activemodel/test/cases/mass_assignment_security/white_list_test.rb deleted file mode 100644 index 737b55492a..0000000000 --- a/activemodel/test/cases/mass_assignment_security/white_list_test.rb +++ /dev/null @@ -1,19 +0,0 @@ -require "cases/helper" - -class WhiteListTest < ActiveModel::TestCase - - def setup - @white_list = ActiveModel::MassAssignmentSecurity::WhiteList.new - @included_key = 'first_name' - @white_list += [ @included_key ] - end - - test "deny? is false for included items" do - assert_equal false, @white_list.deny?(@included_key) - end - - test "deny? is true for non-included items" do - assert_equal true, @white_list.deny?('admin') - end - -end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb deleted file mode 100644 index 45757615f5..0000000000 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ /dev/null @@ -1,118 +0,0 @@ -require "cases/helper" -require 'models/mass_assignment_specific' - - -class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer - - def process_removed_attributes(klass, attrs) - raise StandardError - end - -end - -class MassAssignmentSecurityTest < ActiveModel::TestCase - def test_attribute_protection - user = User.new - expected = { "name" => "John Smith", "email" => "john@smith.com" } - sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true)) - assert_equal expected, sanitized - end - - def test_attribute_protection_when_role_is_nil - user = User.new - expected = { "name" => "John Smith", "email" => "john@smith.com" } - sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), nil) - assert_equal expected, sanitized - end - - def test_only_moderator_role_attribute_accessible - user = SpecialUser.new - expected = { "name" => "John Smith", "email" => "john@smith.com" } - sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator) - assert_equal expected, sanitized - - sanitized = user.sanitize_for_mass_assignment({ "name" => "John Smith", "email" => "john@smith.com", "admin" => true }) - assert_equal({}, sanitized) - end - - def test_attributes_accessible - user = Person.new - expected = { "name" => "John Smith", "email" => "john@smith.com" } - sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true)) - assert_equal expected, sanitized - end - - def test_attributes_accessible_with_admin_role - user = Person.new - expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true } - sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin) - assert_equal expected, sanitized - end - - def test_attributes_accessible_with_roles_given_as_array - user = Account.new - expected = { "name" => "John Smith", "email" => "john@smith.com" } - sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true)) - assert_equal expected, sanitized - end - - def test_attributes_accessible_with_admin_role_when_roles_given_as_array - user = Account.new - expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true } - sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin) - assert_equal expected, sanitized - end - - def test_attributes_protected_by_default - firm = Firm.new - expected = { } - sanitized = firm.sanitize_for_mass_assignment({ "type" => "Client" }) - assert_equal expected, sanitized - end - - def test_mass_assignment_protection_inheritance - assert_blank LoosePerson.accessible_attributes - assert_equal Set.new(['credit_rating', 'administrator']), LoosePerson.protected_attributes - - assert_blank LoosePerson.accessible_attributes - assert_equal Set.new(['credit_rating']), LoosePerson.protected_attributes(:admin) - - assert_blank LooseDescendant.accessible_attributes - assert_equal Set.new(['credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes - - assert_blank LooseDescendantSecond.accessible_attributes - assert_equal Set.new(['credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes, - 'Running attr_protected twice in one class should merge the protections' - - assert_blank TightPerson.protected_attributes - TightPerson.attributes_protected_by_default - assert_equal Set.new(['name', 'address']), TightPerson.accessible_attributes - - assert_blank TightPerson.protected_attributes(:admin) - TightPerson.attributes_protected_by_default - assert_equal Set.new(['name', 'address', 'admin']), TightPerson.accessible_attributes(:admin) - - assert_blank TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default - assert_equal Set.new(['name', 'address', 'phone_number']), TightDescendant.accessible_attributes - - assert_blank TightDescendant.protected_attributes(:admin) - TightDescendant.attributes_protected_by_default - assert_equal Set.new(['name', 'address', 'admin', 'super_powers']), TightDescendant.accessible_attributes(:admin) - end - - def test_mass_assignment_multiparameter_protector - task = Task.new - attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" } - sanitized = task.sanitize_for_mass_assignment(attributes) - assert_equal sanitized, { } - end - - def test_custom_sanitizer - old_sanitizer = User._mass_assignment_sanitizer - - user = User.new - User.mass_assignment_sanitizer = CustomSanitizer.new - assert_raise StandardError do - user.sanitize_for_mass_assignment("admin" => true) - end - ensure - User.mass_assignment_sanitizer = old_sanitizer - end -end
\ No newline at end of file diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 8650b0e495..19e74d3cc9 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -54,18 +54,6 @@ class SecurePasswordTest < ActiveModel::TestCase assert @user.authenticate("secret") end - test "visitor#password_digest should be protected against mass assignment" do - assert Visitor.active_authorizers[:default].kind_of?(ActiveModel::MassAssignmentSecurity::BlackList) - assert Visitor.active_authorizers[:default].include?(:password_digest) - end - - test "Administrator's mass_assignment_authorizer should be WhiteList" do - active_authorizer = Administrator.active_authorizers[:default] - assert active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::WhiteList) - assert !active_authorizer.include?(:password_digest) - assert active_authorizer.include?(:name) - end - test "User should not be created with blank digest" do assert_raise RuntimeError do @user.run_callbacks :create diff --git a/activemodel/test/models/account.rb b/activemodel/test/models/account.rb new file mode 100644 index 0000000000..eed668d38f --- /dev/null +++ b/activemodel/test/models/account.rb @@ -0,0 +1,5 @@ +class Account + include ActiveModel::ForbiddenAttributesProtection + + public :sanitize_for_mass_assignment +end diff --git a/activemodel/test/models/administrator.rb b/activemodel/test/models/administrator.rb index 2d6d34b3e2..2f3aff290c 100644 --- a/activemodel/test/models/administrator.rb +++ b/activemodel/test/models/administrator.rb @@ -2,12 +2,10 @@ class Administrator extend ActiveModel::Callbacks include ActiveModel::Validations include ActiveModel::SecurePassword - include ActiveModel::MassAssignmentSecurity - + define_model_callbacks :create attr_accessor :name, :password_digest - attr_accessible :name has_secure_password end diff --git a/activemodel/test/models/mass_assignment_specific.rb b/activemodel/test/models/mass_assignment_specific.rb deleted file mode 100644 index 1d123fa58c..0000000000 --- a/activemodel/test/models/mass_assignment_specific.rb +++ /dev/null @@ -1,76 +0,0 @@ -class User - include ActiveModel::MassAssignmentSecurity - attr_protected :admin - - public :sanitize_for_mass_assignment -end - -class SpecialUser - include ActiveModel::MassAssignmentSecurity - attr_accessible :name, :email, :as => :moderator - - public :sanitize_for_mass_assignment -end - -class Person - include ActiveModel::MassAssignmentSecurity - attr_accessible :name, :email - attr_accessible :name, :email, :admin, :as => :admin - - public :sanitize_for_mass_assignment -end - -class Account - include ActiveModel::MassAssignmentSecurity - attr_accessible :name, :email, :as => [:default, :admin] - attr_accessible :admin, :as => :admin - - public :sanitize_for_mass_assignment -end - -class Firm - include ActiveModel::MassAssignmentSecurity - - public :sanitize_for_mass_assignment - - def self.attributes_protected_by_default - ["type"] - end -end - -class Task - include ActiveModel::MassAssignmentSecurity - attr_protected :starting - - public :sanitize_for_mass_assignment -end - -class LoosePerson - include ActiveModel::MassAssignmentSecurity - attr_protected :credit_rating, :administrator - attr_protected :credit_rating, :as => :admin -end - -class LooseDescendant < LoosePerson - attr_protected :phone_number -end - -class LooseDescendantSecond< LoosePerson - attr_protected :phone_number - attr_protected :name -end - -class TightPerson - include ActiveModel::MassAssignmentSecurity - attr_accessible :name, :address - attr_accessible :name, :address, :admin, :as => :admin - - def self.attributes_protected_by_default - ["mobile_number"] - end -end - -class TightDescendant < TightPerson - attr_accessible :phone_number - attr_accessible :super_powers, :as => :admin -end diff --git a/activemodel/test/models/project.rb b/activemodel/test/models/project.rb new file mode 100644 index 0000000000..581b6dc0b3 --- /dev/null +++ b/activemodel/test/models/project.rb @@ -0,0 +1,3 @@ +class Project + include ActiveModel::DeprecatedMassAssignmentSecurity +end diff --git a/activemodel/test/models/visitor.rb b/activemodel/test/models/visitor.rb index d15f448516..4d7f4be097 100644 --- a/activemodel/test/models/visitor.rb +++ b/activemodel/test/models/visitor.rb @@ -2,8 +2,7 @@ class Visitor extend ActiveModel::Callbacks include ActiveModel::Validations include ActiveModel::SecurePassword - include ActiveModel::MassAssignmentSecurity - + define_model_callbacks :create has_secure_password(validations: false) |