Class: Heimdallr::Proxy::Collection

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/heimdallr/proxy/collection.rb

Overview

A security-aware proxy for ActiveRecord scopes. This class validates all the method calls and either forwards them to the encapsulated scope or raises an exception.

There are two kinds of collection proxies, explicit and implicit, which instantiate the corresponding types of record proxies. See also Record.

Instance Method Summary collapse

Constructor Details

#initialize(context, scope, options = {}) ⇒ Collection

Create a collection proxy.

The scope is expected to be already restricted with :fetch scope.

Parameters:

  • context

    security context

  • scope

    proxified scope

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • implicit (Boolean)

    proxy type



18
19
20
21
22
23
# File 'lib/heimdallr/proxy/collection.rb', line 18

def initialize(context, scope, options={})
  @context, @scope, @options = context, scope, options

  @restrictions = @scope.restrictions(context)
  @options[:eager_loaded] ||= {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

Wraps a scope or a record in a corresponding proxy.



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/heimdallr/proxy/collection.rb', line 232

def method_missing(method, *args)
  if method =~ /^find_all_by/
    @scope.send(method, *args).map do |element|
      element.restrict(@context, options_with_escape)
    end
  elsif method =~ /^find_by/
    @scope.send(method, *args).restrict(@context, options_with_escape)
  elsif @scope.heimdallr_scopes && @scope.heimdallr_scopes.include?(method)
    Proxy::Collection.new(@context, @scope.send(method, *args), options_with_escape)
  elsif @scope.respond_to? method
    raise InsecureOperationError,
        "Potentially insecure method #{method} was called"
  else
    super
  end
end

Instance Method Details

#allObject

A proxy for all method which returns an array of restricted records.



151
# File 'lib/heimdallr/proxy/collection.rb', line 151

delegate_as_records :all

#any?Object

A proxy for any? method which returns a raw value.



126
# File 'lib/heimdallr/proxy/collection.rb', line 126

delegate_as_value :any?

#averageObject

A proxy for average method which returns a raw value.



135
# File 'lib/heimdallr/proxy/collection.rb', line 135

delegate_as_value :average

#buildObject

A proxy for build method which adds fixtures to the attribute list and returns a restricted record.



108
# File 'lib/heimdallr/proxy/collection.rb', line 108

delegate_as_constructor :build,   :assign_attributes

#calculateObject

A proxy for calculate method which returns a raw value.



133
# File 'lib/heimdallr/proxy/collection.rb', line 133

delegate_as_value :calculate

#countObject

A proxy for count method which returns a raw value.



134
# File 'lib/heimdallr/proxy/collection.rb', line 134

delegate_as_value :count

#creatable?Boolean

Returns:

  • (Boolean)


281
282
283
# File 'lib/heimdallr/proxy/collection.rb', line 281

def creatable?
  @restrictions.can? :create
end

#createObject

A proxy for create method which adds fixtures to the attribute list and returns a restricted record.



110
# File 'lib/heimdallr/proxy/collection.rb', line 110

delegate_as_constructor :create,  :update_attributes

#create!Object

A proxy for create! method which adds fixtures to the attribute list and returns a restricted record.



111
# File 'lib/heimdallr/proxy/collection.rb', line 111

delegate_as_constructor :create!, :update_attributes!

#deleteObject

A proxy for delete method which works on a :delete scope.



141
# File 'lib/heimdallr/proxy/collection.rb', line 141

delegate_as_destroyer :delete

#delete_allObject

A proxy for delete_all method which works on a :delete scope.



142
# File 'lib/heimdallr/proxy/collection.rb', line 142

delegate_as_destroyer :delete_all

#destroyObject

A proxy for destroy method which works on a :delete scope.



143
# File 'lib/heimdallr/proxy/collection.rb', line 143

delegate_as_destroyer :destroy

#destroy_allObject

A proxy for destroy_all method which works on a :delete scope.



144
# File 'lib/heimdallr/proxy/collection.rb', line 144

delegate_as_destroyer :destroy_all

#each {|record| ... } ⇒ Object

A proxy for each which restricts the yielded records.

Yields:

  • (record)

Yield Parameters:



225
226
227
228
229
# File 'lib/heimdallr/proxy/collection.rb', line 225

def each
  @scope.each do |record|
    yield record.restrict(@context, options_with_eager_load)
  end
end

#empty?Object

A proxy for empty? method which returns a raw value.



125
# File 'lib/heimdallr/proxy/collection.rb', line 125

delegate_as_value :empty?

#exists?Object

A proxy for exists? method which returns a raw value.



129
# File 'lib/heimdallr/proxy/collection.rb', line 129

delegate_as_value :exists?

#extendingObject

A proxy for extending method which returns a restricted scope.



123
# File 'lib/heimdallr/proxy/collection.rb', line 123

delegate_as_scope :extending

#find(*args) ⇒ Proxy::Record+

A proxy for find which restricts the returned record or records.

Returns:



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/heimdallr/proxy/collection.rb', line 209

def find(*args)
  result = @scope.find(*args)

  if result.is_a? Enumerable
    result.map do |element|
      element.restrict(@context, options_with_eager_load)
    end
  else
    result.restrict(@context, options_with_eager_load)
  end
end

#firstObject

A proxy for first method which returns a restricted record.



146
# File 'lib/heimdallr/proxy/collection.rb', line 146

delegate_as_record  :first

#first!Object

A proxy for first! method which returns a restricted record.



147
# File 'lib/heimdallr/proxy/collection.rb', line 147

delegate_as_record  :first!

#include?Object

A proxy for include? method which returns a raw value.



128
# File 'lib/heimdallr/proxy/collection.rb', line 128

delegate_as_value :include?

#includes(*associations) ⇒ Object

A proxy for includes which adds Heimdallr conditions for eager loaded associations.



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/heimdallr/proxy/collection.rb', line 157

def includes(*associations)
  # Normalize association list to strict nested hash.
  normalize = ->(list) {
    if list.is_a? Array
      list.map(&normalize).reduce(:merge)
    elsif list.is_a? Symbol
      { list => {} }
    elsif list.is_a? Hash
      hash = {}
      list.each do |key, value|
        hash[key] = normalize.(value)
      end
      hash
    end
  }
  associations = normalize.(associations)

  current_scope = @scope.includes(associations)

  add_conditions = ->(associations, scope) {
    associations.each do |association, nested|
      reflection = scope.reflect_on_association(association)
      if reflection && !reflection.options[:polymorphic]
        associated_klass = reflection.klass

        if associated_klass.respond_to? :restrict
          nested_scope = associated_klass.restrictions(@context).request_scope(:fetch)

          where_values = nested_scope.where_values
          if where_values.any?
            current_scope = current_scope.where(*where_values)
          end

          add_conditions.(nested, associated_klass)
        end
      end
    end
  }

  unless Heimdallr.skip_eager_condition_injection
    add_conditions.(associations, current_scope)
  end

  options = @options.merge(eager_loaded:
    @options[:eager_loaded].merge(associations))

  Proxy::Collection.new(@context, current_scope, options)
end

#insecureObject

Return the underlying scope.

Returns:

  • ActiveRecord scope



252
253
254
# File 'lib/heimdallr/proxy/collection.rb', line 252

def insecure
  @scope
end

#inspectString

Describes the proxy and proxified scope.

Returns:

  • (String)


259
260
261
# File 'lib/heimdallr/proxy/collection.rb', line 259

def inspect
  "#<Heimdallr::Proxy::Collection: #{@scope.to_sql}>"
end

#joinsObject

A proxy for joins method which returns a restricted scope.



116
# File 'lib/heimdallr/proxy/collection.rb', line 116

delegate_as_scope :joins

#lastObject

A proxy for last method which returns a restricted record.



148
# File 'lib/heimdallr/proxy/collection.rb', line 148

delegate_as_record  :last

#last!Object

A proxy for last! method which returns a restricted record.



149
# File 'lib/heimdallr/proxy/collection.rb', line 149

delegate_as_record  :last!

#lengthObject

A proxy for length method which returns a raw value.



131
# File 'lib/heimdallr/proxy/collection.rb', line 131

delegate_as_value :length

#limitObject

A proxy for limit method which returns a restricted scope.



118
# File 'lib/heimdallr/proxy/collection.rb', line 118

delegate_as_scope :limit

#lockObject

A proxy for lock method which returns a restricted scope.



117
# File 'lib/heimdallr/proxy/collection.rb', line 117

delegate_as_scope :lock

#many?Object

A proxy for many? method which returns a raw value.



127
# File 'lib/heimdallr/proxy/collection.rb', line 127

delegate_as_value :many?

#maximumObject

A proxy for maximum method which returns a raw value.



137
# File 'lib/heimdallr/proxy/collection.rb', line 137

delegate_as_value :maximum

#minimumObject

A proxy for minimum method which returns a raw value.



138
# File 'lib/heimdallr/proxy/collection.rb', line 138

delegate_as_value :minimum

#newObject

A proxy for new method which adds fixtures to the attribute list and returns a restricted record.



109
# File 'lib/heimdallr/proxy/collection.rb', line 109

delegate_as_constructor :new,     :assign_attributes

#offsetObject

A proxy for offset method which returns a restricted scope.



119
# File 'lib/heimdallr/proxy/collection.rb', line 119

delegate_as_scope :offset

#orderObject

A proxy for order method which returns a restricted scope.



120
# File 'lib/heimdallr/proxy/collection.rb', line 120

delegate_as_scope :order

#pluckObject

A proxy for pluck method which returns a raw value.



139
# File 'lib/heimdallr/proxy/collection.rb', line 139

delegate_as_value :pluck

#reflect_on_securityHash

Return the associated security metadata. The returned hash will contain keys :context, :scope and :options, corresponding to the parameters in #initialize, :model and :restrictions, representing the model class.

Such a name was deliberately selected for this method in order to reduce namespace pollution.

Returns:

  • (Hash)


271
272
273
274
275
276
277
278
279
# File 'lib/heimdallr/proxy/collection.rb', line 271

def reflect_on_security
  {
    model:        @scope,
    context:      @context,
    scope:        @scope,
    options:      @options,
    restrictions: @restrictions,
  }.merge(@restrictions.reflection)
end

#reorderObject

A proxy for reorder method which returns a restricted scope.



121
# File 'lib/heimdallr/proxy/collection.rb', line 121

delegate_as_scope :reorder

#restrict(context, options = nil) ⇒ Object

Collections cannot be restricted with different context or options.

Returns:

  • self

Raises:

  • (RuntimeError)


29
30
31
32
33
34
35
# File 'lib/heimdallr/proxy/collection.rb', line 29

def restrict(context, options=nil)
  if @context == context && options.nil?
    self
  else
    raise RuntimeError, "Heimdallr proxies cannot be restricted with nonmatching context or options"
  end
end

#reverse_orderObject

A proxy for reverse_order method which returns a restricted scope.



122
# File 'lib/heimdallr/proxy/collection.rb', line 122

delegate_as_scope :reverse_order

#scopedObject

A proxy for scoped method which returns a restricted scope.



113
# File 'lib/heimdallr/proxy/collection.rb', line 113

delegate_as_scope :scoped

#sizeObject

A proxy for size method which returns a raw value.



130
# File 'lib/heimdallr/proxy/collection.rb', line 130

delegate_as_value :size

#sumObject

A proxy for sum method which returns a raw value.



136
# File 'lib/heimdallr/proxy/collection.rb', line 136

delegate_as_value :sum

#to_aObject

A proxy for to_a method which returns an array of restricted records.



152
# File 'lib/heimdallr/proxy/collection.rb', line 152

delegate_as_records :to_a

#to_aryObject

A proxy for to_ary method which returns an array of restricted records.



153
# File 'lib/heimdallr/proxy/collection.rb', line 153

delegate_as_records :to_ary

#uniqObject

A proxy for uniq method which returns a restricted scope.



114
# File 'lib/heimdallr/proxy/collection.rb', line 114

delegate_as_scope :uniq

#whereObject

A proxy for where method which returns a restricted scope.



115
# File 'lib/heimdallr/proxy/collection.rb', line 115

delegate_as_scope :where