threatinator 0.1.6 → 0.2.0

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 (197) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -2
  3. data/Gemfile +18 -13
  4. data/Rakefile +1 -1
  5. data/VERSION +1 -1
  6. data/feeds/ET_block-ip_reputation.feed +26 -0
  7. data/feeds/ET_openbadlist-ip_reputation.feed +35 -0
  8. data/feeds/bambenek_c2_masterlist-domain_reputation.feed +15 -0
  9. data/feeds/bambenek_c2_masterlist-ip_reputation.feed +15 -0
  10. data/feeds/bambenek_dga_feed-domain_reputation.feed +15 -0
  11. data/feeds/berkeley-ip_reputation.feed +23 -0
  12. data/feeds/bitcash_cz_blacklist.feed +20 -0
  13. data/feeds/botscout-ip_reputation.feed +24 -0
  14. data/feeds/cert_mxpoison-ip_reputation.feed +21 -0
  15. data/feeds/chaosreigns-ip_reputation.feed +36 -0
  16. data/feeds/cydef_torexit-ip_reputation.feed +24 -0
  17. data/feeds/danger_bruteforce-ip_reputation.feed +23 -0
  18. data/feeds/falconcrest-ip_reputation.feed +18 -0
  19. data/feeds/h3x_asprox.feed +17 -0
  20. data/feeds/hosts-file_hphostspartial-domain_reputation.feed +18 -0
  21. data/feeds/infiltrated_vabl-ip_reputation.feed +29 -0
  22. data/feeds/isc_suspicious_high-domain_reputation.feed +25 -0
  23. data/feeds/isc_suspicious_low-domain_reputation.feed +25 -0
  24. data/feeds/isc_suspicious_medium-domain_reputation.feed +25 -0
  25. data/feeds/malwaredomainlist-url_reputation.feed +16 -0
  26. data/feeds/malwaredomains-domain_reputation.feed +27 -0
  27. data/feeds/malwaredomains_dyndns-domain_reputation.feed +27 -0
  28. data/feeds/malwaredomains_justdomains-domain_reputation.feed +18 -0
  29. data/feeds/multiproxy-ip_reputation.feed +20 -0
  30. data/feeds/openphish-url_reputation.feed +22 -0
  31. data/feeds/packetmail_perimeterbad-ip_reputation.feed +26 -0
  32. data/feeds/phishtank.feed +1 -1
  33. data/feeds/sigmaproject_atma.feed +25 -0
  34. data/feeds/sigmaproject_spyware.feed +24 -0
  35. data/feeds/sigmaproject_webexploit.feed +26 -0
  36. data/feeds/snort_bpf-ip_reputation.feed +19 -0
  37. data/feeds/steeman-ip_reputation.feed +19 -0
  38. data/feeds/trustedsec-ip_reputation.feed +17 -0
  39. data/feeds/virbl-ip_reputation.feed +24 -0
  40. data/feeds/vxvault-url_reputation.feed +22 -0
  41. data/feeds/yoyo_adservers-domain_reputation.feed +16 -0
  42. data/lib/threatinator/actions/run/action.rb +15 -3
  43. data/lib/threatinator/actions/run/coverage_observer.rb +12 -7
  44. data/lib/threatinator/actions/run/status_observer.rb +37 -0
  45. data/lib/threatinator/cli.rb +9 -3
  46. data/lib/threatinator/cli/parser.rb +14 -4
  47. data/lib/threatinator/config.rb +1 -0
  48. data/lib/threatinator/config/logger.rb +14 -0
  49. data/lib/threatinator/event.rb +28 -18
  50. data/lib/threatinator/event_builder.rb +52 -23
  51. data/lib/threatinator/exceptions.rb +3 -6
  52. data/lib/threatinator/feed.rb +1 -1
  53. data/lib/threatinator/feed_runner.rb +63 -7
  54. data/lib/threatinator/logger.rb +66 -0
  55. data/lib/threatinator/logging.rb +20 -0
  56. data/lib/threatinator/model/base.rb +23 -0
  57. data/lib/threatinator/model/collection.rb +64 -0
  58. data/lib/threatinator/model/observables/fqdn_collection.rb +13 -0
  59. data/lib/threatinator/model/observables/ipv4.rb +30 -0
  60. data/lib/threatinator/model/observables/ipv4_collection.rb +14 -0
  61. data/lib/threatinator/model/observables/url_collection.rb +16 -0
  62. data/lib/threatinator/model/validations.rb +1 -0
  63. data/lib/threatinator/model/validations/type.rb +21 -0
  64. data/lib/threatinator/plugins/output/csv.rb +20 -9
  65. data/spec/feeds/ET_block-ip_reputation_spec.rb +50 -0
  66. data/spec/feeds/ET_compromised-ip_reputation_spec.rb +2 -5
  67. data/spec/feeds/ET_openbadlist-ip_reputation_spec.rb +56 -0
  68. data/spec/feeds/alienvault-ip_reputation_spec.rb +2 -5
  69. data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +0 -3
  70. data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +2 -5
  71. data/spec/feeds/autoshun_shunlist_spec.rb +1 -4
  72. data/spec/feeds/bambenek_c2_masterlist-domain_reputation_spec.rb +39 -0
  73. data/spec/feeds/bambenek_c2_masterlist-ip_reputation_spec.rb +39 -0
  74. data/spec/feeds/bambenek_dga_feed-domain_reputation_spec.rb +39 -0
  75. data/spec/feeds/berkeley-ip_reputation_spec.rb +47 -0
  76. data/spec/feeds/bitcash_cz_blacklist-ip_reputation_spec.rb +50 -0
  77. data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +2 -5
  78. data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +2 -5
  79. data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +2 -5
  80. data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +2 -5
  81. data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +2 -5
  82. data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +2 -5
  83. data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +2 -5
  84. data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +2 -5
  85. data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +2 -5
  86. data/spec/feeds/botscout-ip_reputation_spec.rb +50 -0
  87. data/spec/feeds/cert_mxpoison-ip_reputation_spec.rb +47 -0
  88. data/spec/feeds/chaosreigns-ip_reputation_spec.rb +50 -0
  89. data/spec/feeds/ciarmy-ip_reputation_spec.rb +2 -5
  90. data/spec/feeds/cruzit-ip_reputation_spec.rb +2 -5
  91. data/spec/feeds/cydef_torexit-ip_reputation_spec.rb +47 -0
  92. data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +2 -5
  93. data/spec/feeds/danger_bruteforce-ip_reputation_spec.rb +47 -0
  94. data/spec/feeds/data/ET_block-ip_reputation.txt +80 -0
  95. data/spec/feeds/data/ET_openbadlist-ip_reputation.txt +62 -0
  96. data/spec/feeds/data/bambenek_c2-dommasterlist.csv +30 -0
  97. data/spec/feeds/data/bambenek_c2-ipmasterlist.csv +27 -0
  98. data/spec/feeds/data/bambenek_dga_feed.csv +42 -0
  99. data/spec/feeds/data/berkeley.txt +29 -0
  100. data/spec/feeds/data/bitcash_cz_blacklist.txt +7 -0
  101. data/spec/feeds/data/botscout-ip-reputation.txt +713 -0
  102. data/spec/feeds/data/cert_mxpoison-ip_reputation.txt +17 -0
  103. data/spec/feeds/data/chaosreigns-ip-reputation.txt +26 -0
  104. data/spec/feeds/data/cydef_torexit-ip_reputation.txt +27 -0
  105. data/spec/feeds/data/danger_bruteforce-ip_reputation.txt +12 -0
  106. data/spec/feeds/data/falconcrest_iplist.txt +345 -0
  107. data/spec/feeds/data/h3x_asprox.txt +20 -0
  108. data/spec/feeds/data/hosts-file_hphostspartial_domainlist.txt +24 -0
  109. data/spec/feeds/data/infiltrated_vabl_iplist.txt +33 -0
  110. data/spec/feeds/data/isc_suspicious_high_domainlist.txt +26 -0
  111. data/spec/feeds/data/isc_suspicious_low_domainlist.txt +34 -0
  112. data/spec/feeds/data/isc_suspicious_medium_domainlist.txt +32 -0
  113. data/spec/feeds/data/malwaredomainlist-url-reputation.txt +8 -0
  114. data/spec/feeds/data/malwaredomains_domainlist.txt +24 -0
  115. data/spec/feeds/data/malwaredomains_dyndns_domainlist.txt +34 -0
  116. data/spec/feeds/data/malwaredomains_justdomains_domainlist.txt +18 -0
  117. data/spec/feeds/data/multiproxy_iplist.txt +15 -0
  118. data/spec/feeds/data/openphish-url-reputation.txt +16 -0
  119. data/spec/feeds/data/packetmail_perimeterbad-ip_reputation.txt +44 -0
  120. data/spec/feeds/data/sigmaproject_atma.return.gz +0 -0
  121. data/spec/feeds/data/sigmaproject_spyware.return.gz +0 -0
  122. data/spec/feeds/data/sigmaproject_webexploit.return.gz +0 -0
  123. data/spec/feeds/data/snort_bpf-ip_reputation.txt +16 -0
  124. data/spec/feeds/data/steeman-ip-reputation.txt +13 -0
  125. data/spec/feeds/data/trustedsec-ip-reputation.txt +12 -0
  126. data/spec/feeds/data/virbl-ip_reputation.txt +14 -0
  127. data/spec/feeds/data/vxvault-url-reputation.txt +15 -0
  128. data/spec/feeds/data/yoyo_adservers.txt +25 -0
  129. data/spec/feeds/dshield_attackers-top1000_spec.rb +1 -4
  130. data/spec/feeds/falconcrest-ip_reputation_spec.rb +37 -0
  131. data/spec/feeds/feodo-domain_reputation_spec.rb +0 -3
  132. data/spec/feeds/feodo-ip_reputation_spec.rb +2 -5
  133. data/spec/feeds/h3x_asprox-ip_reputation_spec.rb +50 -0
  134. data/spec/feeds/hosts-file_hphostspartial-domain_reputation_spec.rb +47 -0
  135. data/spec/feeds/infiltrated-ip_reputation_spec.rb +2 -5
  136. data/spec/feeds/infiltrated_vabl-ip_reputation_spec.rb +47 -0
  137. data/spec/feeds/isc_suspicious_high-domain_reputation_spec.rb +47 -0
  138. data/spec/feeds/isc_suspicious_low-domain_reputation_spec.rb +47 -0
  139. data/spec/feeds/isc_suspicious_medium-domain_reputation_spec.rb +47 -0
  140. data/spec/feeds/malc0de-domain_reputation_spec.rb +0 -3
  141. data/spec/feeds/malc0de-ip_reputation_spec.rb +2 -5
  142. data/spec/feeds/malwaredomainlist_url_reputation_spec.rb +50 -0
  143. data/spec/feeds/malwaredomains-domain_reputation_spec.rb +47 -0
  144. data/spec/feeds/malwaredomains_dyndns-domain_reputation_spec.rb +47 -0
  145. data/spec/feeds/malwaredomains_justdomains-domain_reputation_spec.rb +47 -0
  146. data/spec/feeds/mirc-domain_reputation_spec.rb +0 -3
  147. data/spec/feeds/multiproxy-ip_reputation_spec.rb +47 -0
  148. data/spec/feeds/nothink_irc-ip_reputation_spec.rb +2 -5
  149. data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +2 -5
  150. data/spec/feeds/openbl-ip_reputation_spec.rb +2 -5
  151. data/spec/feeds/openphish_url_reputation_spec.rb +50 -0
  152. data/spec/feeds/packetmail_perimeterbad-ip_reputation_spec.rb +47 -0
  153. data/spec/feeds/palevo-domain_reputation_spec.rb +0 -3
  154. data/spec/feeds/palevo-ip_reputation_spec.rb +2 -5
  155. data/spec/feeds/phishtank_spec.rb +2 -5
  156. data/spec/feeds/sigmaproject_atma_spec.rb +63 -0
  157. data/spec/feeds/sigmaproject_spyware_spec.rb +64 -0
  158. data/spec/feeds/sigmaproject_webexploit_spec.rb +63 -0
  159. data/spec/feeds/snort_bpf-ip_reputation_spec.rb +47 -0
  160. data/spec/feeds/spyeye-domain_reputation_spec.rb +0 -3
  161. data/spec/feeds/spyeye-ip_reputation_spec.rb +2 -5
  162. data/spec/feeds/steeman-ip_reputation_spec.rb +50 -0
  163. data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +2 -5
  164. data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +2 -5
  165. data/spec/feeds/trustedsec-ip_reputation_spec.rb +47 -0
  166. data/spec/feeds/virbl-ip_reputation_spec.rb +47 -0
  167. data/spec/feeds/vxvault_url_reputation_spec.rb +50 -0
  168. data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +2 -5
  169. data/spec/feeds/yoyo_adservers_spec.rb +47 -0
  170. data/spec/feeds/zeus-domain_reputation_spec.rb +0 -3
  171. data/spec/feeds/zeus-ip_reputation_spec.rb +2 -5
  172. data/spec/spec_helper.rb +2 -0
  173. data/spec/support/factories/event.rb +11 -7
  174. data/spec/support/factories/feed.rb +28 -1
  175. data/spec/support/factories/ipv4.rb +36 -0
  176. data/spec/support/factories/url.rb +34 -0
  177. data/spec/support/shared/feed_runner_observer.rb +136 -0
  178. data/spec/support/shared/feeds.rb +19 -4
  179. data/spec/support/shared/model/collection.rb +164 -0
  180. data/spec/threatinator/actions/run/action_spec.rb +27 -10
  181. data/spec/threatinator/actions/run/coverage_observer_spec.rb +39 -4
  182. data/spec/threatinator/actions/run/status_observer_spec.rb +86 -0
  183. data/spec/threatinator/event_builder_spec.rb +111 -21
  184. data/spec/threatinator/event_spec.rb +237 -13
  185. data/spec/threatinator/event_spec.rb.new +319 -0
  186. data/spec/threatinator/feed_builder_spec.rb +0 -3
  187. data/spec/threatinator/feed_runner_spec.rb +254 -70
  188. data/spec/threatinator/logger_spec.rb +29 -0
  189. data/spec/threatinator/model/observables/fqdn_collection_spec.rb +42 -0
  190. data/spec/threatinator/model/observables/ipv4_collection_spec.rb +36 -0
  191. data/spec/threatinator/model/observables/ipv4_spec.rb +75 -0
  192. data/spec/threatinator/model/observables/url_collection_spec.rb +45 -0
  193. data/spec/threatinator/model/validations/type_spec.rb +37 -0
  194. data/spec/threatinator/plugins/output/csv_spec.rb +4 -3
  195. metadata +216 -19
  196. data/lib/threatinator/property_definer.rb +0 -101
  197. data/spec/threatinator/property_definer_spec.rb +0 -155
