RSpec.shared_examples_for 'Msf::ModuleManager::Cache' do # Wait for data to be loaded before(:all) do Msf::Modules::Metadata::Cache.instance.get_metadata end let(:parent_path) do parent_pathname.to_path end let(:metadata_cache) do Msf::Modules::Metadata::Cache.instance end let(:parent_pathname) do Metasploit::Framework.root.join('modules') end let(:reference_name) do 'windows/smb/ms08_067_netapi' end let(:type) do 'exploit' end let(:path) do pathname.to_path end let(:pathname) do parent_pathname.join( 'exploits', "#{reference_name}.rb" ) end let(:pathname_modification_time) do pathname.mtime end context '#cache_empty?' do subject(:cache_empty?) do module_manager.cache_empty? end before(:example) do module_manager.send(:module_info_by_path=, module_info_by_path) end context 'with empty' do let(:module_info_by_path) do {} end it { is_expected.to be_truthy } end context 'without empty' do let(:module_info_by_path) do { 'path/to/module' => {} } end it { is_expected.to be_falsey } end end context '#cache_in_memory' do def cache_in_memory module_manager.cache_in_memory( class_or_module, :path => path, :reference_name => reference_name, :type => type ) end def module_info_by_path module_manager.send(:module_info_by_path) end let(:class_or_module) do double('Class or Module', :module_parent => namespace_module) end let(:namespace_module) do double('Msf::Modules::Namespace', :parent_path => parent_path) end context 'with existing :path' do it 'should update module_info_by_path' do expect { cache_in_memory }.to change { module_info_by_path } end context 'module_info_by_path' do subject(:module_info_by_path) do module_manager.send(:module_info_by_path) end before(:example) do cache_in_memory end it 'should have entry for path' do expect(module_info_by_path[path]).to be_a Hash end context 'value' do subject(:value) do module_info_by_path[path] end it 'should have modification time of :path option for :modification_time' do expect(value[:modification_time]).to eq pathname_modification_time end it 'should have parent path from namespace module for :parent_path' do expect(value[:parent_path]).to eq namespace_module.parent_path end it 'should use :reference_name option' do expect(value[:reference_name]).to eq reference_name end it 'should use :type option' do expect(value[:type]).to eq type end end end end context 'without existing :path' do let(:path) do 'non/existent/path' end it 'should not raise error' do expect { cache_in_memory }.to_not raise_error end it 'should not update module_info_by_path' do expect { cache_in_memory }.to_not change { module_info_by_path } end end end describe '#load_cached_module' do context 'with memory cache' do subject(:load_cached_module) do module_manager.load_cached_module(type, reference_name, cache_type: Msf::ModuleManager::Cache::MEMORY) end before(:example) do module_manager.send(:module_info_by_path=, module_info_by_path) end context 'with module info in cache' do include_context 'Metasploit::Framework::Spec::Constants cleaner' let(:module_info_by_path) do { 'path/to/module' => { :parent_path => parent_path, :reference_name => reference_name, :type => type } } end it 'should enumerate loaders until if it find the one where loadable?(parent_path) is true' do # Only the first one gets it since it finds the module first_loader = module_manager.send(:loaders).first expect(first_loader).to receive(:loadable_module?).with(parent_path, type, reference_name, cached_metadata: nil).and_return(false) expect(first_loader).to_not receive(:load_module) second_loader = module_manager.send(:loaders).second expect(second_loader).to receive(:loadable_module?).with(parent_path, type, reference_name, cached_metadata: nil).and_return(true) expect(second_loader).to receive(:load_module).with(parent_path, type, reference_name, force: true, cached_metadata: nil).and_call_original load_cached_module end it 'should force load using #load_module on the loader' do expect_any_instance_of(Msf::Modules::Loader::Directory).to receive( :load_module ).with( parent_path, type, reference_name, :force => true, :cached_metadata => nil ).and_call_original load_cached_module end context 'return from load_module' do before(:example) do # Only the first one gets it since it finds the module loader = module_manager.send(:loaders).first expect(loader).to receive(:load_module).and_return(module_loaded) end context 'with false' do let(:module_loaded) do false end it { is_expected.to be_falsey } end context 'with true' do let(:module_loaded) do true end it { is_expected.to be_truthy } end end end context 'without module info in cache' do let(:module_info_by_path) do {} end it { is_expected.to be_falsey } end end context 'with filesystem cache' do subject(:load_cached_module) do module_manager.load_cached_module(type, reference_name, cache_type: Msf::ModuleManager::Cache::FILESYSTEM) end context 'with module info in cache' do include_context 'Metasploit::Framework::Spec::Constants cleaner' it 'should enumerate loaders until if it find the one where loadable?(parent_path) is true' do first_loader = module_manager.send(:loaders).first expect(first_loader).to receive(:loadable_module?).with(parent_path, type, reference_name, cached_metadata: instance_of(Msf::Modules::Metadata::Obj)).and_return(false) expect(first_loader).to_not receive(:load_module) second_loader = module_manager.send(:loaders).second expect(second_loader).to receive(:loadable_module?).with(parent_path, type, reference_name, cached_metadata: instance_of(Msf::Modules::Metadata::Obj)).and_return(true) expect(second_loader).to receive(:load_module).with(parent_path, type, reference_name, force: true, cached_metadata: instance_of(Msf::Modules::Metadata::Obj)).and_call_original load_cached_module end it 'should force load using #load_module on the loader' do expect_any_instance_of(Msf::Modules::Loader::Directory).to receive( :load_module ).with( parent_path, type, reference_name, :force => true, :cached_metadata => instance_of(Msf::Modules::Metadata::Obj) ).and_call_original load_cached_module end context 'return from load_module' do before(:example) do # Only the first one gets it since it finds the module loader = module_manager.send(:loaders).first expect(loader).to receive(:load_module).and_return(module_loaded) end context 'with false' do let(:module_loaded) do false end it { is_expected.to be_falsey } end context 'with true' do let(:module_loaded) do true end it { is_expected.to be_truthy } end end end context 'without module info in cache' do before(:example) do allow(Msf::Modules::Metadata::Cache.instance).to receive(:get_module_reference).with(anything).and_return(nil) end it { is_expected.to be_falsey } end end end context '#refresh_cache_from_module_files' do context 'with module argument' do def refresh_cache_from_module_files module_manager.refresh_cache_from_module_files(module_class_or_instance) end let(:module_class_or_instance) do Class.new(Msf::Module) end it 'should update store and then update in-memory cache from the store for the given module_class_or_instance' do expect(metadata_cache).to receive(:refresh_metadata_instance).with(module_class_or_instance).ordered expect(module_manager).to receive(:refresh_cache_from_database).ordered refresh_cache_from_module_files end end context 'without module argument' do def refresh_cache_from_module_files module_manager.refresh_cache_from_module_files end it 'should update store and then update in-memory cache from the store for all modules' do expect(metadata_cache).to receive(:refresh_metadata).ordered expect(module_manager).to receive(:refresh_cache_from_database) refresh_cache_from_module_files end end end context '#refresh_cache_from_database' do def refresh_cache_from_database module_manager.refresh_cache_from_database end it 'should call #module_info_by_path_from_database!' do expect(module_manager).to receive(:module_info_by_path_from_database!) refresh_cache_from_database end end context '#module_info_by_path' do it 'should have protected method module_info_by_path' do expect(subject.respond_to?(:module_info_by_path, true)).to be_truthy end end context '#module_info_by_path=' do it 'should have protected method module_info_by_path=' do expect(subject.respond_to?(:module_info_by_path=, true)).to be_truthy end end context '#module_info_by_path_from_database!' do def module_info_by_path module_manager.send(:module_info_by_path) end def module_info_by_path_from_database! module_manager.send(:module_info_by_path_from_database!) end it 'should call get metadata' do allow(metadata_cache).to receive(:get_metadata).and_return([]) expect(metadata_cache).to receive(:get_metadata) module_info_by_path_from_database! end context 'with database cache' do # # Let!s (let + before(:each)) # let!(:mdm_module_detail) do FactoryBot.create(:mdm_module_detail, :file => path, :mtype => type, :mtime => pathname.mtime, :refname => reference_name ) end it 'should create cache entry for path' do module_info_by_path_from_database! expect(module_info_by_path).to have_key(path) end context 'cache entry' do subject(:cache_entry) do module_info_by_path[path] end before(:example) do module_info_by_path_from_database! end it { expect(subject[:modification_time]).to be_a(Time) } it { expect(subject[:parent_path]).to eq(parent_path) } it { expect(subject[:reference_name]).to eq(reference_name) } it { expect(subject[:type]).to eq(type) } end context 'typed module set' do let(:typed_module_set) do module_manager.module_set(type) end context 'with reference_name' do before(:example) do typed_module_set[reference_name] = double('Msf::Module') end it 'should not change reference_name value' do expect { module_info_by_path_from_database! }.to_not change { typed_module_set[reference_name] } end end context 'without reference_name' do it 'should set reference_name value to nil' do module_info_by_path_from_database! # have to use fetch because [] will trigger de-symbolization and # instantiation. expect(typed_module_set.fetch(reference_name)).to eq nil end end end end end end