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.



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/heimdallr/proxy/collection.rb', line 241

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.



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

delegate_as_records :all

#any?Object

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



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

delegate_as_value :any?

#averageObject

A proxy for average method which returns a raw value.



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

delegate_as_value :average

#buildObject

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



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

delegate_as_constructor :build,   :assign_attributes

#calculateObject

A proxy for calculate method which returns a raw value.



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

delegate_as_value :calculate

#countObject

A proxy for count method which returns a raw value.



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

delegate_as_value :count

#creatable?Boolean

Returns:

  • (Boolean)


305
306
307
# File 'lib/heimdallr/proxy/collection.rb', line 305

def creatable?
  @restrictions.can? :create
end

#createObject

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



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

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.



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

delegate_as_constructor :create!, :update_attributes!

#deleteObject

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



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

delegate_as_destroyer :delete

#delete_allObject

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



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

delegate_as_destroyer :delete_all

#destroyObject

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



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

delegate_as_destroyer :destroy

#destroy_allObject

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



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

delegate_as_destroyer :destroy_all

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

A proxy for each which restricts the yielded records.

Yields:

  • (record)

Yield Parameters:



234
235
236
237
238
# File 'lib/heimdallr/proxy/collection.rb', line 234

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.



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

delegate_as_value :empty?

#exists?Object

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



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

delegate_as_value :exists?

#extendingObject

A proxy for extending method which returns a restricted scope.



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

delegate_as_scope :extending

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

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

Returns:



218
219
220
221
222
223
224
225
226
227
228
# File 'lib/heimdallr/proxy/collection.rb', line 218

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.



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

delegate_as_record  :first

#first!Object

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



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

delegate_as_record  :first!

#include?Object

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



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

delegate_as_value :include?

#includes(*associations) ⇒ Object

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



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
205
206
207
208
209
210
211
212
213
# File 'lib/heimdallr/proxy/collection.rb', line 166

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



261
262
263
# File 'lib/heimdallr/proxy/collection.rb', line 261

def insecure
  @scope
end

#insecurely(*args, &block) ⇒ Proxy::Collection

Insecurely taps method saving restricted context for the result Method (or block) is supposed to return proper relation

Returns:



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

def insecurely(*args, &block)
  if block_given?
    result = yield @scope
  else
    method = args.shift
    result = @scope.send method, *args
  end

  Proxy::Collection.new(@context, result, options_with_escape)
end

#inspectString

Describes the proxy and proxified scope.

Returns:

  • (String)


283
284
285
# File 'lib/heimdallr/proxy/collection.rb', line 283

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

#joinsObject

A proxy for joins method which returns a restricted scope.



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

delegate_as_scope :joins

#klassObject

A proxy for klass method which returns a raw value.



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

delegate_as_value :klass

#lastObject

A proxy for last method which returns a restricted record.



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

delegate_as_record  :last

#last!Object

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



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

delegate_as_record  :last!

#lengthObject

A proxy for length method which returns a raw value.



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

delegate_as_value :length

#limitObject

A proxy for limit method which returns a restricted scope.



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

delegate_as_scope :limit

#lockObject

A proxy for lock method which returns a restricted scope.



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

delegate_as_scope :lock

#many?Object

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



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

delegate_as_value :many?

#maximumObject

A proxy for maximum method which returns a raw value.



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

delegate_as_value :maximum

#minimumObject

A proxy for minimum method which returns a raw value.



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

delegate_as_value :minimum

#model_nameObject

A proxy for model_name method which returns a raw value.



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

delegate_as_value :model_name

#newObject

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



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

delegate_as_constructor :new,     :assign_attributes

#offsetObject

A proxy for offset method which returns a restricted scope.



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

delegate_as_scope :offset

#orderObject

A proxy for order method which returns a restricted scope.



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

delegate_as_scope :order

#pluckObject

A proxy for pluck method which returns a raw value.



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

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)


295
296
297
298
299
300
301
302
303
# File 'lib/heimdallr/proxy/collection.rb', line 295

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.



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

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.



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

delegate_as_scope :reverse_order

#scopedObject

A proxy for scoped method which returns a restricted scope.



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

delegate_as_scope :scoped

#sizeObject

A proxy for size method which returns a raw value.



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

delegate_as_value :size

#sumObject

A proxy for sum method which returns a raw value.



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

delegate_as_value :sum

#to_aObject

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



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

delegate_as_records :to_a

#to_aryObject

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



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

delegate_as_records :to_ary

#uniqObject

A proxy for uniq method which returns a restricted scope.



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

delegate_as_scope :uniq

#whereObject

A proxy for where method which returns a restricted scope.



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

delegate_as_scope :where