@@ -466,7 +466,6 @@ parse_eachline(:separator => "\n") {}'
466
466
  feed_loader.call(feed_string).build
467
467
  end.to raise_error { |e|
468
468
  expect(e).to be_a(Threatinator::Exceptions::InvalidAttributeError)
469
- expect(e.attribute).to eq(:provider)
470
469
  }
471
470
  end
472
471
 
@@ -479,7 +478,6 @@ parse_eachline(:separator => "\n") {}'
479
478
  feed_loader.call(feed_string).build
480
479
  end.to raise_error { |e|
481
480
  expect(e).to be_a(Threatinator::Exceptions::InvalidAttributeError)
482
- expect(e.attribute).to eq(:name)
483
481
  }
484
482
  end
485
483
 
@@ -492,7 +490,6 @@ parse_eachline(:separator => "\n") {}'
492
490
  feed_loader.call(feed_string).build
493
491
  end.to raise_error { |e|
494
492
  expect(e).to be_a(Threatinator::Exceptions::InvalidAttributeError)
495
- expect(e.attribute).to eq(:fetcher_builder)
496
493
  }
497
494
  end
498
495
 
@@ -2,21 +2,79 @@ require 'spec_helper'
2
2
  require 'threatinator/feed_runner'
3
3
 
4
4
  describe Threatinator::FeedRunner do
