threatinator 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/CONTRIBUTING.md +119 -0
  4. data/Gemfile +28 -0
  5. data/LICENSE +165 -0
  6. data/README.md +45 -0
  7. data/Rakefile +45 -0
  8. data/VERSION +1 -0
  9. data/bin/threatinator +5 -0
  10. data/lib/threatinator.rb +3 -0
  11. data/lib/threatinator/action.rb +14 -0
  12. data/lib/threatinator/actions/list.rb +2 -0
  13. data/lib/threatinator/actions/list/action.rb +53 -0
  14. data/lib/threatinator/actions/list/config.rb +10 -0
  15. data/lib/threatinator/actions/run.rb +2 -0
  16. data/lib/threatinator/actions/run/action.rb +45 -0
  17. data/lib/threatinator/actions/run/config.rb +32 -0
  18. data/lib/threatinator/actions/run/coverage_observer.rb +54 -0
  19. data/lib/threatinator/actions/run/output_config.rb +59 -0
  20. data/lib/threatinator/cli.rb +13 -0
  21. data/lib/threatinator/cli/action_builder.rb +33 -0
  22. data/lib/threatinator/cli/list_action_builder.rb +19 -0
  23. data/lib/threatinator/cli/parser.rb +113 -0
  24. data/lib/threatinator/cli/run_action_builder.rb +41 -0
  25. data/lib/threatinator/config.rb +6 -0
  26. data/lib/threatinator/config/base.rb +35 -0
  27. data/lib/threatinator/config/feed_search.rb +25 -0
  28. data/lib/threatinator/decoder.rb +24 -0
  29. data/lib/threatinator/decoders/gzip.rb +30 -0
  30. data/lib/threatinator/event.rb +27 -0
  31. data/lib/threatinator/event_builder.rb +41 -0
  32. data/lib/threatinator/exceptions.rb +61 -0
  33. data/lib/threatinator/feed.rb +82 -0
  34. data/lib/threatinator/feed_builder.rb +156 -0
  35. data/lib/threatinator/feed_registry.rb +47 -0
  36. data/lib/threatinator/feed_runner.rb +118 -0
  37. data/lib/threatinator/fetcher.rb +22 -0
  38. data/lib/threatinator/fetchers/http.rb +46 -0
  39. data/lib/threatinator/filter.rb +12 -0
  40. data/lib/threatinator/filters/block.rb +18 -0
  41. data/lib/threatinator/filters/comments.rb +16 -0
  42. data/lib/threatinator/filters/whitespace.rb +19 -0
  43. data/lib/threatinator/output.rb +50 -0
  44. data/lib/threatinator/parser.rb +23 -0
  45. data/lib/threatinator/parsers/csv.rb +7 -0
  46. data/lib/threatinator/parsers/csv/parser.rb +77 -0
  47. data/lib/threatinator/parsers/getline.rb +8 -0
  48. data/lib/threatinator/parsers/getline/parser.rb +45 -0
  49. data/lib/threatinator/parsers/json.rb +8 -0
  50. data/lib/threatinator/parsers/json/adapters/oj.rb +65 -0
  51. data/lib/threatinator/parsers/json/parser.rb +45 -0
  52. data/lib/threatinator/parsers/json/record.rb +20 -0
  53. data/lib/threatinator/parsers/xml.rb +8 -0
  54. data/lib/threatinator/parsers/xml/node.rb +79 -0
  55. data/lib/threatinator/parsers/xml/node_builder.rb +39 -0
  56. data/lib/threatinator/parsers/xml/parser.rb +44 -0
  57. data/lib/threatinator/parsers/xml/path.rb +70 -0
  58. data/lib/threatinator/parsers/xml/pattern.rb +53 -0
  59. data/lib/threatinator/parsers/xml/record.rb +14 -0
  60. data/lib/threatinator/parsers/xml/sax_document.rb +64 -0
  61. data/lib/threatinator/plugin_loader.rb +115 -0
  62. data/lib/threatinator/plugins/output/csv.rb +47 -0
  63. data/lib/threatinator/plugins/output/null.rb +17 -0
  64. data/lib/threatinator/plugins/output/rubydebug.rb +16 -0
  65. data/lib/threatinator/property_definer.rb +101 -0
  66. data/lib/threatinator/record.rb +22 -0
  67. data/lib/threatinator/registry.rb +53 -0
  68. data/lib/threatinator/util.rb +15 -0
  69. data/spec/feeds/ET_compromised-ip_reputation_spec.rb +50 -0
  70. data/spec/feeds/alienvault-ip_reputation_spec.rb +50 -0
  71. data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +50 -0
  72. data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +50 -0
  73. data/spec/feeds/autoshun_shunlist_spec.rb +42 -0
  74. data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +50 -0
  75. data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +50 -0
  76. data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +50 -0
  77. data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +50 -0
  78. data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +50 -0
  79. data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +50 -0
  80. data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +50 -0
  81. data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +50 -0
  82. data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +50 -0
  83. data/spec/feeds/ciarmy-ip_reputation_spec.rb +50 -0
  84. data/spec/feeds/cruzit-ip_reputation_spec.rb +50 -0
  85. data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +50 -0
  86. data/spec/feeds/data/ET_compromised-ip_reputation.txt +11 -0
  87. data/spec/feeds/data/alienvault-ip_reputation.txt +18 -0
  88. data/spec/feeds/data/arbor_domainlist.txt +11 -0
  89. data/spec/feeds/data/arbor_ssh.txt +16 -0
  90. data/spec/feeds/data/autoshun_shunlist.csv +20 -0
  91. data/spec/feeds/data/blocklist_de_apache-ip-reputation.txt +17 -0
  92. data/spec/feeds/data/blocklist_de_bots-ip-reputation.txt +15 -0
  93. data/spec/feeds/data/blocklist_de_ftp-ip-reputation.txt +7 -0
  94. data/spec/feeds/data/blocklist_de_imap-ip-reputation.txt +8 -0
  95. data/spec/feeds/data/blocklist_de_pop3-ip-reputation.txt +11 -0
  96. data/spec/feeds/data/blocklist_de_proftpd-ip-reputation.txt +12 -0
  97. data/spec/feeds/data/blocklist_de_sip-ip-reputation.txt +9 -0
  98. data/spec/feeds/data/blocklist_de_ssh-ip-reputation.txt +10 -0
  99. data/spec/feeds/data/blocklist_de_strongips-ip-reputation.txt +11 -0
  100. data/spec/feeds/data/ciarmy-ip-reputation.txt +11 -0
  101. data/spec/feeds/data/cruzit-ip-reputation.txt +14 -0
  102. data/spec/feeds/data/dan_me_uk_torlist-ip-reputation.txt +11 -0
  103. data/spec/feeds/data/dshield_topattackers.xml +4 -0
  104. data/spec/feeds/data/feodo_domainlist.txt +18 -0
  105. data/spec/feeds/data/feodo_iplist.txt +20 -0
  106. data/spec/feeds/data/infiltrated_iplist.txt +16 -0
  107. data/spec/feeds/data/malc0de_domainlist.txt +18 -0
  108. data/spec/feeds/data/malc0de_iplist.txt +14 -0
  109. data/spec/feeds/data/mirc_domainlist.txt +31 -0
  110. data/spec/feeds/data/nothink_irc_iplist.txt +14 -0
  111. data/spec/feeds/data/nothink_ssh_iplist.txt +10 -0
  112. data/spec/feeds/data/openbl_iplist.txt +12 -0
  113. data/spec/feeds/data/palevo_domainlist.txt +25 -0
  114. data/spec/feeds/data/palevo_iplist.txt +24 -0
  115. data/spec/feeds/data/phishtank-sample.json.gz +0 -0
  116. data/spec/feeds/data/spyeye_domainlist.txt +16 -0
  117. data/spec/feeds/data/spyeye_iplist.txt +19 -0
  118. data/spec/feeds/data/t-arend-de_ssh_iplist.txt +17 -0
  119. data/spec/feeds/data/the_haleys_ssh_iplist.txt +12 -0
  120. data/spec/feeds/data/yourcmc_ssh-ip_reputation.txt +27 -0
  121. data/spec/feeds/data/zeus-ip_reputation.txt +285 -0
  122. data/spec/feeds/data/zeus_domainlist.txt +27 -0
  123. data/spec/feeds/dshield_attackers-top1000_spec.rb +43 -0
  124. data/spec/feeds/feodo-domain_reputation_spec.rb +50 -0
  125. data/spec/feeds/feodo-ip_reputation_spec.rb +50 -0
  126. data/spec/feeds/infiltrated-ip_reputation_spec.rb +50 -0
  127. data/spec/feeds/malc0de-domain_reputation_spec.rb +50 -0
  128. data/spec/feeds/malc0de-ip_reputation_spec.rb +50 -0
  129. data/spec/feeds/mirc-domain_reputation_spec.rb +50 -0
  130. data/spec/feeds/nothink_irc-ip_reputation_spec.rb +50 -0
  131. data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +50 -0
  132. data/spec/feeds/openbl-ip_reputation_spec.rb +50 -0
  133. data/spec/feeds/palevo-domain_reputation_spec.rb +50 -0
  134. data/spec/feeds/palevo-ip_reputation_spec.rb +50 -0
  135. data/spec/feeds/phishtank_spec.rb +45 -0
  136. data/spec/feeds/spyeye-domain_reputation_spec.rb +50 -0
  137. data/spec/feeds/spyeye-ip_reputation_spec.rb +50 -0
  138. data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +50 -0
  139. data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +50 -0
  140. data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +50 -0
  141. data/spec/feeds/zeus-domain_reputation_spec.rb +50 -0
  142. data/spec/feeds/zeus-ip_reputation_spec.rb +50 -0
  143. data/spec/fixtures/feed/provider1/feed1.feed +6 -0
  144. data/spec/fixtures/parsers/test.xml +13 -0
  145. data/spec/fixtures/parsers/test_self_closing.xml +20 -0
  146. data/spec/fixtures/plugins/bad/threatinator/plugins/test_error1/plugin.rb +1 -0
  147. data/spec/fixtures/plugins/bad/threatinator/plugins/test_missing1/plugin.rb +0 -0
  148. data/spec/fixtures/plugins/fake.rb +19 -0
  149. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_a.rb +8 -0
  150. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_b.rb +8 -0
  151. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_c.rb +8 -0
  152. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_d.rb +8 -0
  153. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_e.rb +8 -0
  154. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_f.rb +8 -0
  155. data/spec/spec_helper.rb +52 -0
  156. data/spec/support/bad_feeds/missing_fetcher.feed +7 -0
  157. data/spec/support/bad_feeds/missing_name.feed +6 -0
  158. data/spec/support/bad_feeds/missing_parser.feed +3 -0
  159. data/spec/support/bad_feeds/missing_provider.feed +5 -0
  160. data/spec/support/factories/event.rb +27 -0
  161. data/spec/support/factories/feed.rb +32 -0
  162. data/spec/support/factories/feed_builder.rb +65 -0
  163. data/spec/support/factories/feed_registry.rb +8 -0
  164. data/spec/support/factories/output.rb +11 -0
  165. data/spec/support/factories/record.rb +17 -0
  166. data/spec/support/factories/xml_node.rb +33 -0
  167. data/spec/support/helpers/io.rb +11 -0
  168. data/spec/support/helpers/models.rb +13 -0
  169. data/spec/support/shared/action_builder.rb +47 -0
  170. data/spec/support/shared/decoder.rb +70 -0
  171. data/spec/support/shared/feeds.rb +218 -0
  172. data/spec/support/shared/fetcher.rb +48 -0
  173. data/spec/support/shared/filter.rb +14 -0
  174. data/spec/support/shared/io-like.rb +7 -0
  175. data/spec/support/shared/output.rb +120 -0
  176. data/spec/support/shared/parsers.rb +51 -0
  177. data/spec/support/shared/record.rb +111 -0
  178. data/spec/threatinator/actions/list/action_spec.rb +93 -0
  179. data/spec/threatinator/actions/run/action_spec.rb +89 -0
  180. data/spec/threatinator/actions/run/config_spec.rb +39 -0
  181. data/spec/threatinator/actions/run/coverage_observer_spec.rb +116 -0
  182. data/spec/threatinator/actions/run/output_config_spec.rb +89 -0
  183. data/spec/threatinator/cli/list_action_builder_spec.rb +57 -0
  184. data/spec/threatinator/cli/run_action_builder_spec.rb +133 -0
  185. data/spec/threatinator/cli_spec.rb +175 -0
  186. data/spec/threatinator/config/base_spec.rb +39 -0
  187. data/spec/threatinator/config/feed_search_spec.rb +76 -0
  188. data/spec/threatinator/decoders/gzip_spec.rb +75 -0
  189. data/spec/threatinator/event_builder_spec.rb +33 -0
  190. data/spec/threatinator/event_spec.rb +30 -0
  191. data/spec/threatinator/feed_builder_spec.rb +636 -0
  192. data/spec/threatinator/feed_registry_spec.rb +198 -0
  193. data/spec/threatinator/feed_runner_spec.rb +155 -0
  194. data/spec/threatinator/feed_spec.rb +169 -0
  195. data/spec/threatinator/fetcher_spec.rb +12 -0
  196. data/spec/threatinator/fetchers/http_spec.rb +32 -0
  197. data/spec/threatinator/filter_spec.rb +13 -0
  198. data/spec/threatinator/filters/block_spec.rb +16 -0
  199. data/spec/threatinator/filters/comments_spec.rb +13 -0
  200. data/spec/threatinator/filters/whitespace_spec.rb +12 -0
  201. data/spec/threatinator/parser_spec.rb +13 -0
  202. data/spec/threatinator/parsers/csv/parser_spec.rb +202 -0
  203. data/spec/threatinator/parsers/getline/parser_spec.rb +83 -0
  204. data/spec/threatinator/parsers/json/parser_spec.rb +106 -0
  205. data/spec/threatinator/parsers/json/record_spec.rb +30 -0
  206. data/spec/threatinator/parsers/xml/node_spec.rb +335 -0
  207. data/spec/threatinator/parsers/xml/parser_spec.rb +263 -0
  208. data/spec/threatinator/parsers/xml/path_spec.rb +209 -0
  209. data/spec/threatinator/parsers/xml/pattern_spec.rb +72 -0
  210. data/spec/threatinator/parsers/xml/record_spec.rb +27 -0
  211. data/spec/threatinator/plugin_loader_spec.rb +238 -0
  212. data/spec/threatinator/plugins/output/csv_spec.rb +46 -0
  213. data/spec/threatinator/plugins/output/null_spec.rb +17 -0
  214. data/spec/threatinator/plugins/output/rubydebug_spec.rb +37 -0
  215. data/spec/threatinator/property_definer_spec.rb +155 -0
  216. data/spec/threatinator/record_spec.rb +19 -0
  217. data/spec/threatinator/registry_spec.rb +97 -0
  218. data/spec/threatinator/runner_spec.rb +273 -0
  219. metadata +376 -0
