PageRenderTime 45ms CodeModel.GetById 8ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 0ms

/spec/support/shared/examples/msf/module_manager/cache.rb

https://github.com/debbiemezyene/metasploit-framework
Ruby | 480 lines | 368 code | 102 blank | 10 comment | 8 complexity | c545100851f18575dc9630d90e3df2ec MD5 | raw file
  1shared_examples_for 'Msf::ModuleManager::Cache' do
  2  let(:parent_path) do
  3    parent_pathname.to_path
  4  end
  5
  6  let(:parent_pathname) do
  7    Metasploit::Framework.root.join('modules')
  8  end
  9
 10  let(:reference_name) do
 11    'windows/smb/ms08_067_netapi'
 12  end
 13
 14  let(:type) do
 15    'exploit'
 16  end
 17
 18  let(:path) do
 19    pathname.to_path
 20  end
 21
 22  let(:pathname) do
 23    parent_pathname.join(
 24        'exploits',
 25        "#{reference_name}.rb"
 26    )
 27  end
 28
 29  let(:pathname_modification_time) do
 30    pathname.mtime
 31  end
 32
 33  context '#cache_empty?' do
 34    subject(:cache_empty?) do
 35      module_manager.cache_empty?
 36    end
 37
 38    before(:each) do
 39      module_manager.send(:module_info_by_path=, module_info_by_path)
 40    end
 41
 42    context 'with empty' do
 43      let(:module_info_by_path) do
 44        {}
 45      end
 46
 47      it { should be_true }
 48    end
 49
 50    context 'without empty' do
 51      let(:module_info_by_path) do
 52        {
 53            'path/to/module' => {}
 54        }
 55      end
 56
 57      it { should be_false }
 58    end
 59  end
 60
 61  context '#cache_in_memory' do
 62    def cache_in_memory
 63      module_manager.cache_in_memory(
 64          class_or_module,
 65          :path => path,
 66          :reference_name => reference_name,
 67          :type => type
 68      )
 69    end
 70
 71    def module_info_by_path
 72      module_manager.send(:module_info_by_path)
 73    end
 74
 75    let(:class_or_module) do
 76      double('Class<Msf::Module> or Module', :parent => namespace_module)
 77    end
 78
 79    let(:namespace_module) do
 80      double('Msf::Modules::Namespace', :parent_path => parent_path)
 81    end
 82
 83    context 'with existing :path' do
 84      it 'should update module_info_by_path' do
 85        expect {
 86          cache_in_memory
 87        }.to change { module_info_by_path }
 88      end
 89
 90      context 'module_info_by_path' do
 91        subject(:module_info_by_path) do
 92          module_manager.send(:module_info_by_path)
 93        end
 94
 95        before(:each) do
 96          cache_in_memory
 97        end
 98
 99        it 'should have entry for path' do