5
- let(:output_formatter ) { double("formatter") }
6
- let(:fetcher) { double("fetcher") }
7
- let(:fetcher_builder) { lambda { fetcher } }
5
+ class DummyParser < Threatinator::Parser
6
+ def initialize(records, opts = {})
7
+ @records = records
8
+ end
9
+
10
+ def run(io)
11
+ @records.each do |record|
12
+ yield(record)
13
+ end
14
+ end
15
+ end
16
+
17
+ class DummyFetcher < Threatinator::Fetcher
18
+ def initialize(io, opts = {})
19
+ @io = io
20
+ end
21
+
22
+ def fetch
23
+ return @io
24
+ end
25
+ end
26
+
27
+ class DummyDecoder < Threatinator::Decoder
28
+ def initialize(io)
29
+ @io = io
30
+ end
31
+
32
+ def decode(arg_io)
33
+ return @io
34
+ end
35
+ end
36
+
37
+ class TestObserver
38
+ attr_reader :updates
39
+ def initialize
40
+ @updates = []
41
+ end
42
+
43
+ def update(*args)
44
+ @updates << args
45
+ end
46
+ end
47
+
48
+ class DummyOutput < Threatinator::Output
49
+ def handle_event(event); end
50
+ def finish; end
51
+ end
52
+
53
+ def create_mock_io(name = "io")
54
+ ret = double(name)
55
+ allow(ret).to receive(:close)
56
+ allow(ret).to receive(:closed?).and_return(false)
57
+ ret
58
+ end
8
59
 
