Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add mass_assignment_options support to ActiveRecord
- Loading branch information
1 parent
a11bc9a
commit 9a9f697
Showing
12 changed files
with
612 additions
and
106 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
require "active_record" | ||
require "active_record/mass_assignment_security/associations" | ||
require "active_record/mass_assignment_security/attribute_assignment" | ||
require "active_record/mass_assignment_security/core" | ||
require "active_record/mass_assignment_security/nested_attributes" | ||
require "active_record/mass_assignment_security/persistence" | ||
require "active_record/mass_assignment_security/reflection" | ||
require "active_record/mass_assignment_security/relation" | ||
require "active_record/mass_assignment_security/validations" | ||
require "active_record/mass_assignment_security/associations" | ||
|
||
class ActiveRecord::Base | ||
include ActiveRecord::MassAssignmentSecurity::Core | ||
include ActiveRecord::MassAssignmentSecurity::AttributeAssignment | ||
include ActiveRecord::MassAssignmentSecurity::Persistence | ||
include ActiveRecord::MassAssignmentSecurity::Relation | ||
include ActiveRecord::MassAssignmentSecurity::Validations | ||
include ActiveRecord::MassAssignmentSecurity::NestedAttributes | ||
end | ||
|
||
class ActiveRecord::Reflection::AssociationReflection | ||
include ActiveRecord::MassAssignmentSecurity::Reflection::AssociationReflection | ||
end | ||
|
||
module ActiveRecord::Associations | ||
class Association | ||
include ActiveRecord::MassAssignmentSecurity::Associations::Association | ||
end | ||
|
||
class CollectionAssociation | ||
include ActiveRecord::MassAssignmentSecurity::Associations::CollectionAssociation | ||
end | ||
|
||
class CollectionProxy | ||
include ActiveRecord::MassAssignmentSecurity::Associations::CollectionProxy | ||
end | ||
|
||
class HasManyThroughAssociation | ||
include ActiveRecord::MassAssignmentSecurity::Associations::HasManyThroughAssociation | ||
end | ||
|
||
class SingularAssociation | ||
include ActiveRecord::MassAssignmentSecurity::Associations::SingularAssociation | ||
end | ||
end | ||
|
||
ActiveRecord::SchemaMigration.attr_accessible(:version) |
118 changes: 118 additions & 0 deletions
118
lib/active_record/mass_assignment_security/associations.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
module ActiveRecord | ||
module MassAssignmentSecurity | ||
module Associations | ||
module Association | ||
def build_record(attributes, options) | ||
reflection.build_association(attributes, options) do |record| | ||
attributes = create_scope.except(*(record.changed - [reflection.foreign_key])) | ||
record.assign_attributes(attributes, without_protection: true) | ||
end | ||
end | ||
|
||
private :build_record | ||
end | ||
|
||
module CollectionAssociation | ||
def build(attributes = {}, options = {}, &block) | ||
if attributes.is_a?(Array) | ||
attributes.collect { |attr| build(attr, options, &block) } | ||
else | ||
add_to_target(build_record(attributes, options)) do |record| | ||
yield(record) if block_given? | ||
end | ||
end | ||
end | ||
|
||
def create(attributes = {}, options = {}, &block) | ||
create_record(attributes, options, &block) | ||
end | ||
|
||
def create!(attributes = {}, options = {}, &block) | ||
create_record(attributes, options, true, &block) | ||
end | ||
|
||
def create_record(attributes, options, 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) } | ||
else | ||
transaction do | ||
add_to_target(build_record(attributes, options)) do |record| | ||
yield(record) if block_given? | ||
insert_record(record, true, raise) | ||
end | ||
end | ||
end | ||
end | ||
|
||
private :create_record | ||
end | ||
|
||
module CollectionProxy | ||
def build(attributes = {}, options = {}, &block) | ||
@association.build(attributes, options, &block) | ||
end | ||
|
||
def create(attributes = {}, options = {}, &block) | ||
@association.create(attributes, options, &block) | ||
end | ||
|
||
def create!(attributes = {}, options = {}, &block) | ||
@association.create!(attributes, options, &block) | ||
end | ||
end | ||
|
||
module HasManyThroughAssociation | ||
def build_record(attributes, options = {}) | ||
ensure_not_nested | ||
|
||
record = super(attributes, options) | ||
|
||
inverse = source_reflection.inverse_of | ||
if inverse | ||
if inverse.macro == :has_many | ||
record.send(inverse.name) << build_through_record(record) | ||
elsif inverse.macro == :has_one | ||
record.send("#{inverse.name}=", build_through_record(record)) | ||
end | ||
end | ||
|
||
record | ||
end | ||
|
||
private :build_record | ||
end | ||
|
||
module SingularAssociation | ||
def create(attributes = {}, options = {}, &block) | ||
create_record(attributes, options, &block) | ||
end | ||
|
||
def create!(attributes = {}, options = {}, &block) | ||
create_record(attributes, options, true, &block) | ||
end | ||
|
||
def build(attributes = {}, options = {}) | ||
record = build_record(attributes, options) | ||
yield(record) if block_given? | ||
set_new_record(record) | ||
record | ||
end | ||
|
||
def create_record(attributes, options = {}, raise_error = false) | ||
record = build_record(attributes, options) | ||
yield(record) if block_given? | ||
saved = record.save | ||
set_new_record(record) | ||
raise RecordInvalid.new(record) if !saved && raise_error | ||
record | ||
end | ||
|
||
private :create_record | ||
end | ||
end | ||
end | ||
end |
107 changes: 107 additions & 0 deletions
107
lib/active_record/mass_assignment_security/attribute_assignment.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
require 'active_model/mass_assignment_security' | ||
require 'active_record' | ||
|
||
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 MassAssignmentSecurity | ||
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 | ||
|
||
# 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 = {}) | ||
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.each do |k, v| | ||
if k.include?("(") | ||
multi_parameter_attributes << [ k, v ] | ||
elsif v.is_a?(Hash) | ||
nested_parameter_attributes << [ k, v ] | ||
else | ||
_assign_attribute(k, v) | ||
end | ||
end | ||
|
||
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 | ||
end | ||
end | ||
end |
Oops, something went wrong.