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,198 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/feed_registry'
3
+ require 'threatinator/config/feed_search'
4
+
5
+ describe Threatinator::FeedRegistry do
6
+ def generate_feedfile(filename, provider, name, url = "https://foobar/#{provider}/#{name}.data")
7
+ File.open(filename, "w") do |fio|
8
+ fio.write <<EOS
9
+ provider "#{provider}"
10
+ name "#{name}"
11
+ fetch_http('#{url}')
12
+
13
+ parse_eachline(:separator => "\n") do |builder, line|
14
+ end
15
+ EOS
16
+ end
17
+ end
18
+
19
+ let(:registry) { described_class.new }
20
+ let(:ten_feeds) { 1.upto(10).map { |i| build(:feed, provider: "prov#{i}", name: "name#{i}") } }
21
+
22
+ describe "#clear" do
23
+ it "should remove all existing registrations" do
24
+ ten_feeds.each do |feed|
25
+ registry.register(feed)
26
+ end
27
+ expect(registry.count).to eq(10)
28
+ registry.clear
29
+ expect(registry.count).to eq(0)
30
+
31
+ expect {
32
+ ten_feeds.each do |feed|
33
+ registry.register(feed)
34
+ end
35
+ }.not_to raise_error
36
+ expect(registry.count).to eq(10)
37
+ end
38
+ end
39
+
40
+ describe "#register" do
41
+ it "should register the provided feed" do
42
+ feed = build(:feed, provider: "my_provider", name: "my_name")
43
+ registry.register(feed)
44
+ expect(registry.get("my_provider", "my_name")).to be(feed)
45
+ end
46
+ it "should return the feed that was registered" do
47
+ feed = build(:feed, provider: "my_provider", name: "my_name")
48
+ expect(registry.register(feed)).to be(feed)
49
+ end
50
+ it "should raise a AlreadyRegisteredError if a feed is already registered with the provider and name" do
51
+ feed1 = build(:feed, provider: "my_provider", name: "my_name")
52
+ feed2 = build(:feed, provider: "my_provider", name: "my_name")
53
+ registry.register(feed1)
54
+ expect {
55
+ registry.register(feed2)
56
+ }.to raise_error(Threatinator::Exceptions::AlreadyRegisteredError)
57
+ end
58
+
59
+ it "should raise a AlreadyRegisteredError if the same feed is registered twice" do
60
+ feed = build(:feed, provider: "my_provider", name: "my_name")
61
+ registry.register(feed)
62
+ expect {
63
+ registry.register(feed)
64
+ }.to raise_error(Threatinator::Exceptions::AlreadyRegisteredError)
65
+ end
66
+ end
67
+
68
+ describe "#register_from_file" do
69
+ let(:feedfile) {FEED_FIXTURES.join("provider1", "feed1.feed").to_s}
70
+
71
+ it "should return the feed after parsing the file" do
72
+ ret = registry.register_from_file(feedfile)
73
+ expect(ret).to be_a(Threatinator::Feed)
74
+ expect(ret.provider).to eq("provider1")
75
+ expect(ret.name).to eq("feed1")
76
+ end
77
+
78
+ it "should have registered the feed" do
79
+ expect(registry.count).to eq(0)
80
+ feed = registry.register_from_file(feedfile)
81
+ expect(registry.count).to eq(1)
82
+ expect(registry.get(feed.provider, feed.name)).to be(feed)
83
+ end
84
+ end
85
+
86
+ describe "#each" do
87
+ it "should enumerate through each reigstered feed" do
88
+ ten_feeds.each do |feed|
89
+ registry.register(feed)
90
+ end
91
+ found_feeds = []
92
+ registry.each do |feed|
93
+ found_feeds << feed
94
+ end
95
+ expect(found_feeds).to match_array(ten_feeds)
96
+ end
97
+ end
98
+
99
+ describe "#count" do
100
+ it "should return the number of feeds contained within the registry" do
101
+ expect(registry.count).to eq(0)
102
+ ten_feeds.each do |feed|
103
+ registry.register(feed)
104
+ end
105
+ expect(registry.count).to eq(10)
106
+ end
107
+ end
108
+
109
+ describe "#get" do
110
+ it "should return nil if the provider/name isn't registered" do
111
+ registry.register(build(:feed, provider: "my_provider", name: "my_name"))
112
+ expect(registry.get("asdf", "1234")).to be_nil
113
+ end
114
+ it "should return the correct feed for the given provider and name" do
115
+ ten_feeds.each { |feed| registry.register(feed) }
116
+ feed = build(:feed, provider: "my_provider", name: "my_name")
117
+ registry.register(feed)
118
+ expect(registry.get("my_provider", "my_name")).to be(feed)
119
+ end
120
+ end
121
+
122
+ describe ".build(feed_search_config)" do
123
+ context "with no feed paths" do
124
+ let(:feed_search_config) {
125
+ Threatinator::Config::FeedSearch.new(exclude_default: true, path: [] )
126
+ }
127
+ let(:registry) { described_class.build(feed_search_config) }
128
+
129
+ it "should not have loaded any feeds" do
130
+ expect(registry.count).to eq(0)
131
+ end
132
+ end
133
+
134
+ context "with feed search paths" do
135
+ let(:feed_search_config) {
136
+ Threatinator::Config::FeedSearch.new(exclude_default: true, path: [
137
+ @feed_path1, @feed_path2
138
+ ] )
139
+ }
140
+ before :each do
141
+ @feed_path1 = Dir.mktmpdir
142
+ @feed_path2 = Dir.mktmpdir
143
+ end
144
+
145
+ after :each do
146
+ FileUtils.remove_entry_secure @feed_path1
147
+ FileUtils.remove_entry_secure @feed_path2
148
+ end
149
+
150
+ it "should load feeds from all of the configured paths" do
151
+ 5.times do |i|
152
+ generate_feedfile(File.join(@feed_path1, "feed#{i}.feed"), "provider1", "feed#{i}")
153
+ generate_feedfile(File.join(@feed_path2, "feed#{i}.feed"), "provider2", "feed#{i}")
154
+ end
155
+ registry = described_class.build(feed_search_config)
156
+ expect(registry.count).to eq(10)
157
+ end
158
+
159
+ it "should ignore files that don't end with .feed" do
160
+ generate_feedfile(File.join(@feed_path1, "feed1.fee"), "provider1", "feed1")
161
+ generate_feedfile(File.join(@feed_path1, "feed1.fed"), "provider1", "feed2")
162
+ generate_feedfile(File.join(@feed_path1, "feed1.rb"), "provider1", "feed3")
163
+ generate_feedfile(File.join(@feed_path1, "feed1.feed"), "real_provider", "my_feed")
164
+ registry = described_class.build(feed_search_config)
165
+ expect(registry.count).to eq(1)
166
+ expect(registry.get("real_provider", "my_feed")).to be_a(Threatinator::Feed)
167
+ expect(registry.get("provider1", "feed1")).to be_nil
168
+ expect(registry.get("provider1", "feed2")).to be_nil
169
+ expect(registry.get("provider1", "feed3")).to be_nil
170
+ end
171
+
172
+ it "should recurse subdirectories, loading feeds from there" do
173
+ level1 = File.join(@feed_path1, "level1")
174
+ level2 = File.join(@feed_path1, "level1", "level2")
175
+ level3 = File.join(@feed_path1, "level1", "level2", "level3")
176
+ FileUtils.mkdir_p level1
177
+ FileUtils.mkdir_p level2
178
+ FileUtils.mkdir_p level3
179
+ generate_feedfile(File.join(level1, "feed1.feed"), "provider1", "feed1")
180
+ generate_feedfile(File.join(level2, "feed2.feed"), "provider1", "feed2")
181
+ generate_feedfile(File.join(level3, "feed3.feed"), "provider1", "feed3")
182
+ registry = described_class.build(feed_search_config)
183
+ expect(registry.count).to eq(3)
184
+ end
185
+
186
+ it "should raise an exception if the same feed provider/name combination appears in multiple files" do
187
+ generate_feedfile(File.join(@feed_path1, "feed1.feed"), "provider1", "feed1")
188
+ generate_feedfile(File.join(@feed_path1, "feed2.feed"), "provider1", "feed1")
189
+ expect {
190
+ described_class.build(feed_search_config)
191
+ }.to raise_error(Threatinator::Exceptions::AlreadyRegisteredError)
192
+ end
193
+
194
+ end
195
+
196
+
197
+ end
198
+ end
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/feed_runner'
3
+
4
+ describe Threatinator::FeedRunner do
5
+ let(:output_formatter ) { double("formatter") }
6
+ let(:fetcher) { double("fetcher") }
7
+ let(:fetcher_builder) { lambda { fetcher } }
8
+
9
+ let(:io) { double("io") }
10
+ let(:parser) { double("parser") }
11
+ let(:parser_builder) { lambda { parser} }
12
+
13
+ let(:filter_builders) { [] }
14
+ let(:decoder_builders) { [] }
15
+
16
+ let(:feed) {
17
+ build(:feed, fetcher_builder: fetcher_builder,
18
+ parser_builder: parser_builder, filter_builders: filter_builders,
19
+ decoder_builders: decoder_builders
20
+ )
21
+ }
22
+
23
+ describe ".run(feed, output_formatter)" do
24
+ before :each do
25
+ @feed_runner = double('feed_runner')
26
+ allow(described_class).to receive(:new).and_return(@feed_runner)
27
+ allow(@feed_runner).to receive(:run)
28
+ described_class.run(feed, output_formatter)
29
+ end
30
+
31
+ let(:feed_runner) {@feed_runner}
32
+
33
+ it "initializes a FeedRunner with the given feed and output_formatter" do
34
+ expect(described_class).to have_received(:new).with(feed,output_formatter)
35
+ end
36
+
37
+ it "calls #run on the feed_runner with an empty hash" do
38
+ expect(feed_runner).to have_received(:run).with({})
39
+ end
40
+ end
41
+
42
+ describe ".run(feed, output_formatter, run_opts)" do
43
+ before :each do
44
+ @feed_runner = double('feed_runner')
45
+ allow(described_class).to receive(:new).and_return(@feed_runner)
46
+ allow(@feed_runner).to receive(:run)
47
+ described_class.run(feed, output_formatter, run_opts)
48
+ end
49
+
50
+ let(:feed_runner) {@feed_runner}
51
+ let(:run_opts) { {foo: "bar"} }
52
+
53
+ it "initializes a FeedRunner with the given feed and output_formatter" do
54
+ expect(described_class).to have_received(:new).with(feed,output_formatter)
55
+ end
56
+
57
+ it "calls #run on the feed_runner with an empty hash" do
58
+ expect(feed_runner).to have_received(:run).with(run_opts)
59
+ end
60
+ end
61
+
62
+ context "an instance" do
63
+ let(:feed_runner) { described_class.new(feed, output_formatter) }
64
+
65
+ describe "#run" do
66
+ context "fetching data" do
67
+ before :each do
68
+ allow(parser).to receive(:run).with(io)
69
+ end
70
+
71
+ context "when providing the :io argument" do
72
+ it "should not call fetcher_builder, but initialize the parser with the thing we provided to :io" do
73
+ expect(fetcher_builder).not_to receive(:call)
74
+ expect(fetcher).not_to receive(:fetch)
75
+ feed_runner.run(:io => io)
76
+ end
77
+ end
78
+
79
+ it "should generate a new fetcher via fetcher_builder.call, and then fetch" do
80
+ expect(fetcher_builder).to receive(:call).and_call_original
81
+ expect(fetcher).to receive(:fetch).and_return(io)
82
+ feed_runner.run
83
+ end
84
+ end
85
+
86
+ context "parsing" do
87
+ before :each do
88
+ allow(fetcher).to receive(:fetch).and_return(io)
89
+ end
90
+
91
+ it "should call the parser_block for each for each message data parsed" do
92
+ record1 = Threatinator::Record.new('a1')
93
+ record2 = Threatinator::Record.new('a2')
94
+ record3 = Threatinator::Record.new('a3')
95
+ expect(parser).to receive(:run).with(io).and_yield(record1).and_yield(record2).and_yield(record3)
96
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record1).ordered
97
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record2).ordered
98
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record3).ordered
99
+ feed_runner.run
100
+ end
101
+ end
102
+ context "filtering" do
103
+ before :each do
104
+ allow(parser).to receive(:run).with(io)
105
+ allow(fetcher).to receive(:fetch).and_return(io)
106
+ end
107
+ let(:filter) { double("filter") }
108
+ let(:filter_builders) { [ lambda {filter} ] }
109
+ it "should not call the parser_block if the data was filtered" do
110
+ allow(filter).to receive(:filter?)
111
+ allow(feed_runner).to receive(:_fetch).and_return(io)
112
+ record1 = Threatinator::Record.new('a1')
113
+ record2 = Threatinator::Record.new('a2')
114
+ record3 = Threatinator::Record.new('a3')
115
+
116
+ expect(parser).to receive(:run).with(io).and_yield(record1).and_yield(record2).and_yield(record3)
117
+ expect(filter).to receive(:filter?).with(record1).ordered.and_return(false)
118
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record1).ordered
119
+
120
+ expect(filter).to receive(:filter?).with(record2).ordered.and_return(true)
121
+ expect(filter).to receive(:filter?).with(record3).ordered.and_return(false)
122
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record3).ordered
123
+
124
+ feed_runner.run
125
+ end
126
+ end
127
+
128
+ context "decoding" do
129
+ before :each do
130
+ allow(fetcher).to receive(:fetch).and_return(io)
131
+ end
132
+ let(:decoder1) { double("decoder") }
133
+ let(:decoder2) { double("decoder") }
134
+ let(:decoder3) { double("decoder") }
135
+ let(:decoder_builders) { [ lambda {decoder1}, lambda {decoder2}, lambda {decoder3} ] }
136
+ it "should run through each decoder in the order it was added to the feed" do
137
+ decoded_io1 = double("decoded_io1")
138
+ decoded_io2 = double("decoded_io2")
139
+ decoded_io3 = double("decoded_io3")
140
+
141
+ expect(decoder1).to receive(:decode).with(io).and_return(decoded_io1)
142
+ expect(decoder2).to receive(:decode).with(decoded_io1).and_return(decoded_io2)
143
+ expect(decoder3).to receive(:decode).with(decoded_io2).and_return(decoded_io3)
144
+ expect(parser).to receive(:run).with(decoded_io3)
145
+ feed_runner.run
146
+ end
147
+
148
+ it "should skip decoding if the :skip_decoding was set to true" do
149
+ expect(parser).to receive(:run).with(io)
150
+ feed_runner.run(skip_decoding: true)
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/feed'
3
+
4
+ describe Threatinator::Feed do
5
+ let (:provider) { 'FakeSecureCo' }
6
+ let (:name) { 'MaliciousDataFeed' }
7
+ let(:fetcher_io) { double("io") }
8
+ let(:parser_block) { lambda {} }
9
+ let(:filter_builders) { [] }
10
+ let(:decoder_builders) { [] }
11
+ let(:fetcher_builder) { lambda { FeedSpec::Fetcher.new({}) } }
12
+ let(:parser_builder) { lambda { FeedSpec::Parser.new({}) { } } }
13
+
14
+ let(:feed_opts) {
15
+ {
16
+ :provider => provider,
17
+ :name => name,
18
+ :parser_block => parser_block,
19
+ :fetcher_builder => fetcher_builder,
20
+ :parser_builder => parser_builder,
21
+ :filter_builders => filter_builders,
22
+ :decoder_builders => filter_builders,
23
+ }
24
+ }
25
+
26
+ shared_examples_for "a field with an invalid value" do |value|
27
+ it "should raise InvalidAttributeError if it is a #{value.class}" do
28
+ expect {
29
+ described_class.new(feed_opts.merge(field => value))
30
+ }.to raise_error(Threatinator::Exceptions::InvalidAttributeError)
31
+ end
32
+ end
33
+
34
+ shared_examples_for "a field with a valid value" do |value|
35
+ it "should not raise any errors when set to a #{value.class}" do
36
+ expect {
37
+ described_class.new(feed_opts.merge(field => value))
38
+ }.not_to raise_error
39
+ end
40
+ end
41
+
42
+ shared_examples_for "a field that is required" do
43
+ it "is required" do
44
+ feed_opts.delete(field)
45
+ expect { described_class.new(feed_opts) }.to raise_error(Threatinator::Exceptions::InvalidAttributeError)
46
+ end
47
+ end
48
+
49
+ shared_examples_for "a field with a default" do |value|
50
+ it "should default to #{value.inspect}" do
51
+ feed_opts.delete(field)
52
+ feed = described_class.new(feed_opts)
53
+ expect(feed.send(field)).to eq(value)
54
+ end
55
+ end
56
+
57
+ shared_examples_for "a field that is immutable" do
58
+ it "should be immutable" do
59
+ feed = described_class.new(feed_opts)
60
+ expect(feed.send(field)).not_to be(feed.send(field))
61
+ end
62
+ end
63
+
64
+ describe ":provider" do
65
+ let(:field) { :provider }
66
+ include_examples "a field that is required"
67
+ include_examples "a field that is immutable"
68
+ include_examples "a field with a valid value", "asdf"
69
+ include_examples "a field with an invalid value", 1234
70
+ include_examples "a field with an invalid value", :asdf
71
+ include_examples "a field with an invalid value", []
72
+ include_examples "a field with an invalid value", {}
73
+ end
74
+
75
+ describe ":name" do
76
+ let(:field) { :name }
77
+ include_examples "a field that is required", :name
78
+ include_examples "a field that is immutable"
79
+ include_examples "a field with a valid value", "asdf"
80
+ include_examples "a field with an invalid value", 1234
81
+ include_examples "a field with an invalid value", :asdf
82
+ include_examples "a field with an invalid value", []
83
+ include_examples "a field with an invalid value", {}
84
+ end
85
+
86
+ describe ":fetcher_builder" do
87
+ let(:field) { :fetcher_builder}
88
+ include_examples "a field that is required", :fetcher_builder
89
+ include_examples "a field with a valid value", lambda { }
90
+ include_examples "a field with an invalid value", Class.new
91
+ include_examples "a field with an invalid value", 1234
92
+ include_examples "a field with an invalid value", :asdf
93
+ include_examples "a field with an invalid value", []
94
+ include_examples "a field with an invalid value", {}
95
+ end
96
+
97
+ describe ":parser_builder" do
98
+ let(:field) { :parser_builder }
99
+ include_examples "a field that is required", :parser_builder
100
+ include_examples "a field with a valid value", lambda { }
101
+ include_examples "a field with an invalid value", Class.new
102
+ include_examples "a field with an invalid value", 1234
103
+ include_examples "a field with an invalid value", :asdf
104
+ include_examples "a field with an invalid value", []
105
+ include_examples "a field with an invalid value", {}
106
+ end
107
+
108
+ describe ":filter_builders" do
109
+ let(:field) { :filter_builders}
110
+ include_examples "a field with a default", []
111
+ include_examples "a field with a valid value", []
112
+ include_examples "a field with a valid value", [ lambda { } ]
113
+ include_examples "a field that is immutable"
114
+ include_examples "a field with an invalid value", Class.new
115
+ include_examples "a field with an invalid value", 1234
116
+ include_examples "a field with an invalid value", :asdf
117
+ include_examples "a field with an invalid value", [1,2,3]
118
+ include_examples "a field with an invalid value", {}
119
+ end
120
+
121
+ describe ":decoder_builders" do
122
+ let(:field) { :decoder_builders}
123
+ include_examples "a field with a default", []
124
+ include_examples "a field with a valid value", []
125
+ include_examples "a field with a valid value", [ lambda { } ]
126
+ include_examples "a field that is immutable"
127
+ include_examples "a field with an invalid value", Class.new
128
+ include_examples "a field with an invalid value", 1234
129
+ include_examples "a field with an invalid value", :asdf
130
+ include_examples "a field with an invalid value", [1,2,3]
131
+ include_examples "a field with an invalid value", {}
132
+ end
133
+
134
+ describe ":parser_block" do
135
+ let(:field) { :parser_block }
136
+ include_examples "a field that is required", :parser_block
137
+ include_examples "a field with a valid value", lambda {}
138
+ include_examples "a field with an invalid value", 1234
139
+ include_examples "a field with an invalid value", :asdf
140
+ include_examples "a field with an invalid value", []
141
+ end
142
+
143
+ context "when initialized with required fields" do
144
+ let (:feed) do
145
+ described_class.new(feed_opts)
146
+ end
147
+
148
+ describe "#name" do
149
+ it "should return the name" do
150
+ expect(feed.name).to eq(name)
151
+ end
152
+ end
153
+
154
+ describe "#provider" do
155
+ it "should return the provider" do
156
+ expect(feed.provider).to eq(provider)
157
+ end
158
+ end
159
+
160
+ describe "#parser_block" do
161
+ it "should return the parser_block" do
162
+ expect(feed.parser_block).to eq(parser_block)
163
+ end
164
+ end
165
+
166
+ end
167
+ end
168
+
169
+