9
- let(:io) { double("io") }
10
- let(:parser) { double("parser") }
11
- let(:parser_builder) { lambda { parser} }
60
+ let(:output_formatter ) { DummyOutput.new(nil) }
61
+ let(:io) { create_mock_io("io") }
62
+ let(:fetcher) { DummyFetcher.new(io) }
12
63
 
13
- let(:filter_builders) { [] }
14
- let(:decoder_builders) { [] }
64
+ let(:record1) { Threatinator::Record.new('a1') }
65
+ let(:record2) { Threatinator::Record.new('a2') }
66
+ let(:record3) { Threatinator::Record.new('a3') }
67
+ let(:records) { [ record1, record2, record3 ] }
68
+ let(:parser) { DummyParser.new(records) }
69
+ let(:parser_block) { lambda { |*args| } }
70
+
71
+ let(:filters) { [] }
72
+ let(:decoders) { [] }
15
73
 
16
74
  let(:feed) {
17
- build(:feed, fetcher_builder: fetcher_builder,
18
- parser_builder: parser_builder, filter_builders: filter_builders,
19
- decoder_builders: decoder_builders
75
+ build(:feed, fetcher: fetcher,
76
+ parser: parser, filters: filters,
77
+ decoders: decoders, parser_block: parser_block
20
78
  )
21
79
  }
22
80
 
@@ -61,93 +119,219 @@ describe Threatinator::FeedRunner do
61
119
 
62
120
  context "an instance" do
63
121
  let(:feed_runner) { described_class.new(feed, output_formatter) }
122
+ let(:observer) { TestObserver.new }
123
+
124
+ before :each do
125
+ feed_runner.add_observer(observer)
126
+ allow(observer).to receive(:update).and_call_original
127
+ end
64
128
 
65
129
  describe "#run" do
66
- context "fetching data" do
67
- before :each do
68
- allow(parser).to receive(:run).with(io)
130
+ it "notifies the observer with :start before anything else" do
131
+ expect(observer.updates.first).to be_nil
132
+ feed_runner.run()
133
+ expect(observer.updates.first).to eq([:start])
134
+ end
135
+
136
+ it "closes the last IO instance" do
137
+ allow(fetcher).to receive(:fetch).and_return(io)
138
+ expect(io).to receive(:close)
139
+ feed_runner.run()
140
+ end
141
+
142
+ it "fetches, decodes, and then parses records" do
143
+ expect(observer).to receive(:update).with(:start).ordered
144
+ expect(observer).to receive(:update).with(:start_fetch).ordered
145
+ expect(observer).to receive(:update).with(:end_fetch).ordered
146
+ expect(observer).to receive(:update).with(:start_decode).ordered
147
+ expect(observer).to receive(:update).with(:end_decode).ordered
148
+ expect(observer).to receive(:update).with(:start_parse_record, record1).ordered
149
+ expect(observer).to receive(:update).with(:end_parse_record, record1).ordered
150
+ expect(observer).to receive(:update).with(:start_parse_record, record2).ordered
151
+ expect(observer).to receive(:update).with(:end_parse_record, record2).ordered
152
+ expect(observer).to receive(:update).with(:start_parse_record, record3).ordered
153
+ expect(observer).to receive(:update).with(:end_parse_record, record3).ordered
154
+ expect(observer).to receive(:update).with(:end).ordered
155
+ feed_runner.run()
156
+ end
157
+
158
+ it "notifies the observer with :end last" do
159
+ feed_runner.run()
160
+ expect(observer.updates.last).to eq([:end])
161
+ end
162
+
163
+ context "with :io => io" do
164
+ it "does not build or run the fetcher" do
165
+ expect(feed.fetcher_builder).not_to receive(:call)
166
+ feed_runner.run(:io => io)
69
167
  end
70
168
 
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
169
+ it "does not notify the observer with :start_fetch or :end_fetch" do
170
+ expect(observer).not_to receive(:update).with(:start_fetch)
171
+ expect(observer).not_to receive(:update).with(:end_fetch)
172
+ feed_runner.run(:io => io)
77
173
  end
174
+ end
78
175
 
176
+ context "without :io" do
79
177
  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)