100          module_info_by_path[path].should be_a Hash
101        end
102
103        context 'value' do
104          subject(:value) do
105            module_info_by_path[path]
106          end
107
108          it 'should have modification time of :path option for :modification_time' do
109            value[:modification_time].should == pathname_modification_time
110          end
111
112          it 'should have parent path from namespace module for :parent_path' do
113            value[:parent_path].should == namespace_module.parent_path
114          end
115
116          it 'should use :reference_name option' do
117            value[:reference_name].should == reference_name
118          end
119
120          it 'should use :type option' do
121            value[:type].should == type
122          end
123        end
124      end
125    end
126
127    context 'without existing :path' do
128      let(:path) do
129        'non/existent/path'
130      end
131
132      it 'should not raise error' do
133        expect {
134          cache_in_memory
135        }.to_not raise_error
136      end
137
138      it 'should not update module_info_by_path' do
139        expect {
140          cache_in_memory
141        }.to_not change { module_info_by_path }
142      end
143    end
144  end
145
146  context '#load_cached_module' do
147    subject(:load_cached_module) do
148      module_manager.load_cached_module(type, reference_name)
149    end
150
151    before(:each) do
152      module_manager.send(:module_info_by_path=, module_info_by_path)
153    end
154
155    context 'with module info in cache' do
156      let(:module_info_by_path) do
157        {
158            'path/to/module' => {
159                :parent_path => parent_path,
160                :reference_name => reference_name,
161                :type => type
162            }
163        }
164      end
165
166      it 'should enumerate loaders until if it find the one where loadable?(parent_path) is true' do
167        module_manager.send(:loaders).each do |loader|
168          loader.should_receive(:loadable?).with(parent_path).and_call_original
169        end
170
171        load_cached_module
172      end
173
174      it 'should force load using #load_module on the loader' do
175        Msf::Modules::Loader::Directory.any_instance.should_receive(
176            :load_module
177        ).with(
178            parent_path,
179            type,
180            reference_name,
181            :force => true
182        ).and_call_original
183
184        load_cached_module
185      end
186
187      context 'return from load_module' do
188        before(:each) do
189          module_manager.send(:loaders).each do |loader|
190            loader.stub(:load_module => module_loaded)
191          end
192        end
193
194        context 'with false' do
195          let(:module_loaded) do
196            false
197          end
198
199          it { should be_false }
200        end
201
202        context 'with true' do
203          let(:module_loaded) do
204            true
205          end
206
207          it { should be_true }
208        end
209      end
210    end
211
212    context 'without module info in cache' do
213      let(:module_info_by_path) do
214        {}
215      end
216
217      it { should be_false }
218    end
219  end
220
221  context '#refresh_cache_from_module_files' do
222    before(:each) do
223      module_manager.stub(:framework_migrated? => framework_migrated?)
224    end
225
226    context 'with framework migrated' do
227      let(:framework_migrated?) do
228        true
229      end
230
231      context 'with module argument' do
232        def refresh_cache_from_module_files
233          module_manager.refresh_cache_from_module_files(module_class_or_instance)
234        end
235
236        let(:module_class_or_instance) do
237          Class.new(Msf::Module)
238        end
239
240        it 'should update database and then update in-memory cache from the database for the given module_class_or_instance' do
241          framework.db.should_receive(:update_module_details).with(module_class_or_instance).ordered
242          module_manager.should_receive(:refresh_cache_from_database).ordered
243
244          refresh_cache_from_module_files
245        end
246      end
247
248      context 'without module argument' do
249        def refresh_cache_from_module_files
250          module_manager.refresh_cache_from_module_files
251        end
252
253        it 'should update database and then update in-memory cache from the database for all modules' do
254          framework.db.should_receive(:update_all_module_details).ordered
255          module_manager.should_receive(:refresh_cache_from_database)
256
257          refresh_cache_from_module_files
258        end
259      end
260    end
261
262    context 'without framework migrated' do
263      def refresh_cache_from_module_files
264        module_manager.refresh_cache_from_module_files
265      end
266
267      let(:framework_migrated?) do
268        false
269      end
270
271      it 'should not call Msf::DBManager#update_module_details' do
272        framework.db.should_not_receive(:update_module_details)
273
274        refresh_cache_from_module_files
275      end
276
277      it 'should not call Msf::DBManager#update_all_module_details' do
278        framework.db.should_not_receive(:update_all_module_details)
279
280        refresh_cache_from_module_files
281      end
282
283      it 'should not call #refresh_cache_from_database' do
284        module_manager.should_not_receive(:refresh_cache_from_database)
285
286        refresh_cache_from_module_files
287      end
288    end
289  end
290
291  context '#refresh_cache_from_database' do
292    def refresh_cache_from_database
293      module_manager.refresh_cache_from_database
294    end
295
296    it 'should call #module_info_by_path_from_database!' do
297      module_manager.should_receive(:module_info_by_path_from_database!)
298
299      refresh_cache_from_database
300    end
301  end
302
303  context '#framework_migrated?' do
304    subject(:framework_migrated?) do
305      module_manager.send(:framework_migrated?)
306    end
307
308    context 'with framework database' do
309      before(:each) do
310        framework.db.stub(:migrated => migrated)
311      end
312
313      context 'with migrated' do
314        let(:migrated) do
315          true
316        end
317
318        it { should be_true }
319      end
320
321      context 'without migrated' do
322        let(:migrated) do
323          false
324        end
325
326        it { should be_false }
327      end
328    end
329
330    context 'without framework database' do
331      before(:each) do
332        framework.stub(:db => nil)
333      end
334
335      it { should be_false }
336    end
337  end
338
339  context '#module_info_by_path' do
340    it { should respond_to(:module_info_by_path) }
341  end
342
343  context '#module_info_by_path=' do
344    it { should respond_to(:module_info_by_path=) }
345  end
346
347  context '#module_info_by_path_from_database!' do
348    def module_info_by_path
349      module_manager.send(:module_info_by_path)
350    end
351
352    def module_info_by_path_from_database!
353      module_manager.send(:module_info_by_path_from_database!)
354    end
355
356    before(:each) do
357      module_manager.stub(:framework_migrated? => framework_migrated?)
358    end
359
360    context 'with framework migrated' do
361      include_context 'DatabaseCleaner'
362
363      let(:framework_migrated?) do
364        true
365      end
366
367      before(:each) do
368        configurations = Metasploit::Framework::Database.configurations
369        spec = configurations[Metasploit::Framework.env]
370
371        # Need to connect or ActiveRecord::Base.connection_pool will raise an
372        # error.
373        framework.db.connect(spec)
374      end
375
376      it 'should call ActiveRecord::Base.connection_pool.with_connection' do
377        # 1st is from with_established_connection
378        # 2nd is from module_info_by_path_from_database!
379        ActiveRecord::Base.connection_pool.should_receive(:with_connection).at_least(2).times
380
381        module_info_by_path_from_database!
382      end
383
384      it 'should use ActiveRecord::Batches#find_each to enumerate Mdm::Module::Details in batches' do
385        Mdm::Module::Detail.should_receive(:find_each)
386
387        module_info_by_path_from_database!
388      end
389
390      context 'with database cache' do
391        #
392        # Let!s (let + before(:each))
393        #
394
395        let!(:mdm_module_detail) do
396          FactoryGirl.create(:mdm_module_detail,
397                             :file => path,
398                             :mtype => type,
399                             :mtime => pathname.mtime,
400                             :refname => reference_name
401          )
402        end
403
404        it 'should create cache entry for path' do
405          module_info_by_path_from_database!
406
407          module_info_by_path.should have_key(path)
408        end
409
410        it 'should use Msf::Modules::Loader::Base.typed_path to derive parent_path' do
411          Msf::Modules::Loader::Base.should_receive(:typed_path).with(type, reference_name).and_call_original
412
413          module_info_by_path_from_database!
414        end
415
416        context 'cache entry' do
417          subject(:cache_entry) do
418            module_info_by_path[path]
419          end
420
421          before(:each) do
422            module_info_by_path_from_database!
423          end
424
425          its([:modification_time]) { should be_within(1.second).of(pathname_modification_time) }
426          its([:parent_path]) { should == parent_path }
427          its([:reference_name]) { should == reference_name }
428          its([:type]) { should == type }
429        end
430
431        context 'typed module set' do
432          let(:typed_module_set) do
433            module_manager.module_set(type)
434          end
435
436          context 'with reference_name' do
437            before(:each) do
438              typed_module_set[reference_name] = double('Msf::Module')
439            end
440
441            it 'should not change reference_name value' do
442              expect {
443                module_info_by_path_from_database!
444              }.to_not change {
445                typed_module_set[reference_name]
446              }
447            end
448          end
449
450          context 'without reference_name' do
451            it 'should set reference_name value to Msf::SymbolicModule' do
452              module_info_by_path_from_database!
453
454              # have to use fetch because [] will trigger de-symbolization and
455              # instantiation.
456              typed_module_set.fetch(reference_name).should == Msf::SymbolicModule
457            end
458          end
459        end
460      end
461    end
462
463    context 'without framework migrated' do
464      let(:framework_migrated?) do
465        false
466      end
467
468      it { should_not query_the_database.when_calling(:module_info_by_path_from_database!) }
469
470      it 'should reset #module_info_by_path' do
471        # pre-fill module_info_by_path so change can be detected
472        module_manager.send(:module_info_by_path=, double('In-memory Cache'))
473
474        module_info_by_path_from_database!
475
476        module_info_by_path.should be_empty
477      end
478    end
479  end
480end