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 | |
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
74 files changed, 662 insertions, 2232 deletions
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 8c1f548e47..1a13d7af29 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -2,6 +2,7 @@ require 'active_support/rails' require 'abstract_controller' require 'action_dispatch' require 'action_controller/metal/live' +require 'action_controller/metal/strong_parameters' module ActionController extend ActiveSupport::Autoload @@ -34,6 +35,7 @@ module ActionController autoload :Rescue autoload :Responder autoload :Streaming + autoload :StrongParameters autoload :Testing autoload :UrlFor end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 84e8c2a698..6b8d9384d4 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -214,6 +214,7 @@ module ActionController Caching, MimeResponds, ImplicitRender, + StrongParameters, Cookies, Flash, diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 2736948ce0..bbb711b49d 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -42,9 +42,7 @@ module ActionController # end # # On ActiveRecord models with no +:include+ or +:exclude+ option set, - # if attr_accessible is set on that model, it will only wrap the accessible - # parameters, else it will only wrap the parameters returned by the class - # method attribute_names. + # it will only wrap the parameters returned by the class method attribute_names. # # If you're going to pass the parameters to an +ActiveModel+ object (such as # <tt>User.new(params[:user])</tt>), you might consider passing the model class to @@ -165,10 +163,7 @@ module ActionController unless options[:include] || options[:exclude] model ||= _default_wrap_model - role = options.fetch(:as, :default) - if model.respond_to?(:accessible_attributes) && model.accessible_attributes(role).present? - options[:include] = model.accessible_attributes(role).to_a - elsif model.respond_to?(:attribute_names) && model.attribute_names.present? + if model.respond_to?(:attribute_names) && model.attribute_names.present? options[:include] = model.attribute_names end end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb new file mode 100644 index 0000000000..c3c1921706 --- /dev/null +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -0,0 +1,125 @@ +require 'active_support/concern' +require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/rescuable' + +module ActionController + class ParameterMissing < KeyError + attr_reader :param + + def initialize(param) + @param = param + super("key not found: #{param}") + end + end + + class Parameters < ActiveSupport::HashWithIndifferentAccess + cattr_accessor :permit_all_parameters, instance_accessor: false + attr_accessor :permitted + alias :permitted? :permitted + + def initialize(attributes = nil) + super(attributes) + @permitted = self.class.permit_all_parameters + end + + def permit! + @permitted = true + self + end + + def require(key) + self[key].presence || raise(ParameterMissing.new(key)) + end + + alias :required :require + + def permit(*filters) + params = self.class.new + + filters.each do |filter| + case filter + when Symbol, String then + params[filter] = self[filter] if has_key?(filter) + when Hash then + self.slice(*filter.keys).each do |key, value| + return unless value + + key = key.to_sym + + params[key] = each_element(value) do |value| + # filters are a Hash, so we expect value to be a Hash too + next if filter.is_a?(Hash) && !value.is_a?(Hash) + + value = self.class.new(value) if !value.respond_to?(:permit) + + value.permit(*Array.wrap(filter[key])) + end + end + end + end + + params.permit! + end + + def [](key) + convert_hashes_to_parameters(key, super) + end + + def fetch(key, *args) + convert_hashes_to_parameters(key, super) + rescue KeyError + raise ActionController::ParameterMissing.new(key) + end + + def slice(*keys) + self.class.new(super) + end + + def dup + super.tap do |duplicate| + duplicate.instance_variable_set :@permitted, @permitted + end + end + + private + def convert_hashes_to_parameters(key, value) + if value.is_a?(Parameters) || !value.is_a?(Hash) + value + else + # Convert to Parameters on first access + self[key] = self.class.new(value) + end + end + + def each_element(object) + if object.is_a?(Array) + object.map { |el| yield el }.compact + elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ } + hash = object.class.new + object.each { |k,v| hash[k] = yield v } + hash + else + yield object + end + end + end + + module StrongParameters + extend ActiveSupport::Concern + include ActiveSupport::Rescuable + + included do + rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception| + render text: "Required parameter missing: #{parameter_missing_exception.param}", status: :bad_request + end + end + + def params + @_params ||= Parameters.new(request.parameters) + end + + def params=(val) + @_params = val.is_a?(Hash) ? Parameters.new(val) : val + end + end +end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 3ecc105e22..d7e8194bf6 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -19,6 +19,10 @@ module ActionController ActionController::Helpers.helpers_path = app.helpers_paths end + initializer "action_controller.parameters_config" do |app| + ActionController::Parameters.permit_all_parameters = app.config.action_controller.delete(:permit_all_parameters) + end + initializer "action_controller.set_configs" do |app| paths = app.config.paths options = app.config.action_controller diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb new file mode 100644 index 0000000000..ea67126cc5 --- /dev/null +++ b/actionpack/test/controller/parameters/nested_parameters_test.rb @@ -0,0 +1,113 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' + +class NestedParametersTest < ActiveSupport::TestCase + test "permitted nested parameters" do + params = ActionController::Parameters.new({ + book: { + title: "Romeo and Juliet", + authors: [{ + name: "William Shakespeare", + born: "1564-04-26" + }, { + name: "Christopher Marlowe" + }], + details: { + pages: 200, + genre: "Tragedy" + } + }, + magazine: "Mjallo!" + }) + + permitted = params.permit book: [ :title, { authors: [ :name ] }, { details: :pages } ] + + assert permitted.permitted? + assert_equal "Romeo and Juliet", permitted[:book][:title] + assert_equal "William Shakespeare", permitted[:book][:authors][0][:name] + assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name] + assert_equal 200, permitted[:book][:details][:pages] + assert_nil permitted[:book][:details][:genre] + assert_nil permitted[:book][:authors][1][:born] + assert_nil permitted[:magazine] + end + + test "nested arrays with strings" do + params = ActionController::Parameters.new({ + :book => { + :genres => ["Tragedy"] + } + }) + + permitted = params.permit :book => :genres + assert_equal ["Tragedy"], permitted[:book][:genres] + end + + test "permit may specify symbols or strings" do + params = ActionController::Parameters.new({ + :book => { + :title => "Romeo and Juliet", + :author => "William Shakespeare" + }, + :magazine => "Shakespeare Today" + }) + + permitted = params.permit({:book => ["title", :author]}, "magazine") + assert_equal "Romeo and Juliet", permitted[:book][:title] + assert_equal "William Shakespeare", permitted[:book][:author] + assert_equal "Shakespeare Today", permitted[:magazine] + end + + test "nested array with strings that should be hashes" do + params = ActionController::Parameters.new({ + book: { + genres: ["Tragedy"] + } + }) + + permitted = params.permit book: { genres: :type } + assert_empty permitted[:book][:genres] + end + + test "nested array with strings that should be hashes and additional values" do + params = ActionController::Parameters.new({ + book: { + title: "Romeo and Juliet", + genres: ["Tragedy"] + } + }) + + permitted = params.permit book: [ :title, { genres: :type } ] + assert_equal "Romeo and Juliet", permitted[:book][:title] + assert_empty permitted[:book][:genres] + end + + test "nested string that should be a hash" do + params = ActionController::Parameters.new({ + book: { + genre: "Tragedy" + } + }) + + permitted = params.permit book: { genre: :type } + assert_nil permitted[:book][:genre] + end + + test "fields_for-style nested params" do + params = ActionController::Parameters.new({ + book: { + authors_attributes: { + :'0' => { name: 'William Shakespeare', age_of_death: '52' }, + :'-1' => { name: 'Unattributed Assistant' } + } + } + }) + permitted = params.permit book: { authors_attributes: [ :name ] } + + assert_not_nil permitted[:book][:authors_attributes]['0'] + assert_not_nil permitted[:book][:authors_attributes]['-1'] + assert_nil permitted[:book][:authors_attributes]['0'][:age_of_death] + assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][:name] + assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-1'][:name] + end +end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb new file mode 100644 index 0000000000..7fe8e6051b --- /dev/null +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -0,0 +1,73 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' + +class ParametersPermitTest < ActiveSupport::TestCase + setup do + @params = ActionController::Parameters.new({ person: { + age: "32", name: { first: "David", last: "Heinemeier Hansson" } + }}) + end + + test "fetch raises ParameterMissing exception" do + e = assert_raises(ActionController::ParameterMissing) do + @params.fetch :foo + end + assert_equal :foo, e.param + end + + test "fetch doesnt raise ParameterMissing exception if there is a default" do + assert_equal "monkey", @params.fetch(:foo, "monkey") + assert_equal "monkey", @params.fetch(:foo) { "monkey" } + end + + test "permitted is sticky on accessors" do + assert !@params.slice(:person).permitted? + assert !@params[:person][:name].permitted? + + @params.each { |key, value| assert(value.permitted?) if key == :person } + + assert !@params.fetch(:person).permitted? + + assert !@params.values_at(:person).first.permitted? + end + + test "permitted is sticky on mutators" do + assert !@params.delete_if { |k| k == :person }.permitted? + assert !@params.keep_if { |k,v| k == :person }.permitted? + end + + test "permitted is sticky beyond merges" do + assert !@params.merge(a: "b").permitted? + end + + test "modifying the parameters" do + @params[:person][:hometown] = "Chicago" + @params[:person][:family] = { brother: "Jonas" } + + assert_equal "Chicago", @params[:person][:hometown] + assert_equal "Jonas", @params[:person][:family][:brother] + end + + test "permitting parameters that are not there should not include the keys" do + assert !@params.permit(:person, :funky).has_key?(:funky) + end + + test "permit state is kept on a dup" do + @params.permit! + assert_equal @params.permitted?, @params.dup.permitted? + end + + test "permitted takes a default value when Parameters.permit_all_parameters is set" do + begin + ActionController::Parameters.permit_all_parameters = true + params = ActionController::Parameters.new({ person: { + age: "32", name: { first: "David", last: "Heinemeier Hansson" } + }}) + + assert params.slice(:person).permitted? + assert params[:person][:name].permitted? + ensure + ActionController::Parameters.permit_all_parameters = false + end + end +end diff --git a/actionpack/test/controller/parameters/parameters_require_test.rb b/actionpack/test/controller/parameters/parameters_require_test.rb new file mode 100644 index 0000000000..bdaba8d2d8 --- /dev/null +++ b/actionpack/test/controller/parameters/parameters_require_test.rb @@ -0,0 +1,10 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' + +class ParametersRequireTest < ActiveSupport::TestCase + test "required parameters must be present not merely not nil" do + assert_raises(ActionController::ParameterMissing) do + ActionController::Parameters.new(person: {}).require(:person) + end + end +end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 5b05f77045..209f021cf7 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -155,7 +155,6 @@ class ParamsWrapperTest < ActionController::TestCase end def test_derived_wrapped_keys_from_matching_model - User.expects(:respond_to?).with(:accessible_attributes).returns(false) User.expects(:respond_to?).with(:attribute_names).returns(true) User.expects(:attribute_names).twice.returns(["username"]) @@ -168,7 +167,6 @@ class ParamsWrapperTest < ActionController::TestCase def test_derived_wrapped_keys_from_specified_model with_default_wrapper_options do - Person.expects(:respond_to?).with(:accessible_attributes).returns(false) Person.expects(:respond_to?).with(:attribute_names).returns(true) Person.expects(:attribute_names).twice.returns(["username"]) @@ -179,46 +177,8 @@ class ParamsWrapperTest < ActionController::TestCase assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) end end - - def test_accessible_wrapped_keys_from_matching_model - User.expects(:respond_to?).with(:accessible_attributes).returns(true) - User.expects(:accessible_attributes).with(:default).twice.returns(["username"]) - - with_default_wrapper_options do - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) - end - end - - def test_accessible_wrapped_keys_from_specified_model - with_default_wrapper_options do - Person.expects(:respond_to?).with(:accessible_attributes).returns(true) - Person.expects(:accessible_attributes).with(:default).twice.returns(["username"]) - - UsersController.wrap_parameters Person - - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) - end - end - - def test_accessible_wrapped_keys_with_role_from_specified_model - with_default_wrapper_options do - Person.expects(:respond_to?).with(:accessible_attributes).returns(true) - Person.expects(:accessible_attributes).with(:admin).twice.returns(["username"]) - - UsersController.wrap_parameters Person, :as => :admin - - @request.env['CONTENT_TYPE'] = 'application/json' - post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) - end - end def test_not_wrapping_abstract_model - User.expects(:respond_to?).with(:accessible_attributes).returns(false) User.expects(:respond_to?).with(:attribute_names).returns(true) User.expects(:attribute_names).returns([]) diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb new file mode 100644 index 0000000000..f46249d712 --- /dev/null +++ b/actionpack/test/controller/permitted_params_test.rb @@ -0,0 +1,25 @@ +require 'abstract_unit' + +class PeopleController < ActionController::Base + def create + render text: params[:person].permitted? ? "permitted" : "forbidden" + end + + def create_with_permit + render text: params[:person].permit(:name).permitted? ? "permitted" : "forbidden" + end +end + +class ActionControllerPermittedParamsTest < ActionController::TestCase + tests PeopleController + + test "parameters are forbidden" do + post :create, { person: { name: "Mjallo!" } } + assert_equal "forbidden", response.body + end + + test "parameters can be permitted and are then not forbidden" do + post :create_with_permit, { person: { name: "Mjallo!" } } + assert_equal "permitted", response.body + end +end diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb new file mode 100644 index 0000000000..661bcb3945 --- /dev/null +++ b/actionpack/test/controller/required_params_test.rb @@ -0,0 +1,30 @@ +require 'abstract_unit' + +class BooksController < ActionController::Base + def create + params.require(:book).require(:name) + head :ok + end +end + +class ActionControllerRequiredParamsTest < ActionController::TestCase + tests BooksController + + test "missing required parameters will raise exception" do + post :create, { magazine: { name: "Mjallo!" } } + assert_response :bad_request + + post :create, { book: { title: "Mjallo!" } } + assert_response :bad_request + end + + test "required parameters that are present will not raise" do + post :create, { book: { name: "Mjallo!" } } + assert_response :ok + end + + test "missing parameters will be mentioned in the return" do + post :create, { magazine: { name: "Mjallo!" } } + assert_equal "Required parameter missing: book", response.body + end +end diff --git a/actionpack/test/fixtures/company.rb b/actionpack/test/fixtures/company.rb index e29978801e..f3ac3642fa 100644 --- a/actionpack/test/fixtures/company.rb +++ b/actionpack/test/fixtures/company.rb @@ -1,6 +1,5 @@ class Company < ActiveRecord::Base has_one :mascot - attr_protected :rating self.sequence_name = :companies_nonstd_seq validates_presence_of :name 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) diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 9f47e7e631..495f0cde59 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -233,10 +233,10 @@ module ActiveRecord def stale_state end - def build_record(attributes, options) - reflection.build_association(attributes, options) do |record| + def build_record(attributes) + reflection.build_association(attributes) do |record| attributes = create_scope.except(*(record.changed - [reflection.foreign_key])) - record.assign_attributes(attributes, :without_protection => true) + record.assign_attributes(attributes) end end end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 424c1a5b79..fe3e5b00f7 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -95,22 +95,22 @@ module ActiveRecord first_or_last(:last, *args) end - def build(attributes = {}, options = {}, &block) + def build(attributes = {}, &block) if attributes.is_a?(Array) - attributes.collect { |attr| build(attr, options, &block) } + attributes.collect { |attr| build(attr, &block) } else - add_to_target(build_record(attributes, options)) do |record| + add_to_target(build_record(attributes)) do |record| yield(record) if block_given? end end end - def create(attributes = {}, options = {}, &block) - create_record(attributes, options, &block) + def create(attributes = {}, &block) + create_record(attributes, &block) end - def create!(attributes = {}, options = {}, &block) - create_record(attributes, options, true, &block) + def create!(attributes = {}, &block) + create_record(attributes, true, &block) end # Add +records+ to this association. Returns +self+ so method calls may @@ -425,16 +425,16 @@ module ActiveRecord persisted + memory end - def create_record(attributes, options, raise = false, &block) + def create_record(attributes, raise = false, &block) unless owner.persisted? raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end if attributes.is_a?(Array) - attributes.collect { |attr| create_record(attr, options, raise, &block) } + attributes.collect { |attr| create_record(attr, raise, &block) } else transaction do - add_to_target(build_record(attributes, options)) do |record| + add_to_target(build_record(attributes)) do |record| yield(record) if block_given? insert_record(record, true, raise) end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 66132b7260..c113957faa 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -222,8 +222,8 @@ module ActiveRecord # # person.pets.size # => 5 # size of the collection # person.pets.count # => 0 # count from database - def build(attributes = {}, options = {}, &block) - @association.build(attributes, options, &block) + def build(attributes = {}, &block) + @association.build(attributes, &block) end ## @@ -253,8 +253,8 @@ module ActiveRecord # # #<Pet id: 2, name: "Spook", person_id: 1>, # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> # # ] - def create(attributes = {}, options = {}, &block) - @association.create(attributes, options, &block) + def create(attributes = {}, &block) + @association.create(attributes, &block) end ## @@ -265,14 +265,13 @@ module ActiveRecord # end # # class Pet - # attr_accessible :name # validates :name, presence: true # end # # person.pets.create!(name: nil) # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank - def create!(attributes = {}, options = {}, &block) - @association.create!(attributes, options, &block) + def create!(attributes = {}, &block) + @association.create!(attributes, &block) end ## diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index d8f14c896c..c7d8a84a7e 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -96,10 +96,10 @@ module ActiveRecord @through_records.delete(record.object_id) end - def build_record(attributes, options = {}) + def build_record(attributes) ensure_not_nested - record = super(attributes, options) + record = super(attributes) inverse = source_reflection.inverse_of if inverse diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index b84cb4922d..32f4557c28 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -17,16 +17,16 @@ module ActiveRecord replace(record) end - def create(attributes = {}, options = {}, &block) - create_record(attributes, options, &block) + def create(attributes = {}, &block) + create_record(attributes, &block) end - def create!(attributes = {}, options = {}, &block) - create_record(attributes, options, true, &block) + def create!(attributes = {}, &block) + create_record(attributes, true, &block) end - def build(attributes = {}, options = {}) - record = build_record(attributes, options) + def build(attributes = {}) + record = build_record(attributes) yield(record) if block_given? set_new_record(record) record @@ -51,8 +51,8 @@ module ActiveRecord replace(record) end - def create_record(attributes, options, raise_error = false) - record = build_record(attributes, options) + def create_record(attributes, raise_error = false) + record = build_record(attributes) yield(record) if block_given? saved = record.save set_new_record(record) diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index d9989274c8..ccc61ec5d3 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -1,98 +1,32 @@ module ActiveRecord - ActiveSupport.on_load(:active_record_config) do - mattr_accessor :whitelist_attributes, instance_accessor: false - mattr_accessor :mass_assignment_sanitizer, instance_accessor: false - end - module AttributeAssignment extend ActiveSupport::Concern - include ActiveModel::MassAssignmentSecurity - - included do - initialize_mass_assignment_sanitizer - end - - module ClassMethods - def inherited(child) # :nodoc: - child.send :initialize_mass_assignment_sanitizer if self == Base - super - end - - private - - # The primary key and inheritance column can never be set by mass-assignment for security reasons. - def attributes_protected_by_default - default = [ primary_key, inheritance_column ] - default << 'id' unless primary_key.eql? 'id' - default - end - - def initialize_mass_assignment_sanitizer - attr_accessible(nil) if Model.whitelist_attributes - self.mass_assignment_sanitizer = Model.mass_assignment_sanitizer if Model.mass_assignment_sanitizer - end - end + include ActiveModel::DeprecatedMassAssignmentSecurity + include ActiveModel::ForbiddenAttributesProtection # Allows you to set all the attributes at once by passing in a hash with keys # matching the attribute names (which again matches the column names). # - # If any attributes are protected by either +attr_protected+ or - # +attr_accessible+ then only settable attributes will be assigned. - # - # class User < ActiveRecord::Base - # attr_protected :is_admin - # end - # - # user = User.new - # user.attributes = { :username => 'Phusion', :is_admin => true } - # user.username # => "Phusion" - # user.is_admin? # => false + # If the passed hash responds to permitted? method and the return value + # of this method is false an ActiveModel::ForbiddenAttributesError exception + # is raised. def attributes=(new_attributes) return unless new_attributes.is_a?(Hash) assign_attributes(new_attributes) end - # Allows you to set all the attributes for a particular mass-assignment - # security role by passing in a hash of attributes with keys matching - # the attribute names (which again matches the column names) and the role - # name using the :as option. - # - # To bypass mass-assignment security you can use the :without_protection => true - # option. - # - # class User < ActiveRecord::Base - # attr_accessible :name - # attr_accessible :name, :is_admin, :as => :admin - # end - # - # user = User.new - # user.assign_attributes({ :name => 'Josh', :is_admin => true }) - # user.name # => "Josh" - # user.is_admin? # => false - # - # user = User.new - # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin) - # user.name # => "Josh" - # user.is_admin? # => true - # - # user = User.new - # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true) - # user.name # => "Josh" - # user.is_admin? # => true - def assign_attributes(new_attributes, options = {}) + # Allows you to set all the attributes by passing in a hash of attributes with + # keys matching the attribute names (which again matches the column names) + def assign_attributes(new_attributes) return if new_attributes.blank? attributes = new_attributes.stringify_keys multi_parameter_attributes = [] nested_parameter_attributes = [] - previous_options = @mass_assignment_options - @mass_assignment_options = options - unless options[:without_protection] - attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role) - end + attributes = sanitize_for_mass_assignment(attributes) attributes.each do |k, v| if k.include?("(") @@ -106,18 +40,6 @@ module ActiveRecord assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty? assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? - ensure - @mass_assignment_options = previous_options - end - - protected - - def mass_assignment_options - @mass_assignment_options ||= {} - end - - def mass_assignment_role - mass_assignment_options[:as] || :default end private diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 7b7811a706..aa6704d5c9 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -18,7 +18,7 @@ module ActiveRecord # Sets the primary key value def id=(value) - write_attribute(self.class.primary_key, value) + write_attribute(self.class.primary_key, value) if self.class.primary_key end # Queries the primary key value @@ -53,8 +53,7 @@ module ActiveRecord end # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the - # primary_key_prefix_type setting, though. Since primary keys are usually protected from mass assignment, - # remember to let your database generate them or include the key in +attr_accessible+. + # primary_key_prefix_type setting, though. def primary_key @primary_key = reset_primary_key unless defined? @primary_key @primary_key diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 43ffca0227..8f798830d4 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -172,7 +172,7 @@ module ActiveRecord # # # Instantiates a single new object bypassing mass-assignment security # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) - def initialize(attributes = nil, options = {}) + def initialize(attributes = nil) defaults = self.class.column_defaults.dup defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? } @@ -183,7 +183,7 @@ module ActiveRecord ensure_proper_type populate_with_current_scope_attributes - assign_attributes(attributes, options) if attributes + assign_attributes(attributes) if attributes yield self if block_given? run_callbacks :initialize unless _initialize_callbacks.empty? @@ -386,7 +386,7 @@ module ActiveRecord @destroyed = false @marked_for_destruction = false @new_record = true - @mass_assignment_options = nil + @txn = nil @_start_transaction_state = {} end end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 3005dc042c..a33285b16f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -194,18 +194,6 @@ module ActiveRecord # the parent model is saved. This happens inside the transaction initiated # by the parents save method. See ActiveRecord::AutosaveAssociation. # - # === Using with attr_accessible - # - # The use of <tt>attr_accessible</tt> can interfere with nested attributes - # if you're not careful. For example, if the <tt>Member</tt> model above - # was using <tt>attr_accessible</tt> like this: - # - # attr_accessible :name - # - # You would need to modify it to look like this: - # - # attr_accessible :name, :posts_attributes - # # === Validating the presence of a parent model # # If you want to validate that a child record is associated with a parent @@ -224,9 +212,7 @@ module ActiveRecord module ClassMethods REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } } - # Defines an attributes writer for the specified association(s). If you - # are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you - # will need to add the attribute writer to the allowed list. + # Defines an attributes writer for the specified association(s). # # Supported options: # [:allow_destroy] @@ -296,7 +282,7 @@ module ActiveRecord remove_method(:#{association_name}_attributes=) end def #{association_name}_attributes=(attributes) - assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) end eoruby else @@ -340,15 +326,15 @@ module ActiveRecord if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) && (options[:update_only] || record.id.to_s == attributes['id'].to_s) - assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes) + assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) - elsif attributes['id'].present? && !assignment_opts[:without_protection] + elsif attributes['id'].present? raise_nested_attributes_record_not_found(association_name, attributes['id']) elsif !reject_new_record?(association_name, attributes) method = "build_#{association_name}" if respond_to?(method) - send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) + send(method, attributes.except(*UNASSIGNABLE_KEYS)) else raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" end @@ -382,7 +368,7 @@ module ActiveRecord # { :name => 'John' }, # { :id => '2', :_destroy => true } # ]) - def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {}) + def assign_nested_attributes_for_collection_association(association_name, attributes_collection) options = self.nested_attributes_options[association_name] unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) @@ -427,7 +413,7 @@ module ActiveRecord if attributes['id'].blank? unless reject_new_record?(association_name, attributes) - association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) + association.build(attributes.except(*UNASSIGNABLE_KEYS)) end elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } unless association.loaded? || call_reject_if(association_name, attributes) @@ -443,10 +429,8 @@ module ActiveRecord end if !call_reject_if(association_name, attributes) - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) end - elsif assignment_opts[:without_protection] - association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) else raise_nested_attributes_record_not_found(association_name, attributes['id']) end @@ -455,8 +439,8 @@ module ActiveRecord # Updates a record with the +attributes+ or marks it for destruction if # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. - def assign_to_or_mark_for_destruction(record, attributes, allow_destroy, assignment_opts) - record.assign_attributes(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) + def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) + record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS)) record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy end @@ -487,7 +471,7 @@ module ActiveRecord end def unassignable_keys(assignment_opts) - assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS + UNASSIGNABLE_KEYS end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 7bd65c180d..6f38262b86 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -35,11 +35,11 @@ module ActiveRecord # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u| # u.is_admin = false # end - def create(attributes = nil, options = {}, &block) + def create(attributes = nil, &block) if attributes.is_a?(Array) - attributes.collect { |attr| create(attr, options, &block) } + attributes.collect { |attr| create(attr, &block) } else - object = new(attributes, options, &block) + object = new(attributes, &block) object.save object end @@ -186,24 +186,24 @@ module ActiveRecord # # When updating model attributes, mass-assignment security protection is respected. # If no +:as+ option is supplied then the +:default+ role will be used. - # If you want to bypass the protection given by +attr_protected+ and - # +attr_accessible+ then you can do so using the +:without_protection+ option. - def update_attributes(attributes, options = {}) + # If you want to bypass the forbidden attributes protection then you can do so using + # the +:without_protection+ option. + def update_attributes(attributes) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do - assign_attributes(attributes, options) + assign_attributes(attributes) save end end # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead # of +save+, so an exception is raised if the record is invalid. - def update_attributes!(attributes, options = {}) + def update_attributes!(attributes) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do - assign_attributes(attributes, options) + assign_attributes(attributes) save! end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index d9c65fa170..f322b96f79 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -181,8 +181,8 @@ module ActiveRecord # Returns a new, unsaved instance of the associated class. +options+ will # be passed to the class's constructor. - def build_association(*options, &block) - klass.new(*options, &block) + def build_association(attributes, &block) + klass.new(attributes, &block) end def table_name diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 2d0457636e..ed80422336 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -151,22 +151,22 @@ module ActiveRecord # user.last_name = "O'Hara" # end # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'> - def first_or_create(attributes = nil, options = {}, &block) - first || create(attributes, options, &block) + def first_or_create(attributes = nil, &block) + first || create(attributes, &block) end # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid. # # Expects arguments in the same format as <tt>Base.create!</tt>. - def first_or_create!(attributes = nil, options = {}, &block) - first || create!(attributes, options, &block) + def first_or_create!(attributes = nil, &block) + first || create!(attributes, &block) end # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>. # # Expects arguments in the same format as <tt>Base.new</tt>. - def first_or_initialize(attributes = nil, options = {}, &block) - first || new(attributes, options, &block) + def first_or_initialize(attributes = nil, &block) + first || new(attributes, &block) end # Runs EXPLAIN on the query or queries triggered by this relation and diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index ca22154c84..9830abe7d8 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -4,7 +4,6 @@ require 'active_record/base' module ActiveRecord class SchemaMigration < ActiveRecord::Base - attr_accessible :version def self.table_name "#{Base.table_name_prefix}schema_migrations#{Base.table_name_suffix}" diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index cef2bbd563..ed561bfb3c 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -32,11 +32,11 @@ module ActiveRecord module ClassMethods # Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+ # so an exception is raised if the record is invalid. - def create!(attributes = nil, options = {}, &block) + def create!(attributes = nil, &block) if attributes.is_a?(Array) - attributes.collect { |attr| create!(attr, options, &block) } + attributes.collect { |attr| create!(attr, &block) } else - object = new(attributes, options) + object = new(attributes) yield(object) if block_given? object.save! object diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb index 2cca17b94f..056f55470c 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb +++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb @@ -3,10 +3,5 @@ class <%= class_name %> < <%= parent_class_name.classify %> <% attributes.select {|attr| attr.reference? }.each do |attribute| -%> belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %> <% end -%> -<% if !accessible_attributes.empty? -%> - attr_accessible <%= accessible_attributes.map {|a| ":#{a.name}" }.sort.join(', ') %> -<% else -%> - # attr_accessible :title, :body -<% end -%> end <% end -%> diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 4405f34355..4b56037a08 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -188,28 +188,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal invoice.id, line_item.invoice_id end - def test_association_conditions_bypass_attribute_protection - car = Car.create(:name => 'honda') - - bulb = car.frickinawesome_bulbs.new - assert_equal true, bulb.frickinawesome? - - bulb = car.frickinawesome_bulbs.new(:frickinawesome => false) - assert_equal true, bulb.frickinawesome? - - bulb = car.frickinawesome_bulbs.build - assert_equal true, bulb.frickinawesome? - - bulb = car.frickinawesome_bulbs.build(:frickinawesome => false) - assert_equal true, bulb.frickinawesome? - - bulb = car.frickinawesome_bulbs.create - assert_equal true, bulb.frickinawesome? - - bulb = car.frickinawesome_bulbs.create(:frickinawesome => false) - assert_equal true, bulb.frickinawesome? - end - # When creating objects on the association, we must not do it within a scope (even though it # would be convenient), because this would cause that scope to be applied to any callbacks etc. def test_build_and_create_should_not_happen_within_scope @@ -1580,19 +1558,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "RED!", car.bulbs.to_a.first.color end - def test_new_is_called_with_attributes_and_options - car = Car.create(:name => 'honda') - - bulb = car.bulbs.build - assert_equal Bulb, bulb.class - - bulb = car.bulbs.build(:bulb_type => :custom) - assert_equal Bulb, bulb.class - - bulb = car.bulbs.build({ :bulb_type => :custom }, :as => :admin) - assert_equal CustomBulb, bulb.class - end - def test_abstract_class_with_polymorphic_has_many post = SubStiPost.create! :title => "fooo", :body => "baa" tagging = Tagging.create! :taggable => post diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index c591fd8532..d4ceae6f80 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -58,21 +58,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert post.reload.people(true).include?(person) end - def test_associate_existing_with_strict_mass_assignment_sanitizer - SecureReader.mass_assignment_sanitizer = :strict - - SecureReader.new - - post = posts(:thinking) - person = people(:david) - - assert_queries(1) do - post.secure_people << person - end - ensure - SecureReader.mass_assignment_sanitizer = :logger - end - def test_associate_existing_record_twice_should_add_to_target_twice post = posts(:thinking) person = people(:david) diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 8bc633f2b5..2d3cb654df 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -446,38 +446,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal pirate.id, ship.pirate_id end - def test_association_conditions_bypass_attribute_protection - car = Car.create(:name => 'honda') - - bulb = car.build_frickinawesome_bulb - assert_equal true, bulb.frickinawesome? - - bulb = car.build_frickinawesome_bulb(:frickinawesome => false) - assert_equal true, bulb.frickinawesome? - - bulb = car.create_frickinawesome_bulb - assert_equal true, bulb.frickinawesome? - - bulb = car.create_frickinawesome_bulb(:frickinawesome => false) - assert_equal true, bulb.frickinawesome? - end - - def test_new_is_called_with_attributes_and_options - car = Car.create(:name => 'honda') - - bulb = car.build_bulb - assert_equal Bulb, bulb.class - - bulb = car.build_bulb - assert_equal Bulb, bulb.class - - bulb = car.build_bulb(:bulb_type => :custom) - assert_equal Bulb, bulb.class - - bulb = car.build_bulb({ :bulb_type => :custom }, :as => :admin) - assert_equal CustomBulb, bulb.class - end - def test_build_with_block car = Car.create(:name => 'honda') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index b25d169038..fbfdd0f07a 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -55,10 +55,6 @@ class ReadonlyTitlePost < Post attr_readonly :title end -class ProtectedTitlePost < Post - attr_protected :title -end - class Weird < ActiveRecord::Base; end class Boolean < ActiveRecord::Base diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb index 392f5f4cd5..dde36e7f72 100644 --- a/activerecord/test/cases/deprecated_dynamic_methods_test.rb +++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb @@ -199,23 +199,7 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase assert !new_customer.persisted? end - def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected - c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000}) - assert_equal "Fortune 1000", c.name - assert_not_equal 1000, c.rating - assert c.valid? - assert !c.persisted? - end - - def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected - c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000}) - assert_equal "Fortune 1000", c.name - assert_not_equal 1000, c.rating - assert c.valid? - assert c.persisted? - end - - def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected + def test_find_or_initialize_from_one_attribute_should_set_attribute c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000) assert_equal "Fortune 1000", c.name assert_equal 1000, c.rating @@ -223,7 +207,7 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase assert !c.persisted? end - def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected + def test_find_or_create_from_one_attribute_should_set_attribute c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000) assert_equal "Fortune 1000", c.name assert_equal 1000, c.rating @@ -231,7 +215,7 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase assert c.persisted? end - def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash + def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_set_the_hash c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"}) assert_equal "Fortune 1000", c.name assert_equal 1000, c.rating @@ -239,7 +223,7 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase assert !c.persisted? end - def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash + def test_find_or_create_from_one_attribute_should_set_attribute_even_when_set_the_hash c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"}) assert_equal "Fortune 1000", c.name assert_equal 1000, c.rating @@ -247,7 +231,7 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase assert c.persisted? end - def test_find_or_initialize_should_set_protected_attributes_if_given_as_block + def test_find_or_initialize_should_set_attributes_if_given_as_block c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } assert_equal "Fortune 1000", c.name assert_equal 1000.to_f, c.rating.to_f @@ -255,7 +239,7 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase assert !c.persisted? end - def test_find_or_create_should_set_protected_attributes_if_given_as_block + def test_find_or_create_should_set_attributes_if_given_as_block c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } assert_equal "Fortune 1000", c.name assert_equal 1000.to_f, c.rating.to_f diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index 9705a11387..71b2b16608 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -49,7 +49,7 @@ module ActiveRecord dbtopic = Topic.first topic = Topic.new - topic.attributes = dbtopic.attributes + topic.attributes = dbtopic.attributes.except("id") #duped has no timestamp values duped = dbtopic.dup diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb new file mode 100644 index 0000000000..9a2172f41e --- /dev/null +++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb @@ -0,0 +1,49 @@ +require 'cases/helper' +require 'active_support/core_ext/hash/indifferent_access' +require 'models/person' + +class ProtectedParams < ActiveSupport::HashWithIndifferentAccess + attr_accessor :permitted + alias :permitted? :permitted + + def initialize(attributes) + super(attributes) + @permitted = false + end + + def permit! + @permitted = true + self + end + + def dup + super.tap do |duplicate| + duplicate.instance_variable_set :@permitted, @permitted + end + end +end + +class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase + def test_forbidden_attributes_cannot_be_used_for_mass_assignment + params = ProtectedParams.new(first_name: 'Guille', gender: 'm') + assert_raises(ActiveModel::ForbiddenAttributesError) do + Person.new(params) + end + end + + def test_permitted_attributes_can_be_used_for_mass_assignment + params = ProtectedParams.new(first_name: 'Guille', gender: 'm') + params.permit! + person = Person.new(params) + + assert_equal 'Guille', person.first_name + assert_equal 'm', person.gender + end + + def test_regular_hash_should_still_be_used_for_mass_assignment + person = Person.new(first_name: 'Guille', gender: 'm') + + assert_equal 'Guille', person.first_name + assert_equal 'm', person.gender + end +end diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb deleted file mode 100644 index a36b2c2506..0000000000 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ /dev/null @@ -1,966 +0,0 @@ -require "cases/helper" -require 'models/company' -require 'models/subscriber' -require 'models/keyboard' -require 'models/task' -require 'models/person' - - -module MassAssignmentTestHelpers - def setup - # another AR test modifies the columns which causes issues with create calls - TightPerson.reset_column_information - LoosePerson.reset_column_information - end - - def attributes_hash - { - :id => 5, - :first_name => 'Josh', - :gender => 'm', - :comments => 'rides a sweet bike' - } - end - - def assert_default_attributes(person, create = false) - unless create - assert_nil person.id - else - assert !!person.id - end - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_nil person.comments - end - - def assert_admin_attributes(person, create = false) - unless create - assert_nil person.id - else - assert !!person.id - end - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_equal 'rides a sweet bike', person.comments - end - - def assert_all_attributes(person) - assert_equal 5, person.id - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_equal 'rides a sweet bike', person.comments - end - - def with_strict_sanitizer - ActiveRecord::Base.mass_assignment_sanitizer = :strict - yield - ensure - ActiveRecord::Base.mass_assignment_sanitizer = :logger - end -end - -module MassAssignmentRelationTestHelpers - def setup - super - @person = LoosePerson.create(attributes_hash) - end -end - - -class MassAssignmentSecurityTest < ActiveRecord::TestCase - include MassAssignmentTestHelpers - - def test_customized_primary_key_remains_protected - subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try') - assert_nil subscriber.id - - keyboard = Keyboard.new(:key_number => 9, :name => 'nice try') - assert_nil keyboard.id - end - - def test_customized_primary_key_remains_protected_when_referred_to_as_id - subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try') - assert_nil subscriber.id - - keyboard = Keyboard.new(:id => 9, :name => 'nice try') - assert_nil keyboard.id - end - - def test_mass_assigning_invalid_attribute - firm = Firm.new - - assert_raise(ActiveRecord::UnknownAttributeError) do - firm.attributes = { "id" => 5, "type" => "Client", "i_dont_even_exist" => 20 } - end - end - - def test_mass_assigning_does_not_choke_on_nil - assert_nil Firm.new.assign_attributes(nil) - end - - def test_mass_assigning_does_not_choke_on_empty_hash - assert_nil Firm.new.assign_attributes({}) - end - - def test_assign_attributes_uses_default_role_when_no_role_is_provided - p = LoosePerson.new - p.assign_attributes(attributes_hash) - - assert_default_attributes(p) - end - - def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used - p = LoosePerson.new - p.assign_attributes(attributes_hash, :without_protection => true) - - assert_all_attributes(p) - end - - def test_assign_attributes_with_default_role_and_attr_protected_attributes - p = LoosePerson.new - p.assign_attributes(attributes_hash, :as => :default) - - assert_default_attributes(p) - end - - def test_assign_attributes_with_admin_role_and_attr_protected_attributes - p = LoosePerson.new - p.assign_attributes(attributes_hash, :as => :admin) - - assert_admin_attributes(p) - end - - def test_assign_attributes_with_default_role_and_attr_accessible_attributes - p = TightPerson.new - p.assign_attributes(attributes_hash, :as => :default) - - assert_default_attributes(p) - end - - def test_assign_attributes_with_admin_role_and_attr_accessible_attributes - p = TightPerson.new - p.assign_attributes(attributes_hash, :as => :admin) - - assert_admin_attributes(p) - end - - def test_new_with_attr_accessible_attributes - p = TightPerson.new(attributes_hash) - - assert_default_attributes(p) - end - - def test_new_with_attr_protected_attributes - p = LoosePerson.new(attributes_hash) - - assert_default_attributes(p) - end - - def test_create_with_attr_accessible_attributes - p = TightPerson.create(attributes_hash) - - assert_default_attributes(p, true) - end - - def test_create_with_attr_protected_attributes - p = LoosePerson.create(attributes_hash) - - assert_default_attributes(p, true) - end - - def test_new_with_admin_role_with_attr_accessible_attributes - p = TightPerson.new(attributes_hash, :as => :admin) - - assert_admin_attributes(p) - end - - def test_new_with_admin_role_with_attr_protected_attributes - p = LoosePerson.new(attributes_hash, :as => :admin) - - assert_admin_attributes(p) - end - - def test_create_with_admin_role_with_attr_accessible_attributes - p = TightPerson.create(attributes_hash, :as => :admin) - - assert_admin_attributes(p, true) - end - - def test_create_with_admin_role_with_attr_protected_attributes - p = LoosePerson.create(attributes_hash, :as => :admin) - - assert_admin_attributes(p, true) - end - - def test_create_with_bang_with_admin_role_with_attr_accessible_attributes - p = TightPerson.create!(attributes_hash, :as => :admin) - - assert_admin_attributes(p, true) - end - - def test_create_with_bang_with_admin_role_with_attr_protected_attributes - p = LoosePerson.create!(attributes_hash, :as => :admin) - - assert_admin_attributes(p, true) - end - - def test_new_with_without_protection_with_attr_accessible_attributes - p = TightPerson.new(attributes_hash, :without_protection => true) - - assert_all_attributes(p) - end - - def test_new_with_without_protection_with_attr_protected_attributes - p = LoosePerson.new(attributes_hash, :without_protection => true) - - assert_all_attributes(p) - end - - def test_create_with_without_protection_with_attr_accessible_attributes - p = TightPerson.create(attributes_hash, :without_protection => true) - - assert_all_attributes(p) - end - - def test_create_with_without_protection_with_attr_protected_attributes - p = LoosePerson.create(attributes_hash, :without_protection => true) - - assert_all_attributes(p) - end - - def test_create_with_bang_with_without_protection_with_attr_accessible_attributes - p = TightPerson.create!(attributes_hash, :without_protection => true) - - assert_all_attributes(p) - end - - def test_create_with_bang_with_without_protection_with_attr_protected_attributes - p = LoosePerson.create!(attributes_hash, :without_protection => true) - - assert_all_attributes(p) - end - - def test_protection_against_class_attribute_writers - [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, - :default_timezone, :schema_format, :lock_optimistically, :timestamped_migrations, :default_scopes, - :connection_handler, :nested_attributes_options, :_attr_readonly, :attribute_types_cached_by_default, - :attribute_method_matchers, :time_zone_aware_attributes, :skip_time_zone_conversion_for_attributes].each do |method| - assert_respond_to Task, method - assert_respond_to Task, "#{method}=" - assert_respond_to Task.new, method - assert !Task.new.respond_to?("#{method}=") - end - end - - test "ActiveRecord::Model.whitelist_attributes works for models which include Model" do - begin - prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true - - klass = Class.new { include ActiveRecord::Model } - assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class - assert_equal [], klass.active_authorizers[:default].to_a - ensure - ActiveRecord::Model.whitelist_attributes = prev - end - end - - test "ActiveRecord::Model.whitelist_attributes works for models which inherit Base" do - begin - prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true - - klass = Class.new(ActiveRecord::Base) - assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class - assert_equal [], klass.active_authorizers[:default].to_a - - klass.attr_accessible 'foo' - assert_equal ['foo'], Class.new(klass).active_authorizers[:default].to_a - ensure - ActiveRecord::Model.whitelist_attributes = prev - end - end - - test "ActiveRecord::Model.mass_assignment_sanitizer works for models which include Model" do - begin - sanitizer = Object.new - prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer - - klass = Class.new { include ActiveRecord::Model } - assert_equal sanitizer, klass._mass_assignment_sanitizer - - ActiveRecord::Model.mass_assignment_sanitizer = nil - klass = Class.new { include ActiveRecord::Model } - assert_not_nil klass._mass_assignment_sanitizer - ensure - ActiveRecord::Model.mass_assignment_sanitizer = prev - end - end - - test "ActiveRecord::Model.mass_assignment_sanitizer works for models which inherit Base" do - begin - sanitizer = Object.new - prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer - - klass = Class.new(ActiveRecord::Base) - assert_equal sanitizer, klass._mass_assignment_sanitizer - - sanitizer2 = Object.new - klass.mass_assignment_sanitizer = sanitizer2 - assert_equal sanitizer2, Class.new(klass)._mass_assignment_sanitizer - ensure - ActiveRecord::Model.mass_assignment_sanitizer = prev - end - end -end - - -# This class should be deleted when we remove activerecord-deprecated_finders as a -# dependency. -class MassAssignmentSecurityDeprecatedFindersTest < ActiveRecord::TestCase - include MassAssignmentTestHelpers - - def setup - super - @deprecation_behavior = ActiveSupport::Deprecation.behavior - ActiveSupport::Deprecation.behavior = :silence - end - - def teardown - ActiveSupport::Deprecation.behavior = @deprecation_behavior - end - - def test_find_or_initialize_by_with_attr_accessible_attributes - p = TightPerson.find_or_initialize_by_first_name('Josh', attributes_hash) - - assert_default_attributes(p) - end - - def test_find_or_initialize_by_with_admin_role_with_attr_accessible_attributes - p = TightPerson.find_or_initialize_by_first_name('Josh', attributes_hash, :as => :admin) - - assert_admin_attributes(p) - end - - def test_find_or_initialize_by_with_attr_protected_attributes - p = LoosePerson.find_or_initialize_by_first_name('Josh', attributes_hash) - - assert_default_attributes(p) - end - - def test_find_or_initialize_by_with_admin_role_with_attr_protected_attributes - p = LoosePerson.find_or_initialize_by_first_name('Josh', attributes_hash, :as => :admin) - - assert_admin_attributes(p) - end - - def test_find_or_create_by_with_attr_accessible_attributes - p = TightPerson.find_or_create_by_first_name('Josh', attributes_hash) - - assert_default_attributes(p, true) - end - - def test_find_or_create_by_with_admin_role_with_attr_accessible_attributes - p = TightPerson.find_or_create_by_first_name('Josh', attributes_hash, :as => :admin) - - assert_admin_attributes(p, true) - end - - def test_find_or_create_by_with_attr_protected_attributes - p = LoosePerson.find_or_create_by_first_name('Josh', attributes_hash) - - assert_default_attributes(p, true) - end - - def test_find_or_create_by_with_admin_role_with_attr_protected_attributes - p = LoosePerson.find_or_create_by_first_name('Josh', attributes_hash, :as => :admin) - - assert_admin_attributes(p, true) - end - -end - - -class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase - include MassAssignmentTestHelpers - include MassAssignmentRelationTestHelpers - - # build - - def test_has_one_build_with_attr_protected_attributes - best_friend = @person.build_best_friend(attributes_hash) - assert_default_attributes(best_friend) - end - - def test_has_one_build_with_attr_accessible_attributes - best_friend = @person.build_best_friend(attributes_hash) - assert_default_attributes(best_friend) - end - - def test_has_one_build_with_admin_role_with_attr_protected_attributes - best_friend = @person.build_best_friend(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend) - end - - def test_has_one_build_with_admin_role_with_attr_accessible_attributes - best_friend = @person.build_best_friend(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend) - end - - def test_has_one_build_without_protection - best_friend = @person.build_best_friend(attributes_hash, :without_protection => true) - assert_all_attributes(best_friend) - end - - def test_has_one_build_with_strict_sanitizer - with_strict_sanitizer do - best_friend = @person.build_best_friend(attributes_hash.except(:id, :comments)) - assert_equal @person.id, best_friend.best_friend_id - end - end - - # create - - def test_has_one_create_with_attr_protected_attributes - best_friend = @person.create_best_friend(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_has_one_create_with_attr_accessible_attributes - best_friend = @person.create_best_friend(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_has_one_create_with_admin_role_with_attr_protected_attributes - best_friend = @person.create_best_friend(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_has_one_create_with_admin_role_with_attr_accessible_attributes - best_friend = @person.create_best_friend(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_has_one_create_without_protection - best_friend = @person.create_best_friend(attributes_hash, :without_protection => true) - assert_all_attributes(best_friend) - end - - def test_has_one_create_with_strict_sanitizer - with_strict_sanitizer do - best_friend = @person.create_best_friend(attributes_hash.except(:id, :comments)) - assert_equal @person.id, best_friend.best_friend_id - end - end - - # create! - - def test_has_one_create_with_bang_with_attr_protected_attributes - best_friend = @person.create_best_friend!(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_has_one_create_with_bang_with_attr_accessible_attributes - best_friend = @person.create_best_friend!(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes - best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes - best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_has_one_create_with_bang_without_protection - best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true) - assert_all_attributes(best_friend) - end - - def test_has_one_create_with_bang_with_strict_sanitizer - with_strict_sanitizer do - best_friend = @person.create_best_friend!(attributes_hash.except(:id, :comments)) - assert_equal @person.id, best_friend.best_friend_id - end - end - -end - - -class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase - include MassAssignmentTestHelpers - include MassAssignmentRelationTestHelpers - - # build - - def test_belongs_to_build_with_attr_protected_attributes - best_friend = @person.build_best_friend_of(attributes_hash) - assert_default_attributes(best_friend) - end - - def test_belongs_to_build_with_attr_accessible_attributes - best_friend = @person.build_best_friend_of(attributes_hash) - assert_default_attributes(best_friend) - end - - def test_belongs_to_build_with_admin_role_with_attr_protected_attributes - best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend) - end - - def test_belongs_to_build_with_admin_role_with_attr_accessible_attributes - best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend) - end - - def test_belongs_to_build_without_protection - best_friend = @person.build_best_friend_of(attributes_hash, :without_protection => true) - assert_all_attributes(best_friend) - end - - # create - - def test_belongs_to_create_with_attr_protected_attributes - best_friend = @person.create_best_friend_of(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_belongs_to_create_with_attr_accessible_attributes - best_friend = @person.create_best_friend_of(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_belongs_to_create_with_admin_role_with_attr_protected_attributes - best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes - best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_belongs_to_create_without_protection - best_friend = @person.create_best_friend_of(attributes_hash, :without_protection => true) - assert_all_attributes(best_friend) - end - - def test_belongs_to_create_with_strict_sanitizer - with_strict_sanitizer do - best_friend = @person.create_best_friend_of(attributes_hash.except(:id, :comments)) - assert_equal best_friend.id, @person.best_friend_of_id - end - end - - # create! - - def test_belongs_to_create_with_bang_with_attr_protected_attributes - best_friend = @person.create_best_friend!(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_belongs_to_create_with_bang_with_attr_accessible_attributes - best_friend = @person.create_best_friend!(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes - best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes - best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_belongs_to_create_with_bang_without_protection - best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true) - assert_all_attributes(best_friend) - end - - def test_belongs_to_create_with_bang_with_strict_sanitizer - with_strict_sanitizer do - best_friend = @person.create_best_friend_of!(attributes_hash.except(:id, :comments)) - assert_equal best_friend.id, @person.best_friend_of_id - end - end - -end - - -class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase - include MassAssignmentTestHelpers - include MassAssignmentRelationTestHelpers - - # build - - def test_has_many_build_with_attr_protected_attributes - best_friend = @person.best_friends.build(attributes_hash) - assert_default_attributes(best_friend) - end - - def test_has_many_build_with_attr_accessible_attributes - best_friend = @person.best_friends.build(attributes_hash) - assert_default_attributes(best_friend) - end - - def test_has_many_build_with_admin_role_with_attr_protected_attributes - best_friend = @person.best_friends.build(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend) - end - - def test_has_many_build_with_admin_role_with_attr_accessible_attributes - best_friend = @person.best_friends.build(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend) - end - - def test_has_many_build_without_protection - best_friend = @person.best_friends.build(attributes_hash, :without_protection => true) - assert_all_attributes(best_friend) - end - - def test_has_many_build_with_strict_sanitizer - with_strict_sanitizer do - best_friend = @person.best_friends.build(attributes_hash.except(:id, :comments)) - assert_equal @person.id, best_friend.best_friend_id - end - end - - # create - - def test_has_many_create_with_attr_protected_attributes - best_friend = @person.best_friends.create(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_has_many_create_with_attr_accessible_attributes - best_friend = @person.best_friends.create(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_has_many_create_with_admin_role_with_attr_protected_attributes - best_friend = @person.best_friends.create(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_has_many_create_with_admin_role_with_attr_accessible_attributes - best_friend = @person.best_friends.create(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_has_many_create_without_protection - best_friend = @person.best_friends.create(attributes_hash, :without_protection => true) - assert_all_attributes(best_friend) - end - - def test_has_many_create_with_strict_sanitizer - with_strict_sanitizer do - best_friend = @person.best_friends.create(attributes_hash.except(:id, :comments)) - assert_equal @person.id, best_friend.best_friend_id - end - end - - # create! - - def test_has_many_create_with_bang_with_attr_protected_attributes - best_friend = @person.best_friends.create!(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_has_many_create_with_bang_with_attr_accessible_attributes - best_friend = @person.best_friends.create!(attributes_hash) - assert_default_attributes(best_friend, true) - end - - def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes - best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes - best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) - assert_admin_attributes(best_friend, true) - end - - def test_has_many_create_with_bang_without_protection - best_friend = @person.best_friends.create!(attributes_hash, :without_protection => true) - assert_all_attributes(best_friend) - end - - def test_has_many_create_with_bang_with_strict_sanitizer - with_strict_sanitizer do - best_friend = @person.best_friends.create!(attributes_hash.except(:id, :comments)) - assert_equal @person.id, best_friend.best_friend_id - end - end - -end - - -class MassAssignmentSecurityNestedAttributesTest < ActiveRecord::TestCase - include MassAssignmentTestHelpers - - def nested_attributes_hash(association, collection = false, except = [:id]) - if collection - { :first_name => 'David' }.merge(:"#{association}_attributes" => [attributes_hash.except(*except)]) - else - { :first_name => 'David' }.merge(:"#{association}_attributes" => attributes_hash.except(*except)) - end - end - - # build - - def test_has_one_new_with_attr_protected_attributes - person = LoosePerson.new(nested_attributes_hash(:best_friend)) - assert_default_attributes(person.best_friend) - end - - def test_has_one_new_with_attr_accessible_attributes - person = TightPerson.new(nested_attributes_hash(:best_friend)) - assert_default_attributes(person.best_friend) - end - - def test_has_one_new_with_admin_role_with_attr_protected_attributes - person = LoosePerson.new(nested_attributes_hash(:best_friend), :as => :admin) - assert_admin_attributes(person.best_friend) - end - - def test_has_one_new_with_admin_role_with_attr_accessible_attributes - person = TightPerson.new(nested_attributes_hash(:best_friend), :as => :admin) - assert_admin_attributes(person.best_friend) - end - - def test_has_one_new_without_protection - person = LoosePerson.new(nested_attributes_hash(:best_friend, false, nil), :without_protection => true) - assert_all_attributes(person.best_friend) - end - - def test_belongs_to_new_with_attr_protected_attributes - person = LoosePerson.new(nested_attributes_hash(:best_friend_of)) - assert_default_attributes(person.best_friend_of) - end - - def test_belongs_to_new_with_attr_accessible_attributes - person = TightPerson.new(nested_attributes_hash(:best_friend_of)) - assert_default_attributes(person.best_friend_of) - end - - def test_belongs_to_new_with_admin_role_with_attr_protected_attributes - person = LoosePerson.new(nested_attributes_hash(:best_friend_of), :as => :admin) - assert_admin_attributes(person.best_friend_of) - end - - def test_belongs_to_new_with_admin_role_with_attr_accessible_attributes - person = TightPerson.new(nested_attributes_hash(:best_friend_of), :as => :admin) - assert_admin_attributes(person.best_friend_of) - end - - def test_belongs_to_new_without_protection - person = LoosePerson.new(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true) - assert_all_attributes(person.best_friend_of) - end - - def test_has_many_new_with_attr_protected_attributes - person = LoosePerson.new(nested_attributes_hash(:best_friends, true)) - assert_default_attributes(person.best_friends.first) - end - - def test_has_many_new_with_attr_accessible_attributes - person = TightPerson.new(nested_attributes_hash(:best_friends, true)) - assert_default_attributes(person.best_friends.first) - end - - def test_has_many_new_with_admin_role_with_attr_protected_attributes - person = LoosePerson.new(nested_attributes_hash(:best_friends, true), :as => :admin) - assert_admin_attributes(person.best_friends.first) - end - - def test_has_many_new_with_admin_role_with_attr_accessible_attributes - person = TightPerson.new(nested_attributes_hash(:best_friends, true), :as => :admin) - assert_admin_attributes(person.best_friends.first) - end - - def test_has_many_new_without_protection - person = LoosePerson.new(nested_attributes_hash(:best_friends, true, nil), :without_protection => true) - assert_all_attributes(person.best_friends.first) - end - - # create - - def test_has_one_create_with_attr_protected_attributes - person = LoosePerson.create(nested_attributes_hash(:best_friend)) - assert_default_attributes(person.best_friend, true) - end - - def test_has_one_create_with_attr_accessible_attributes - person = TightPerson.create(nested_attributes_hash(:best_friend)) - assert_default_attributes(person.best_friend, true) - end - - def test_has_one_create_with_admin_role_with_attr_protected_attributes - person = LoosePerson.create(nested_attributes_hash(:best_friend), :as => :admin) - assert_admin_attributes(person.best_friend, true) - end - - def test_has_one_create_with_admin_role_with_attr_accessible_attributes - person = TightPerson.create(nested_attributes_hash(:best_friend), :as => :admin) - assert_admin_attributes(person.best_friend, true) - end - - def test_has_one_create_without_protection - person = LoosePerson.create(nested_attributes_hash(:best_friend, false, nil), :without_protection => true) - assert_all_attributes(person.best_friend) - end - - def test_belongs_to_create_with_attr_protected_attributes - person = LoosePerson.create(nested_attributes_hash(:best_friend_of)) - assert_default_attributes(person.best_friend_of, true) - end - - def test_belongs_to_create_with_attr_accessible_attributes - person = TightPerson.create(nested_attributes_hash(:best_friend_of)) - assert_default_attributes(person.best_friend_of, true) - end - - def test_belongs_to_create_with_admin_role_with_attr_protected_attributes - person = LoosePerson.create(nested_attributes_hash(:best_friend_of), :as => :admin) - assert_admin_attributes(person.best_friend_of, true) - end - - def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes - person = TightPerson.create(nested_attributes_hash(:best_friend_of), :as => :admin) - assert_admin_attributes(person.best_friend_of, true) - end - - def test_belongs_to_create_without_protection - person = LoosePerson.create(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true) - assert_all_attributes(person.best_friend_of) - end - - def test_has_many_create_with_attr_protected_attributes - person = LoosePerson.create(nested_attributes_hash(:best_friends, true)) - assert_default_attributes(person.best_friends.first, true) - end - - def test_has_many_create_with_attr_accessible_attributes - person = TightPerson.create(nested_attributes_hash(:best_friends, true)) - assert_default_attributes(person.best_friends.first, true) - end - - def test_has_many_create_with_admin_role_with_attr_protected_attributes - person = LoosePerson.create(nested_attributes_hash(:best_friends, true), :as => :admin) - assert_admin_attributes(person.best_friends.first, true) - end - - def test_has_many_create_with_admin_role_with_attr_accessible_attributes - person = TightPerson.create(nested_attributes_hash(:best_friends, true), :as => :admin) - assert_admin_attributes(person.best_friends.first, true) - end - - def test_has_many_create_without_protection - person = LoosePerson.create(nested_attributes_hash(:best_friends, true, nil), :without_protection => true) - assert_all_attributes(person.best_friends.first) - end - - # create! - - def test_has_one_create_with_bang_with_attr_protected_attributes - person = LoosePerson.create!(nested_attributes_hash(:best_friend)) - assert_default_attributes(person.best_friend, true) - end - - def test_has_one_create_with_bang_with_attr_accessible_attributes - person = TightPerson.create!(nested_attributes_hash(:best_friend)) - assert_default_attributes(person.best_friend, true) - end - - def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes - person = LoosePerson.create!(nested_attributes_hash(:best_friend), :as => :admin) - assert_admin_attributes(person.best_friend, true) - end - - def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes - person = TightPerson.create!(nested_attributes_hash(:best_friend), :as => :admin) - assert_admin_attributes(person.best_friend, true) - end - - def test_has_one_create_with_bang_without_protection - person = LoosePerson.create!(nested_attributes_hash(:best_friend, false, nil), :without_protection => true) - assert_all_attributes(person.best_friend) - end - - def test_belongs_to_create_with_bang_with_attr_protected_attributes - person = LoosePerson.create!(nested_attributes_hash(:best_friend_of)) - assert_default_attributes(person.best_friend_of, true) - end - - def test_belongs_to_create_with_bang_with_attr_accessible_attributes - person = TightPerson.create!(nested_attributes_hash(:best_friend_of)) - assert_default_attributes(person.best_friend_of, true) - end - - def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes - person = LoosePerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin) - assert_admin_attributes(person.best_friend_of, true) - end - - def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes - person = TightPerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin) - assert_admin_attributes(person.best_friend_of, true) - end - - def test_belongs_to_create_with_bang_without_protection - person = LoosePerson.create!(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true) - assert_all_attributes(person.best_friend_of) - end - - def test_has_many_create_with_bang_with_attr_protected_attributes - person = LoosePerson.create!(nested_attributes_hash(:best_friends, true)) - assert_default_attributes(person.best_friends.first, true) - end - - def test_has_many_create_with_bang_with_attr_accessible_attributes - person = TightPerson.create!(nested_attributes_hash(:best_friends, true)) - assert_default_attributes(person.best_friends.first, true) - end - - def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes - person = LoosePerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin) - assert_admin_attributes(person.best_friends.first, true) - end - - def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes - person = TightPerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin) - assert_admin_attributes(person.best_friends.first, true) - end - - def test_has_many_create_with_bang_without_protection - person = LoosePerson.create!(nested_attributes_hash(:best_friends, true, nil), :without_protection => true) - assert_all_attributes(person.best_friends.first) - end - - def test_mass_assignment_options_are_reset_after_exception - person = NestedPerson.create!({ :first_name => 'David', :gender => 'm' }, :as => :admin) - person.create_best_friend!({ :first_name => 'Jeremy', :gender => 'm' }, :as => :admin) - - attributes = { :best_friend_attributes => { :comments => 'rides a sweet bike' } } - assert_raises(RuntimeError) { person.assign_attributes(attributes, :as => :admin) } - assert_equal 'm', person.best_friend.gender - - person.best_friend_attributes = { :gender => 'f' } - assert_equal 'm', person.best_friend.gender - end - - def test_mass_assignment_options_are_nested_correctly - person = NestedPerson.create!({ :first_name => 'David', :gender => 'm' }, :as => :admin) - person.create_best_friend!({ :first_name => 'Jeremy', :gender => 'm' }, :as => :admin) - - attributes = { :best_friend_first_name => 'Josh', :best_friend_attributes => { :gender => 'f' } } - person.assign_attributes(attributes, :as => :admin) - assert_equal 'Josh', person.best_friend.first_name - assert_equal 'f', person.best_friend.gender - end - -end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 4ffa4836e0..b5f32a57b2 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -608,26 +608,6 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal "The First Topic", topic.title end - def test_update_attributes_as_admin - person = TightPerson.create({ "first_name" => 'Joshua' }) - person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin) - person.reload - - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_equal 'from NZ', person.comments - end - - def test_update_attributes_without_protection - person = TightPerson.create({ "first_name" => 'Joshua' }) - person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true) - person.reload - - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_equal 'from NZ', person.comments - end - def test_update_attributes! Reply.validates_presence_of(:title) reply = Reply.find(2) @@ -649,26 +629,6 @@ class PersistencesTest < ActiveRecord::TestCase Reply.reset_callbacks(:validate) end - def test_update_attributes_with_bang_as_admin - person = TightPerson.create({ "first_name" => 'Joshua' }) - person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin) - person.reload - - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_equal 'from NZ', person.comments - end - - def test_update_attributestes_with_bang_without_protection - person = TightPerson.create({ "first_name" => 'Joshua' }) - person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true) - person.reload - - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_equal 'from NZ', person.comments - end - def test_destroyed_returns_boolean developer = Developer.first assert_equal false, developer.destroyed? diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index b11b330374..3f587d177b 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -7,12 +7,6 @@ require 'models/developer' require 'models/parrot' require 'models/company' -class ProtectedPerson < ActiveRecord::Base - self.table_name = 'people' - attr_accessor :addon - attr_protected :first_name -end - class ValidationsTest < ActiveRecord::TestCase fixtures :topics, :developers diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 0dc2fdd8ae..e4c0278c0d 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -2,8 +2,6 @@ class Bulb < ActiveRecord::Base default_scope { where(:name => 'defaulty') } belongs_to :car - attr_protected :car_id, :frickinawesome - attr_reader :scope_after_initialize, :attributes_after_initialize after_initialize :record_scope_after_initialize @@ -20,12 +18,12 @@ class Bulb < ActiveRecord::Base self[:color] = color.upcase + "!" end - def self.new(attributes = {}, options = {}, &block) + def self.new(attributes = {}, &block) bulb_type = (attributes || {}).delete(:bulb_type) - if options && options[:as] == :admin && bulb_type.present? + if bulb_type.present? bulb_class = "#{bulb_type.to_s.camelize}Bulb".constantize - bulb_class.new(attributes, options, &block) + bulb_class.new(attributes, &block) else super end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 9bdce6e729..17b17724e8 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -3,7 +3,6 @@ class AbstractCompany < ActiveRecord::Base end class Company < AbstractCompany - attr_protected :rating self.sequence_name = :companies_nonstd_seq validates_presence_of :name diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb index eb2aedc425..461bb0de09 100644 --- a/activerecord/test/models/company_in_module.rb +++ b/activerecord/test/models/company_in_module.rb @@ -3,7 +3,6 @@ require 'active_support/core_ext/object/with_options' module MyApplication module Business class Company < ActiveRecord::Base - attr_protected :rating end class Firm < Company diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 6e6ff29f77..6ad0cf6987 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -59,9 +59,6 @@ class LoosePerson < ActiveRecord::Base self.table_name = 'people' self.abstract_class = true - attr_protected :comments, :best_friend_id, :best_friend_of_id - attr_protected :as => :admin - has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id @@ -74,11 +71,6 @@ class LooseDescendant < LoosePerson; end class TightPerson < ActiveRecord::Base self.table_name = 'people' - attr_accessible :first_name, :gender - attr_accessible :first_name, :gender, :comments, :as => :admin - attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes - attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes, :as => :admin - has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id @@ -97,10 +89,6 @@ end class NestedPerson < ActiveRecord::Base self.table_name = 'people' - attr_accessible :first_name, :best_friend_first_name, :best_friend_attributes - attr_accessible :first_name, :gender, :comments, :as => :admin - attr_accessible :best_friend_attributes, :best_friend_first_name, :as => :admin - has_one :best_friend, :class_name => 'NestedPerson', :foreign_key => :best_friend_id accepts_nested_attributes_for :best_friend, :update_only => true diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb index f5b6079bd2..f8fb9c573e 100644 --- a/activerecord/test/models/reader.rb +++ b/activerecord/test/models/reader.rb @@ -9,8 +9,6 @@ class SecureReader < ActiveRecord::Base belongs_to :secure_post, :class_name => "Post", :foreign_key => "post_id" belongs_to :secure_person, :inverse_of => :secure_readers, :class_name => "Person", :foreign_key => "person_id" - - attr_accessible nil end class LazyReader < ActiveRecord::Base diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index 53bc95e5f2..079e325aad 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -6,8 +6,6 @@ class Reply < Topic belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count" has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id" - - attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title end class UniqueReply < Reply diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 09b08d5663..f7d8f718de 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -46,11 +46,6 @@ module <%= app_const_base %> # like if you have constraints or database-specific column types. # config.active_record.schema_format = :sql - # Enforce whitelist mode for mass assignment. - # This will create an empty whitelist of attributes available for mass-assignment for all models - # in your app. As such, your models will need to explicitly whitelist or blacklist accessible - # parameters by using an attr_accessible or attr_protected declaration. - <%= comment_if :skip_active_record %>config.active_record.whitelist_attributes = true <% unless options.skip_sprockets? -%> # Enable the asset pipeline. diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index bcd0e7c898..6b5b3a0b1f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -23,9 +23,6 @@ config.action_dispatch.best_standards_support = :builtin <%- unless options.skip_active_record? -%> - # Raise exception on mass assignment protection for Active Record models. - config.active_record.mass_assignment_sanitizer = :strict - # Log the query plan for queries taking more than this (works # with SQLite, MySQL, and PostgreSQL). config.active_record.auto_explain_threshold_in_seconds = 0.5 diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 75897ba8cd..202fc98adf 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -31,11 +31,6 @@ # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - <%- unless options.skip_active_record? -%> - # Raise exception on mass assignment protection for Active Record models. - config.active_record.mass_assignment_sanitizer = :strict - <%- end -%> - # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index 0618b16984..f30ad6e20d 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -10,6 +10,8 @@ module Rails class_option :orm, :banner => "NAME", :type => :string, :required => true, :desc => "ORM to generate the controller for" + argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + def create_controller_files template "controller.rb", File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb") end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index b3e74f9b02..5d038d20e7 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -45,7 +45,7 @@ class <%= controller_class_name %>Controller < ApplicationController # POST <%= route_url %> # POST <%= route_url %>.json def create - @<%= singular_table_name %> = <%= orm_class.build(class_name, "params[:#{singular_table_name}]") %> + @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %> respond_to do |format| if @<%= orm_instance.save %> @@ -64,7 +64,7 @@ class <%= controller_class_name %>Controller < ApplicationController @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> respond_to do |format| - if @<%= orm_instance.update_attributes("params[:#{singular_table_name}]") %> + if @<%= orm_instance.update_attributes("#{singular_table_name}_params") %> format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> } format.json { head :no_content } else @@ -85,5 +85,17 @@ class <%= controller_class_name %>Controller < ApplicationController format.json { head :no_content } end end + + private + + # Use this method to whitelist the permissible parameters. Example: params.require(:person).permit(:name, :age) + # Also, you can specialize this method with per-user checking of permissible attributes. + def <%= "#{singular_table_name}_params" %> + <%- if attributes.empty? -%> + params[<%= ":#{singular_table_name}" %>] + <%- else -%> + params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes.map {|a| ":#{a.name}" }.sort.join(', ') %>) + <%- end -%> + end end <% end -%> diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 428c90afd0..d014e5e362 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -358,19 +358,6 @@ module ApplicationTests assert_equal "utf-16", ActionDispatch::Response.default_charset end - test "sets all Active Record models to whitelist all attributes by default" do - add_to_config <<-RUBY - config.active_record.whitelist_attributes = true - RUBY - - require "#{app_path}/config/environment" - - klass = Class.new(ActiveRecord::Base) - - assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class - assert_equal [], klass.active_authorizers[:default].to_a - end - test "registers interceptors with ActionMailer" do add_to_config <<-RUBY config.action_mailer.interceptors = MyMailInterceptor @@ -595,6 +582,28 @@ module ApplicationTests assert_equal '{"title"=>"foo"}', last_response.body end + test "config.action_controller.permit_all_parameters = true" do + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ActionController::Base + def create + render :text => params[:post].permitted? ? "permitted" : "forbidden" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + config.action_controller.permit_all_parameters = true + RUBY + + require "#{app_path}/config/environment" + + post "/posts", {:post => {"title" =>"zomg"}} + assert_equal 'permitted', last_response.body + end + test "config.action_dispatch.ignore_accept_header" do make_basic_app do |app| app.config.action_dispatch.ignore_accept_header = true diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index e0286502f3..fcbc3c048c 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -20,7 +20,6 @@ class LoadingTest < ActiveSupport::TestCase app_file "app/models/post.rb", <<-MODEL class Post < ActiveRecord::Base validates_acceptance_of :title, :accept => "omg" - attr_accessible :title end MODEL diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 3ceb8c22a0..e7b6a20b32 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -212,7 +212,6 @@ class AppGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ - assert_file "config/application.rb", /#\s+config\.active_record\.whitelist_attributes = true/ assert_file "test/test_helper.rb" do |helper_content| assert_no_match(/fixtures :all/, helper_content) end @@ -343,15 +342,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generated_environments_file_for_sanitizer - run_generator [destination_root, "--skip-active-record"] - %w(development test).each do |env| - assert_file "config/environments/#{env}.rb" do |file| - assert_no_match(/config.active_record.mass_assignment_sanitizer = :strict/, file) - end - end - end - def test_generated_environments_file_for_auto_explain run_generator [destination_root, "--skip-active-record"] %w(development production).each do |env| @@ -361,11 +351,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_active_record_whitelist_attributes_is_present_application_config - run_generator - assert_file "config/application.rb", /config\.active_record\.whitelist_attributes = true/ - end - def test_pretend_option output = run_generator [File.join(destination_root, "myapp"), "--pretend"] assert_no_match(/run bundle install/, output) diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index ec33bd7c6b..436de26826 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -328,14 +328,4 @@ class ModelGeneratorTest < Rails::Generators::TestCase end end end - - def test_attr_accessible_added_with_non_reference_attributes - run_generator - assert_file 'app/models/account.rb', /attr_accessible :age, :name/ - end - - def test_attr_accessible_added_with_comments_when_no_attributes_present - run_generator ["Account"] - assert_file 'app/models/account.rb', /# attr_accessible :title, :body/ - end end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 1eea50b0d9..1e16f04d85 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -33,14 +33,14 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase end assert_instance_method :create, content do |m| - assert_match(/@user = User\.new\(params\[:user\]\)/, m) + assert_match(/@user = User\.new\(user_params\)/, m) assert_match(/@user\.save/, m) assert_match(/@user\.errors/, m) end assert_instance_method :update, content do |m| assert_match(/@user = User\.find\(params\[:id\]\)/, m) - assert_match(/@user\.update_attributes\(params\[:user\]\)/, m) + assert_match(/@user\.update_attributes\(user_params\)/, m) assert_match(/@user\.errors/, m) end @@ -48,6 +48,9 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_match(/@user = User\.find\(params\[:id\]\)/, m) assert_match(/@user\.destroy/, m) end + + assert_match(/def user_params/, content) + assert_match(/params\.require\(:user\)\.permit\(:age, :name\)/, content) end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 9b456c64ef..40c5188042 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -43,14 +43,14 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_instance_method :create, content do |m| - assert_match(/@product_line = ProductLine\.new\(params\[:product_line\]\)/, m) + assert_match(/@product_line = ProductLine\.new\(product_line_params\)/, m) assert_match(/@product_line\.save/, m) assert_match(/@product_line\.errors/, m) end assert_instance_method :update, content do |m| assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m) - assert_match(/@product_line\.update_attributes\(params\[:product_line\]\)/, m) + assert_match(/@product_line\.update_attributes\(product_line_params\)/, m) assert_match(/@product_line\.errors/, m) end @@ -166,14 +166,14 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_instance_method :create, content do |m| - assert_match(/@admin_role = Admin::Role\.new\(params\[:admin_role\]\)/, m) + assert_match(/@admin_role = Admin::Role\.new\(admin_role_params\)/, m) assert_match(/@admin_role\.save/, m) assert_match(/@admin_role\.errors/, m) end assert_instance_method :update, content do |m| assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m) - assert_match(/@admin_role\.update_attributes\(params\[:admin_role\]\)/, m) + assert_match(/@admin_role\.update_attributes\(admin_role_params\)/, m) assert_match(/@admin_role\.errors/, m) end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 07a7faa3af..0f36eb67e5 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -253,9 +253,6 @@ module TestHelpers def use_frameworks(arr) to_remove = [:actionmailer, :activerecord] - arr - if to_remove.include? :activerecord - remove_from_config "config.active_record.whitelist_attributes = true" - end $:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' } end |