178
+ expect(feed.fetcher_builder).to receive(:call).and_call_original
179
+ expect(fetcher).to receive(:fetch).and_call_original
82
180
  feed_runner.run
83
181
  end
182
+
183
+ it "notifies the observer with :start_fetch, then fetches, then notifies observer with :end_fetch" do
184
+ expect(observer).to receive(:update).with(:start_fetch).ordered
185
+ expect(fetcher).to receive(:fetch).and_call_original.ordered
186
+ expect(observer).to receive(:update).with(:end_fetch).ordered
187
+ feed_runner.run()
188
+ end
84
189
  end
85
190
 
86
- context "parsing" do
87
- before :each do
88
- allow(fetcher).to receive(:fetch).and_return(io)
191
+ it "calls the feed.parser_block for each for each message data parsed" do
192
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record1).ordered
193
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record2).ordered
194
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record3).ordered
195
+ feed_runner.run()
196
+ end
197
+
198
+
199
+ context "when handling record" do
200
+ let(:records) { [ record1 ] }
201
+
202
+ it "notifies observer with :start_parse_record, and the record prior to handling" do
203
+ expect(observer).to receive(:update).with(:start_parse_record, record1).ordered
204
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record1).ordered
205
+ feed_runner.run()
89
206
  end
90
207
 
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)
208
+ it "notifies observer with :end_parse_record, and the record after handling" do
96
209
  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
