/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

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