protected_attributes_continued 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ require 'set'
2
+
3
+ module ActiveModel
4
+ module MassAssignmentSecurity
5
+ class PermissionSet < Set #:nodoc:
6
+
7
+ def +(values)
8
+ super(values.compact.map(&:to_s))
9
+ end
10
+
11
+ def include?(key)
12
+ super(remove_multiparameter_id(key))
13
+ end
14
+
15
+ def deny?(key)
16
+ raise NotImplementedError, "#deny?(key) supposed to be overwritten"
17
+ end
18
+
19
+ protected
20
+
21
+ def remove_multiparameter_id(key)
22
+ key.to_s.gsub(/\(.+/, '')
23
+ end
24
+ end
25
+
26
+ class WhiteList < PermissionSet #:nodoc:
27
+
28
+ def deny?(key)
29
+ !include?(key)
30
+ end
31
+ end
32
+
33
+ class BlackList < PermissionSet #:nodoc:
34
+
35
+ def deny?(key)
36
+ include?(key)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,75 @@
1
+ module ActiveModel
2
+ module MassAssignmentSecurity
3
+ class Sanitizer #:nodoc:
4
+ # Returns all attributes not denied by the authorizer.
5
+ def sanitize(klass, attributes, authorizer)
6
+ rejected = []
7
+ sanitized_attributes = attributes.reject do |key, value|
8
+ rejected << key if authorizer.deny?(key)
9
+ end
10
+ process_removed_attributes(klass, rejected) unless rejected.empty?
11
+ sanitized_attributes
12
+ end
13
+
14
+ protected
15
+
16
+ def process_removed_attributes(klass, attrs)
17
+ raise NotImplementedError, "#process_removed_attributes(klass, attrs) is intended to be overwritten by a subclass"
18
+ end
19
+ end
20
+
21
+ class LoggerSanitizer < Sanitizer #:nodoc:
22
+ def initialize(target)
23
+ @target = target
24
+ super()
25
+ end
26
+
27
+ def logger
28
+ @target.logger
29
+ end
30
+
31
+ def logger?
32
+ @target.respond_to?(:logger) && @target.logger
33
+ end
34
+
35
+ def backtrace
36
+ if defined? Rails.backtrace_cleaner
37
+ Rails.backtrace_cleaner.clean(caller)
38
+ else
39
+ caller
40
+ end
41
+ end
42
+
43
+ def process_removed_attributes(klass, attrs)
44
+ if logger?
45
+ logger.warn do
46
+ "WARNING: Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}\n" +
47
+ backtrace.map { |trace| "\t#{trace}" }.join("\n")
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ class StrictSanitizer < Sanitizer #:nodoc:
54
+ def initialize(target = nil)
55
+ super()
56
+ end
57
+
58
+ def process_removed_attributes(klass, attrs)
59
+ unless (attrs - insensitive_attributes).empty?
60
+ raise ActiveModel::MassAssignmentSecurity::Error.new(klass, attrs)
61
+ end
62
+ end
63
+
64
+ def insensitive_attributes
65
+ ['id']
66
+ end
67
+ end
68
+
69
+ class Error < StandardError #:nodoc:
70
+ def initialize(klass, attrs)
71
+ super("Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}")
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,29 @@
1
+ require "active_record"
2
+
3
+ def active_record_40?
4
+ ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR == 0
5
+ end
6
+
7
+ require "active_record/mass_assignment_security/associations"
8
+ require "active_record/mass_assignment_security/attribute_assignment"
9
+ require "active_record/mass_assignment_security/core"
10
+ require "active_record/mass_assignment_security/nested_attributes"
11
+ require "active_record/mass_assignment_security/persistence"
12
+ require "active_record/mass_assignment_security/reflection"
13
+ require "active_record/mass_assignment_security/relation"
14
+ require "active_record/mass_assignment_security/validations"
15
+ require "active_record/mass_assignment_security/associations"
16
+ require "active_record/mass_assignment_security/inheritance"
17
+
18
+ class ActiveRecord::Base
19
+ include ActiveRecord::MassAssignmentSecurity::Core
20
+ include ActiveRecord::MassAssignmentSecurity::AttributeAssignment
21
+ include ActiveRecord::MassAssignmentSecurity::Persistence
22
+ include ActiveRecord::MassAssignmentSecurity::Validations
23
+ include ActiveRecord::MassAssignmentSecurity::NestedAttributes
24
+ include ActiveRecord::MassAssignmentSecurity::Inheritance
25
+ end
26
+
27
+ class ActiveRecord::SchemaMigration < ActiveRecord::Base
28
+ attr_accessible :version
29
+ end
@@ -0,0 +1,154 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Association
4
+ undef :build_record
5
+
6
+ def build_record(attributes, options)
7
+ reflection.build_association(attributes, options) do |record|
8
+ attributes = create_scope.except(*(record.changed - [reflection.foreign_key]))
9
+ record.assign_attributes(attributes, without_protection: true)
10
+ end
11
+ end
12
+
13
+ private :build_record
14
+ end
15
+
16
+ class CollectionAssociation
17
+ undef :build
18
+ undef :create
19
+ undef :create!
20
+
21
+ def build(attributes = {}, options = {}, &block)
22
+ if attributes.is_a?(Array)
23
+ attributes.collect { |attr| build(attr, options, &block) }
24
+ else
25
+ add_to_target(build_record(attributes, options)) do |record|
26
+ yield(record) if block_given?
27
+ end
28
+ end
29
+ end
30
+
31
+ def create(attributes = {}, options = {}, &block)
32
+ create_record(attributes, options, &block)
33
+ end
34
+
35
+ def create!(attributes = {}, options = {}, &block)
36
+ create_record(attributes, options, true, &block)
37
+ end
38
+
39
+ def create_record(attributes, options, raise = false, &block)
40
+ unless owner.persisted?
41
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
42
+ end
43
+
44
+ if attributes.is_a?(Array)
45
+ attributes.collect { |attr| create_record(attr, options, raise, &block) }
46
+ else
47
+ transaction do
48
+ add_to_target(build_record(attributes, options)) do |record|
49
+ yield(record) if block_given?
50
+ insert_record(record, true, raise)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ private :create_record
57
+ end
58
+
59
+ class CollectionProxy
60
+ undef :create
61
+ undef :create!
62
+
63
+ def build(attributes = {}, options = {}, &block)
64
+ @association.build(attributes, options, &block)
65
+ end
66
+ alias_method :new, :build
67
+
68
+ def create(attributes = {}, options = {}, &block)
69
+ @association.create(attributes, options, &block)
70
+ end
71
+
72
+ def create!(attributes = {}, options = {}, &block)
73
+ @association.create!(attributes, options, &block)
74
+ end
75
+ end
76
+
77
+ module ThroughAssociation
78
+ undef :build_record if respond_to?(:build_record, false)
79
+
80
+ private
81
+
82
+ def build_record(attributes, options={})
83
+ inverse = source_reflection.inverse_of
84
+ target = through_association.target
85
+
86
+ if inverse && target && !target.is_a?(Array)
87
+ attributes[inverse.foreign_key] = target.id
88
+ end
89
+
90
+ super(attributes, options)
91
+ end
92
+ end
93
+
94
+ class HasManyThroughAssociation
95
+ undef :build_record
96
+ undef :options_for_through_record if respond_to?(:options_for_through_record, false)
97
+
98
+ def build_record(attributes, options = {})
99
+ ensure_not_nested
100
+
101
+ record = super(attributes, options)
102
+
103
+ inverse = source_reflection.inverse_of
104
+ if inverse
105
+ if inverse.macro == :has_many
106
+ record.send(inverse.name) << build_through_record(record)
107
+ elsif inverse.macro == :has_one
108
+ record.send("#{inverse.name}=", build_through_record(record))
109
+ end
110
+ end
111
+
112
+ record
113
+ end
114
+ private :build_record
115
+
116
+ def options_for_through_record
117
+ [through_scope_attributes, without_protection: true]
118
+ end
119
+ private :options_for_through_record
120
+ end
121
+
122
+ class SingularAssociation
123
+ undef :create
124
+ undef :create!
125
+ undef :build
126
+
127
+ def create(attributes = {}, options = {}, &block)
128
+ create_record(attributes, options, &block)
129
+ end
130
+
131
+ def create!(attributes = {}, options = {}, &block)
132
+ create_record(attributes, options, true, &block)
133
+ end
134
+
135
+ def build(attributes = {}, options = {})
136
+ record = build_record(attributes, options)
137
+ yield(record) if block_given?
138
+ set_new_record(record)
139
+ record
140
+ end
141
+
142
+ def create_record(attributes, options = {}, raise_error = false)
143
+ record = build_record(attributes, options)
144
+ yield(record) if block_given?
145
+ saved = record.save
146
+ set_new_record(record)
147
+ raise RecordInvalid.new(record) if !saved && raise_error
148
+ record
149
+ end
150
+
151
+ private :create_record
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,88 @@
1
+ require 'active_model/mass_assignment_security'
2
+ require 'active_record'
3
+
4
+ module ActiveRecord
5
+ module MassAssignmentSecurity
6
+ module AttributeAssignment
7
+ extend ActiveSupport::Concern
8
+ include ActiveModel::MassAssignmentSecurity
9
+
10
+ module ClassMethods
11
+ private
12
+
13
+ # The primary key and inheritance column can never be set by mass-assignment for security reasons.
14
+ def attributes_protected_by_default
15
+ default = [ primary_key, inheritance_column ]
16
+ default << 'id' unless primary_key.eql? 'id'
17
+ default
18
+ end
19
+ end
20
+
21
+ # Allows you to set all the attributes for a particular mass-assignment
22
+ # security role by passing in a hash of attributes with keys matching
23
+ # the attribute names (which again matches the column names) and the role
24
+ # name using the :as option.
25
+ #
26
+ # To bypass mass-assignment security you can use the :without_protection => true
27
+ # option.
28
+ #
29
+ # class User < ActiveRecord::Base
30
+ # attr_accessible :name
31
+ # attr_accessible :name, :is_admin, :as => :admin
32
+ # end
33
+ #
34
+ # user = User.new
35
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true })
36
+ # user.name # => "Josh"
37
+ # user.is_admin? # => false
38
+ #
39
+ # user = User.new
40
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
41
+ # user.name # => "Josh"
42
+ # user.is_admin? # => true
43
+ #
44
+ # user = User.new
45
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
46
+ # user.name # => "Josh"
47
+ # user.is_admin? # => true
48
+ def assign_attributes(new_attributes, options = {})
49
+ return if new_attributes.blank?
50
+
51
+ attributes = new_attributes.stringify_keys
52
+ multi_parameter_attributes = []
53
+ nested_parameter_attributes = []
54
+ previous_options = @mass_assignment_options
55
+ @mass_assignment_options = options
56
+
57
+ unless options[:without_protection]
58
+ attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
59
+ end
60
+
61
+ attributes.each do |k, v|
62
+ if k.include?("(")
63
+ multi_parameter_attributes << [ k, v ]
64
+ elsif v.is_a?(Hash)
65
+ nested_parameter_attributes << [ k, v ]
66
+ else
67
+ _assign_attribute(k, v)
68
+ end
69
+ end
70
+
71
+ assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
72
+ assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
73
+ ensure
74
+ @mass_assignment_options = previous_options
75
+ end
76
+
77
+ protected
78
+
79
+ def mass_assignment_options
80
+ @mass_assignment_options ||= {}
81
+ end
82
+
83
+ def mass_assignment_role
84
+ mass_assignment_options[:as] || :default
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module MassAssignmentSecurity
3
+ module Core
4
+
5
+ private
6
+
7
+ def init_attributes(attributes, options)
8
+ assign_attributes(attributes, options)
9
+ end
10
+
11
+ def init_internals
12
+ super
13
+ @mass_assignment_options = nil
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module ActiveRecord
2
+ module MassAssignmentSecurity
3
+ module Inheritance
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ private
8
+ # Detect the subclass from the inheritance column of attrs. If the inheritance column value
9
+ # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
10
+ # If this is a StrongParameters hash, and access to inheritance_column is not permitted,
11
+ # this will ignore the inheritance column and return nil
12
+ def subclass_from_attributes?(attrs)
13
+ active_authorizer[:default].deny?(inheritance_column) ? nil : super
14
+ end
15
+
16
+ # Support Active Record <= 4.0.3, which uses the old method signature.
17
+ def subclass_from_attrs(attrs)
18
+ active_authorizer[:default].deny?(inheritance_column) ? nil : super
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,157 @@
1
+ module ActiveRecord
2
+ module MassAssignmentSecurity
3
+ module NestedAttributes
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
9
+
10
+ def accepts_nested_attributes_for(*attr_names)
11
+ options = { :allow_destroy => false, :update_only => false }
12
+ options.update(attr_names.extract_options!)
13
+ options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
14
+ options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
15
+
16
+ attr_names.each do |association_name|
17
+ if reflection = reflect_on_association(association_name)
18
+ if active_record_40?
19
+ reflection.options[:autosave] = true
20
+ else
21
+ reflection.autosave = true
22
+ end
23
+ add_autosave_association_callbacks(reflection)
24
+
25
+ nested_attributes_options = self.nested_attributes_options.dup
26
+ nested_attributes_options[association_name.to_sym] = options
27
+ self.nested_attributes_options = nested_attributes_options
28
+
29
+ type = (reflection.collection? ? :collection : :one_to_one)
30
+
31
+ generated_methods_module = active_record_40? ? generated_feature_methods : generated_association_methods
32
+
33
+ # def pirate_attributes=(attributes)
34
+ # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
35
+ # end
36
+ generated_methods_module.module_eval <<-eoruby, __FILE__, __LINE__ + 1
37
+ if method_defined?(:#{association_name}_attributes=)
38
+ remove_method(:#{association_name}_attributes=)
39
+ end
40
+ def #{association_name}_attributes=(attributes)
41
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
42
+ end
43
+ eoruby
44
+ else
45
+ raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ UNASSIGNABLE_KEYS = %w( id _destroy )
54
+
55
+ def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {})
56
+ options = self.nested_attributes_options[association_name]
57
+ attributes = attributes.with_indifferent_access
58
+
59
+ if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
60
+ (options[:update_only] || record.id.to_s == attributes['id'].to_s)
61
+ assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes)
62
+
63
+ elsif attributes['id'].present? && !assignment_opts[:without_protection]
64
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
65
+
66
+ elsif !reject_new_record?(association_name, attributes)
67
+ method = "build_#{association_name}"
68
+ if respond_to?(method)
69
+ send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
70
+ else
71
+ raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
72
+ end
73
+ end
74
+ end
75
+
76
+ def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {})
77
+ options = self.nested_attributes_options[association_name]
78
+
79
+ unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
80
+ raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
81
+ end
82
+
83
+ if limit = options[:limit]
84
+ limit = case limit
85
+ when Symbol
86
+ send(limit)
87
+ when Proc
88
+ limit.call
89
+ else
90
+ limit
91
+ end
92
+
93
+ if limit && attributes_collection.size > limit
94
+ raise ::ActiveRecord::NestedAttributes::TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
95
+ end
96
+ end
97
+
98
+ if attributes_collection.is_a? Hash
99
+ keys = attributes_collection.keys
100
+ attributes_collection = if keys.include?('id') || keys.include?(:id)
101
+ [attributes_collection]
102
+ else
103
+ attributes_collection.values
104
+ end
105
+ end
106
+
107
+ association = association(association_name)
108
+
109
+ existing_records = if association.loaded?
110
+ association.target
111
+ else
112
+ attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
113
+ attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
114
+ end
115
+
116
+ attributes_collection.each do |attributes|
117
+ attributes = attributes.with_indifferent_access
118
+
119
+ if attributes['id'].blank?
120
+ unless reject_new_record?(association_name, attributes)
121
+ association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
122
+ end
123
+ elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
124
+ unless association.loaded? || call_reject_if(association_name, attributes)
125
+ # Make sure we are operating on the actual object which is in the association's
126
+ # proxy_target array (either by finding it, or adding it if not found)
127
+ target_record = association.target.detect { |record| record == existing_record }
128
+
129
+ if target_record
130
+ existing_record = target_record
131
+ else
132
+ association.add_to_target(existing_record)
133
+ end
134
+ end
135
+
136
+ if !call_reject_if(association_name, attributes)
137
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts)
138
+ end
139
+ elsif assignment_opts[:without_protection]
140
+ association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
141
+ else
142
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
143
+ end
144
+ end
145
+ end
146
+
147
+ def assign_to_or_mark_for_destruction(record, attributes, allow_destroy, assignment_opts)
148
+ record.assign_attributes(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
149
+ record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
150
+ end
151
+
152
+ def unassignable_keys(assignment_opts)
153
+ assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS
154
+ end
155
+ end
156
+ end
157
+ end