210
+ expect(observer).to receive(:update).with(:end_parse_record, record1).ordered
211
+ feed_runner.run()
212
+ end
213
+
214
+ context "when the record is parsed into one or more events" do
215
+ let(:parser_block) {
216
+ lambda do |cep, record|
217
+ cep.call do |eb|
218
+ eb.type = :c2
219
+ eb.add_ipv4('1.1.1.1')
220
+ end
221
+ cep.call do |eb|
222
+ eb.type = :c2
223
+ eb.add_ipv4('2.2.2.2')
224
+ end
225
+ end
226
+ }
227
+
228
+ it "notifies the observer with (:record_parsed, record, events) for each event" do
229
+ expect(observer).to receive(:update).with(
230
+ :record_parsed, record1, satisfy { |events|
231
+ expect(events[0].ipv4s).to contain_exactly(build(:ipv4, ipv4: '1.1.1.1'))
232
+ expect(events[1].ipv4s).to contain_exactly(build(:ipv4, ipv4: '2.2.2.2'))
233
+ })
234
+
235
+ feed_runner.run()
236
+ end
237
+ end
238
+
239
+ context "when no events have been parsed from the record" do
240
+ let(:parser_block) {
241
+ lambda do |cep, record|
242
+ end
243
+ }
244
+
245
+ it "notifies the observer with (:record_missed, record) for each event" do
246
+ expect(observer).to receive(:update).with(:record_missed, record1)
247
+ feed_runner.run()
248
+ end
249
+ end
250
+
251
+ context "when a record has been filtered" do
252
+ let(:filters) { [ lambda { |record| true } ] }
253
+ it "notifies the observer with (:record_filtered, record)" do
254
+ expect(observer).to receive(:update).with(:record_filtered, record1)
255
+ feed_runner.run()
256
+ end
257
+ end
258
+
259
+ context "when a record has an event that fails to build" do
260
+ let(:parser_block) {
261
+ lambda do |cep, record|
262
+ cep.call do |eb|
263
+ eb.type = :c2
264
+ end
265
+ cep.call do |eb|
266
+ eb.type = :asdf
267
+ end
268
+ end
269
+ }
270
+
271
+ it "notifies the observer with (:record_error, record, array_of_errors)" do
272
+ expect(observer).to receive(:update).with(:record_error, record1, a_collection_containing_exactly(
273
+ kind_of(Threatinator::Exceptions::EventBuildError)
274
+ ))
275
+ feed_runner.run()
276
+ end
277
+
278
+ it "does not notify the observer of any events that may have NOT have errors" do
279
+ expect(observer).not_to receive(:update).with(:record_parsed, record1, kind_of(Object))
280
+ feed_runner.run()
281
+ end
100
282
  end