@@ -0,0 +1,11 @@
1
+ require 'stringio'
2
+ module IOHelpers
3
+ def temp_stdout
4
+ $stdout = StringIO.new
5
+ yield $stdout.string
6
+ return $stdout.string
7
+ ensure
8
+ $stdout = STDOUT
9
+ end
10
+ end
11
+
@@ -0,0 +1,13 @@
1
+ require 'threatinator/parser'
2
+ require 'threatinator/fetcher'
3
+
4
+ module FeedSpec
5
+ class Fetcher < Threatinator::Fetcher
6
+ def initialize(opts = {})
7
+ @io = opts[:io]
8
+ end
9
+ def fetch; @io; end
10
+ end
11
+ class Parser < Threatinator::Parser
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "an action builder" do
4
+ # expects :builder
5
+ # expects :config_hash
6
+ describe "a call to #feed_registry" do
7
+ let(:feed_registry) { double('feed_registry') }
8
+ let(:feed_search_hash) { double('feed search hash') }
9
+ let(:feed_search) { double('feed_search') }
10
+
11
+ before :each do
12
+ allow(Threatinator::FeedRegistry).to receive(:build).and_return(feed_registry)
13
+ allow(Threatinator::Config::FeedSearch).to receive(:new).and_return(feed_search)
14
+ end
15
+
16
+
17
+ context "when config_hash['feed_search'] exists" do
18
+ before :each do
19
+ config_hash['feed_search'] = feed_search_hash
20
+ end
21
+
22
+ it "builds a new Threatinator::Config::FeedSearch using config_hash['feed_search']" do
23
+ expect(Threatinator::Config::FeedSearch).to receive(:new).with(feed_search_hash)
24
+ builder.feed_registry
25
+ end
26
+ end
27
+ context "when config_hash['feed_search'] does not exist" do
28
+ before :each do
29
+ config_hash.delete('feed_search')
30
+ end
31
+
32
+ it "builds a new Threatinator::Config::FeedSearch using an empty hash" do
33
+ expect(Threatinator::Config::FeedSearch).to receive(:new).with({})
34
+ builder.feed_registry
35
+ end
36
+ end
37
+
38
+ it "builds a new feed registry using the config" do
39
+ expect(Threatinator::FeedRegistry).to receive(:build).with(feed_search)
40
+ builder.feed_registry
41
+ end
42
+
43
+ it "returns the instance of Threatinator::FeedRegistry" do
44
+ expect(builder.feed_registry).to be(feed_registry)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'threatinator/exceptions'
4
+
5
+ shared_examples_for "a decoder" do
6
+ # Expects :encode_data_proc, :decoder_opts
7
+ let(:extra_opts) { { } }
8
+ let(:decoder) { described_class.new(decoder_opts.merge(extra_opts)) }
9
+
10
+ describe "an instance" do
11
+ subject { decoder }
12
+ it { is_expected.to respond_to(:decode) }
13
+
14
+ it "should close the IO that it decodes from" do
15
+ data = encode_data_proc.call("here's some data")
16
+ io = StringIO.new(data)
17
+ expect(io).not_to be_closed
18
+ decoder.decode(io)
19
+ expect(io).to be_closed
20
+ end
21
+
22
+ end
23
+
24
+ describe "decoding a UTF-8 string" do
25
+ let(:original_string) {
26
+ "\xE1\x9A\xA0\xE1\x9B\x87\xE1\x9A\xBB\xE1\x9B\xAB\xE1\x9B\x92\xE1\x9B\xA6\xE1\x9A\xA6\xE1\x9B\xAB\xE1\x9A\xA0\xE1\x9A\xB1\xE1\x9A\xA9\xE1\x9A\xA0\xE1\x9A\xA2\xE1\x9A\xB1\xE1\x9B\xAB\xE1\x9A\xA0\xE1\x9B\x81\xE1\x9A\xB1\xE1\x9A\xAA\xE1\x9B\xAB\xE1\x9A\xB7\xE1\x9B\x96\xE1\x9A\xBB\xE1\x9A\xB9\xE1\x9B\xA6\xE1\x9B\x9A\xE1\x9A\xB3\xE1\x9A\xA2\xE1\x9B\x97\n\xE1\x9B\x8B\xE1\x9A\xB3\xE1\x9B\x96\xE1\x9A\xAA\xE1\x9B\x9A\xE1\x9B\xAB\xE1\x9A\xA6\xE1\x9B\x96\xE1\x9A\xAA\xE1\x9A\xBB\xE1\x9B\xAB\xE1\x9B\x97\xE1\x9A\xAA\xE1\x9A\xBE\xE1\x9A\xBE\xE1\x9A\xAA\xE1\x9B\xAB\xE1\x9A\xB7\xE1\x9B\x96\xE1\x9A\xBB\xE1\x9A\xB9\xE1\x9B\xA6\xE1\x9B\x9A\xE1\x9A\xB3\xE1\x9B\xAB\xE1\x9B\x97\xE1\x9B\x81\xE1\x9A\xB3\xE1\x9B\x9A\xE1\x9A\xA2\xE1\x9A\xBE\xE1\x9B\xAB\xE1\x9A\xBB\xE1\x9B\xA6\xE1\x9B\x8F\xE1\x9B\xAB\xE1\x9B\x9E\xE1\x9A\xAB\xE1\x9B\x9A\xE1\x9A\xAA\xE1\x9A\xBE\n\xE1\x9A\xB7\xE1\x9B\x81\xE1\x9A\xA0\xE1\x9B\xAB\xE1\x9A\xBB\xE1\x9B\x96\xE1\x9B\xAB\xE1\x9A\xB9\xE1\x9B\x81\xE1\x9B\x9A\xE1\x9B\x96\xE1\x9B\xAB\xE1\x9A\xA0\xE1\x9A\xA9\xE1\x9A\xB1\xE1\x9B\xAB\xE1\x9B\x9E\xE1\x9A\xB1\xE1\x9B\x81\xE1\x9A\xBB\xE1\x9B\x8F\xE1\x9A\xBE\xE1\x9B\x96\xE1\x9B\xAB\xE1\x9B\x9E\xE1\x9A\xA9\xE1\x9B\x97\xE1\x9B\x96\xE1\x9B\x8B\xE1\x9B\xAB\xE1\x9A\xBB\xE1\x9B\x9A\xE1\x9B\x87\xE1\x9B\x8F\xE1\x9A\xAA\xE1\x9A\xBE\xE1\x9B\xAC\n"
27
+ .force_encoding("UTF-8")
28
+ }
29
+
30
+ let(:encoded_string) { encode_data_proc.call(original_string) }
31
+ let(:encoded_io) { StringIO.new(encoded_string) }
32
+
33
+ describe "the decoded data" do
34
+ it "should equal the original string" do
35
+ expect(decoder.decode(encoded_io).read()).to eq(original_string)
36
+ end
37
+ it "should be UTF-8 encoded" do
38
+ expect(decoder.decode(encoded_io).read().encoding).to eq(Encoding::UTF_8)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "decoding a UTF-8 string2" do
44
+ let(:original_string) {
45
+ "21826 | Corporación Telemic C.A.,VE | 200.75.105.49 | 2014-07-18 19:54:54 | sshpwauth".force_encoding("UTF-8")
46
+ }
47
+
48
+ let(:encoded_string) { encode_data_proc.call(original_string) }
49
+ let(:encoded_io) { StringIO.new(encoded_string) }
50
+
51
+ describe "the decoded data" do
52
+ it "should equal the original string" do
53
+ expect(decoder.decode(encoded_io).read()).to eq(original_string)
54
+ end
55
+ it "should be UTF-8 encoded" do
56
+ expect(decoder.decode(encoded_io).read().encoding).to eq(Encoding::UTF_8)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ shared_examples_for "a decoder encountering an error during decoding" do
63
+ # Expects :decoder, :input_io
64
+ it "#decode should raise a DecoderError" do
65
+ expect {
66
+ decoder.decode(input_io)
67
+ }.to raise_error(Threatinator::Exceptions::DecoderError)
68
+ end
69
+ end
70
+
@@ -0,0 +1,218 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/feed_builder'
3
+ require 'threatinator/feed_runner'
4
+ require 'threatinator/plugins/output/null'
5
+ require 'pathname'
6
+
7
+ shared_context 'a parsed record' do
8
+ # Expects :observer
9
+ before :each do
10
+ @record, @status, @events = observer.first
11
+ end
12
+ let(:record) { @record }
13
+ let(:events) { @events }
14
+ let(:status) { @status }
15
+ end
16
+
17
+ shared_context 'a parsed feed' do
18
+ # expects :observer
19
+ let(:events) { observer.map { |status, record, events| events } }
20
+ let(:records) { observer.map { |status, record, events| record } }
21
+ let(:num_records) { observer.count }
22
+ let(:num_records_filtered) { observer.num_records_filtered }
23
+ let(:num_records_parsed) { observer.num_records_parsed }
24
+ let(:num_records_missed) { observer.num_records_missed }
25
+ end
26
+
27
+ shared_context 'for feeds', :feed => lambda { true } do
28
+ _feed_path = Pathname.new(self.description)
29
+ if _feed_path.relative?
30
+ # It's relative to the root of our project.
31
+ _feed_path = PROJECT_ROOT + _feed_path
32
+ end
33
+ _feed_path = _feed_path.expand_path
34
+
35
+ before :all do
36
+ @feed_builder = Threatinator::FeedBuilder.from_file(_feed_path.to_s)
37
+ end
38
+
39
+ let(:feed_path) { feed_path.to_s }
40
+ let(:output_formatter) { Threatinator::Plugins::Output::Null.new(Threatinator::Plugins::Output::Null::Config.new) }
41
+ let(:feed_runner) { Threatinator::FeedRunner.new(feed, output_formatter) }
42
+ let(:feed) { @feed_builder.build() }
43
+ end
44
+
45
+ shared_examples_for 'any feed', :feed do
46
+ # Expects :provider, :name, :feed
47
+ subject { feed }
48
+ it { should be_a(Threatinator::Feed) }
49
+
50
+ describe "#provider" do
51
+ subject { feed.provider }
52
+ it { is_expected.to be_a(::String) }
53
+ it { is_expected.to eq(provider) }
54
+ end
55
+
56
+ describe "#name" do
57
+ subject { feed.name}
58
+ it { is_expected.to be_a(::String) }
59
+ it { is_expected.to eq(name) }
60
+ end
61
+
62
+ describe "#parser_block" do
63
+ subject { feed.parser_block }
64
+ it { is_expected.to be_a(::Proc) }
65
+ end
66
+
67
+ describe "#fetcher_builder" do
68
+ subject { feed.fetcher_builder }
69
+ it { is_expected.to be_a ::Proc }
70
+ specify "when called, it should generate a kind of Threatinator::Fetcher" do
71
+ expect(subject.call).to be_kind_of(Threatinator::Fetcher)
72
+ end
73
+ end
74
+
75
+ describe "#parser_builder" do
76
+ subject { feed.parser_builder }
77
+ it { is_expected.to be_a ::Proc }
78
+
79
+ specify "when called, it should generate a kind of Threatinator::Parser" do
80
+ expect(subject.call).to be_kind_of(Threatinator::Parser)
81
+ end
82
+ end
83
+
84
+ describe "#filter_builders" do
85
+ subject { feed.filter_builders }
86
+
87
+ it "should be an Array of Proc objects" do
88
+ expect(subject).to be_an ::Array
89
+ end
90
+
91
+ specify "each Proc, when called, should generate an object that responds to :filter?" do
92
+ subject.each do |filter_builder|
93
+ expect(filter_builder.call).to respond_to(:filter?)
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "#decoder_builders" do
99
+ subject { feed.decoder_builders }
100
+
101
+ it "should be an Array of Proc objects" do
102
+ expect(subject).to be_an ::Array
103
+ end
104
+
105
+ specify "each Proc, when called, should generate a kind of Threatinator::Decoder" do
106
+ subject.each do |decoder_builder|
107
+ expect(decoder_builder.call).to be_kind_of(Threatinator::Decoder)
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ module FeedHelpers
114
+ class FeedRunnerObserver
115
+ include Enumerable
116
+ attr_reader :records, :statuses, :events, :num_records_filtered,
117
+ :num_records_missed, :num_records_parsed
118
+
119
+ def initialize
120
+ @records = []
121
+ @statuses = []
122
+ @events = []
123
+ @num_records_filtered = 0
124
+ @num_records_parsed = 0
125
+ @num_records_missed = 0
126
+ end
127
+
128
+ def each
129
+ @records.each_with_index do |record, i|
130
+ yield(record, @statuses[i], @events[i])
131
+ end
132
+ end
133
+
134
+ # Handles FeedRunner observations
135
+ def update(message, *args)
136
+ case message
137
+ when :record_missed
138
+ @records << args.shift
139
+ @statuses << :missed
140
+ @events << []
141
+ @num_records_missed += 1
142
+ when :record_filtered
143
+ @records << args.shift
144
+ @statuses << :filtered
145
+ @events << []
146
+ @num_records_filtered += 1
147
+ when :record_parsed
148
+ @records << args.shift
149
+ @statuses << :parsed
150
+ @events << args.shift
151
+ @num_records_parsed += 1
152
+ end
153
+ end
154
+ end
155
+
156
+ module FeedHelperMethods
157
+ def it_fetches_url(url)
158
+ describe 'fetching' do
159
+ it "should fetch the url #{url}" do
160
+ stub_request(:get, url)
161
+ feed.fetcher_builder.call().fetch()
162
+ expect(a_request(:get, url)).to have_been_made
163
+ end
164
+ end
165
+ end
166
+
167
+ def feed_data(filename)
168
+ (FEED_DATA_ROOT + filename).to_s
169
+ end
170
+
171
+ def describe_parsing_a_record(data, &block)
172
+ context("parsing a record from '#{data}'", :caller => caller) do
173
+ let(:observer) { FeedRunnerObserver.new }
174
+ before :each do
175
+ sio = StringIO.new(data)
176
+ feed_runner.add_observer(observer)
177
+ feed_runner.run(:io => sio)
178
+ @status, @record, @events = observer.first
179
+ end
180
+
181
+ after :each do
182
+ feed_runner.delete_observer(observer)
183
+ end
184
+
185
+ it "should have handled exactly 1 record" do
186
+ expect(observer.count).to eq(1)
187
+ end
188
+
189
+ describe "the record" do
190
+ include_context 'a parsed record'
191
+ instance_exec(&block)
192
+ end
193
+ end
194
+ end
195
+
196
+ def describe_parsing_the_file(filename, &block)
197
+ filepath = Pathname.new(filename)
198
+ relative_filename = filepath.relative_path_from(PROJECT_ROOT).to_s
199
+
200
+ context("parsing the file '#{relative_filename}'", :caller => caller) do
201
+ let(:observer) { FeedRunnerObserver.new }
202
+ before :each do
203
+ fio = File.open(filename, 'r')
204
+ feed_runner.add_observer(observer)
205
+ feed_runner.run(:io => fio)
206
+ fio.close unless fio.closed?
207
+ end
208
+
209
+ after :each do
210
+ feed_runner.delete_observer(observer)
211
+ end
212
+
213
+ include_context "a parsed feed"
214
+ instance_exec(&block)
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "a fetcher" do
4
+ let(:expected_data) { "here's some data" }
5
+
6
+ subject { fetcher }
7
+ it { should respond_to(:fetch) }
8
+
9
+ it "should == itself" do
10
+ expect(fetcher).to be == fetcher
11
+ end
12
+
13
+ it "should == an identically configured instance" do
14
+ expect(fetcher_builder.call()).to be == fetcher_builder.call()
15
+ end
16
+
17
+ it "should not == a differently configured instance" do
18
+ expect(fetcher_builder.call()).not_to be == fetcher_builder_different.call()
19
+ end
20
+
21
+ it "should eql?(itself)" do
22
+ expect(fetcher).to eql(fetcher)
23
+ end
24
+
25
+ it "should eql? an identically configured instance" do
26
+ expect(fetcher_builder.call()).to eql(fetcher_builder.call())
27
+ end
28
+
29
+ it "should not eql? an differently configured instance" do
30
+ expect(fetcher_builder.call()).not_to eql(fetcher_builder_different.call())
31
+ end
32
+
33
+ context "the object returned by #fetch" do
34
+ subject { fetcher.fetch }
35
+
36
+ it_should_behave_like "an IO-like object"
37
+
38
+ it "should return the expected data when #read" do
39
+ expect(subject.read()).to eq(expected_data)
40
+ end
41
+ end
42
+ end
43
+
44
+ shared_examples_for "a #fetch call that failed" do
45
+ it "should raise Threatinator::Exceptions::FetchFailed" do
46
+ expect { fetcher.fetch() }.to raise_error(Threatinator::Exceptions::FetchFailed)
47
+ end
48
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "a filter" do
4
+ # expects :filter, :should_filter, and :shouldnt_filter
5
+
6
+ describe "#filter?" do
7
+ it "should return true for data that is meant to be filtered" do
8
+ expect(filter.filter?(should_filter)).to eq(true)
9
+ end
10
+ it "should return false for data that is meant to be filtered" do
11
+ expect(filter.filter?(shouldnt_filter)).to eq(false)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+
2
+ shared_examples_for "an IO-like object" do
3
+ it { is_expected.to respond_to(:close) }
4
+ it { is_expected.to respond_to(:read) }
5
+ it { is_expected.to respond_to(:gets) }
6
+ it { is_expected.to respond_to(:pos) }
7
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+ require 'threatinator/plugin_loader'
4
+
5
+ shared_examples_for "an output plugin" do |name|
6
+ let(:output) { described_class.new(config) }
7
+
8
+ specify "the plugin loader should find the class by (#{name.inspect})" do
9
+ loader = Threatinator::PluginLoader.new
10
+ loader.load_plugins(:output)
11
+ expect(loader.get(:output, name.to_sym)).to be(described_class)
12
+ end
13
+ end
14
+
15
+ shared_examples_for "a file-based output plugin" do |name|
16
+ it_should_behave_like "an output plugin", name
17
+
18
+ let(:event) { build(:event) }
19
+
20
+ describe "config.io => IO" do
21
+ let(:output) { described_class.new(config) }
22
+ let(:io) { StringIO.new }
23
+ before :each do
24
+ config.io = io
25
+ end
26
+
27
+ describe "#handle_event" do
28
+ before :each do
29
+ output.handle_event(event)
30
+ end
31
+
32
+ it "writes data to the provided IO" do
33
+ expect(io.string).not_to be_empty
34
+ end
35
+ end
36
+
37
+ describe "#finish" do
38
+ before :each do
39
+ allow(io).to receive(:close)
40
+ output.finish
41
+ end
42
+
43
+ it "closes the IO" do
44
+ expect(io).to have_received(:close)
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "config.filename => String" do
50
+ let(:output) { described_class.new(config) }
51
+ before :each do
52
+ @tempfile = Tempfile.new("asdf")
53
+ @tempfile.close
54
+ @filepath = @tempfile.path
55
+ config.filename = @filepath
56
+ end
57
+
58
+ let(:filepath) { @filepath }
59
+
60
+ describe "#finish" do
61
+ it "closes the filehandle" do
62
+ expect(output.instance_variable_get(:"@output_io")).to receive(:close)
63
+ output.finish
64
+ end
65
+ end
66
+
67
+ describe "#handle_event" do
68
+ before :each do
69
+ output.handle_event(event)
70
+ end
71
+ specify "writes data to the path specified by :filename" do
72
+ output.finish
73
+ expect(File.read(@filepath)).not_to be_empty
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "without specifying config.io or config.filename" do
79
+ before :each do
80
+ @orig_stdout = $stdout
81
+ @duped_io = StringIO.new
82
+ $stdout = double("stdout").as_null_object
83
+ allow($stdout).to receive(:dup).and_return(@duped_io)
84
+ @output = described_class.new(config)
85
+ end
86
+
87
+ let(:output) { @output }
88
+
89
+ after :each do
90
+ $stdout = @orig_stdout
91
+ end
92
+
93
+ it "uses a duplicate of $stdout" do
94
+ expect($stdout).to have_received(:dup)
95
+ end
96
+
97
+ describe "#handle_event" do
98
+ before :each do
99
+ output.handle_event(event)
100
+ end
101
+
102
+ it "writes to the dupe of $stdout" do
103
+ expect(@duped_io.string).not_to be_empty
104
+ end
105
+ end
106
+
107
+ describe "#finish" do
108
+ before :each do
109
+ allow(@duped_io).to receive(:close).and_call_original
110
+ output.finish
111
+ end
112
+
113
+ it "closes the dup of $stdout" do
114
+ expect(@duped_io).to have_received(:close)
115
+ end
116
+ end
117
+
118
+ end
119
+ end
120
+