283
+
101
284
  end
285
+
102
286
  context "filtering" do
287
+ let(:filters) { [ lambda { |record| record.data == "a2" } ] }
288
+
103
289
  before :each do
104
- allow(parser).to receive(:run).with(io)
105
290
  allow(fetcher).to receive(:fetch).and_return(io)
106
291
  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
292
 
120
- expect(filter).to receive(:filter?).with(record2).ordered.and_return(true)
121
- expect(filter).to receive(:filter?).with(record3).ordered.and_return(false)
293
+ it "only calls the parser_block for data that was not filtered" do
294
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record1).ordered
122
295
  expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record3).ordered
123
-
124
- feed_runner.run
296
+ feed_runner.run()
125
297
  end
126
298
  end
127
299
 
128
300
  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
301
+ let(:decoded_io1) { create_mock_io('decoded_io1') }
302
+ let(:decoded_io2) { create_mock_io('decoded_io2') }
303
+ let(:decoded_io3) { create_mock_io('decoded_io3') }
304
+ let(:decoder1) { DummyDecoder.new(decoded_io1) }
305
+ let(:decoder2) { DummyDecoder.new(decoded_io2) }
306
+ let(:decoder3) { DummyDecoder.new(decoded_io3) }
307
+ let(:decoders) { [ decoder1, decoder2, decoder3 ] }
308
+
309
+ context "without :skip_decoding" do
310
+ it "notifies the observer with :start_decode, decodes, and then notifies the observer with :end_decode" do
311
+ expect(observer).to receive(:update).with(:start_decode).ordered
312
+ expect(decoder1).to receive(:decode).with(io).and_call_original.ordered
313
+ expect(decoder2).to receive(:decode).with(decoded_io1).and_call_original.ordered
314
+ expect(decoder3).to receive(:decode).with(decoded_io2).and_call_original.ordered
315
+ expect(observer).to receive(:update).with(:end_decode).ordered
316
+ feed_runner.run
317
+ end
318
+
319
+ it "should run through each decoder in the order it was added to the feed" do
320
+ expect(decoder1).to receive(:decode).with(io).and_call_original
321
+ expect(decoder2).to receive(:decode).with(decoded_io1).and_call_original
322
+ expect(decoder3).to receive(:decode).with(decoded_io2).and_call_original
323
+ expect(parser).to receive(:run).with(decoded_io3)
324
+ feed_runner.run
325
+ end
146
326
  end
147
327
 
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)
328
+ context "with :skip_decoding => true" do
329
+ it "does not decode" do
330
+ expect(observer).not_to receive(:update).with(:start_decode)
331
+ expect(observer).not_to receive(:update).with(:end_decode)
332
+ expect(parser).to receive(:run).with(io)
333
+ feed_runner.run(skip_decoding: true)
334
+ end
151
335
  end
152
336
  end
153
337
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/logger'
3
+ require 'threatinator/config/logger'
4
+
5
+ describe Threatinator::Logger do
6
+ before :each do
7
+ @orig_level = Threatinator::Logger.level
8
+ end
9
+
10
+ after :each do
11
+ Threatinator::Logger.level = @orig_level
12
+ end
13
+
14
+ describe ".configure_logger(config)" do
15
+ let(:config) { Threatinator::Config::Logger.new }
16
+ it "sets the logging level to that specified by config.level" do
17
+ Threatinator::Logger.level = Threatinator::Logger::Levels::FATAL
18
+ config.level = "DEBUG"
19
+ Threatinator::Logger.configure_logger(config)
20
+ expect(Threatinator::Logger.level).to eq(Threatinator::Logger::Levels::DEBUG)
21
+ end
22
+
23
+ it "warns when an unknown logging level is provided" do
24
+ config.level = "FOO"
25
+ expect(Threatinator::Logger.default_logger).to receive(:warn).with(/Ignoring unknown logging level:/)
26
+ Threatinator::Logger.configure_logger(config)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/model/observables/fqdn_collection'
3
+
4
+ describe Threatinator::Model::Observables::FqdnCollection do
5
+ it_behaves_like "a model collection" do
6
+ def generate_ten_valid_members
7
+ ret = []
8
+ 1.upto(10) do |i|
9
+ ret << "domain#{i}.com"
10
+ end
11
+ ret
12
+ end
13
+
14
+ def generate_invalid_members
15
+ [1234, :foobar]
16
+ end
17
+ end
18
+
19
+ let(:collection) { described_class.new }
20
+
21
+ describe "#valid_member?(v)" do
22
+ context "when provided a valid FQDN string" do
23
+ it "returns true" do
24
+ expect(collection.valid_member?("yahoo.com")).to eq(true)
25
+ end
26
+ end
27
+
28
+ context "when provided an invalid FQDN string" do
29
+ it "returns false" do
30
+ pending "doesn't actually validate fqdns, yet"
31
+ expect(collection.valid_member?("yahoo..com")).to eq(false)
32
+ end
33
+ end
34
+
35
+ context "when provided something other than a string" do
36
+ it "returns false" do
37
+ expect(collection.valid_member?(:asdf)).to eq(false)
38
+ expect(collection.valid_member?(1234)).to eq(false)
39
+ end
40
+ end
41
+ end
42
+ end