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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/CONTRIBUTING.md +119 -0
- data/Gemfile +28 -0
- data/LICENSE +165 -0
- data/README.md +45 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/bin/threatinator +5 -0
- data/lib/threatinator.rb +3 -0
- data/lib/threatinator/action.rb +14 -0
- data/lib/threatinator/actions/list.rb +2 -0
- data/lib/threatinator/actions/list/action.rb +53 -0
- data/lib/threatinator/actions/list/config.rb +10 -0
- data/lib/threatinator/actions/run.rb +2 -0
- data/lib/threatinator/actions/run/action.rb +45 -0
- data/lib/threatinator/actions/run/config.rb +32 -0
- data/lib/threatinator/actions/run/coverage_observer.rb +54 -0
- data/lib/threatinator/actions/run/output_config.rb +59 -0
- data/lib/threatinator/cli.rb +13 -0
- data/lib/threatinator/cli/action_builder.rb +33 -0
- data/lib/threatinator/cli/list_action_builder.rb +19 -0
- data/lib/threatinator/cli/parser.rb +113 -0
- data/lib/threatinator/cli/run_action_builder.rb +41 -0
- data/lib/threatinator/config.rb +6 -0
- data/lib/threatinator/config/base.rb +35 -0
- data/lib/threatinator/config/feed_search.rb +25 -0
- data/lib/threatinator/decoder.rb +24 -0
- data/lib/threatinator/decoders/gzip.rb +30 -0
- data/lib/threatinator/event.rb +27 -0
- data/lib/threatinator/event_builder.rb +41 -0
- data/lib/threatinator/exceptions.rb +61 -0
- data/lib/threatinator/feed.rb +82 -0
- data/lib/threatinator/feed_builder.rb +156 -0
- data/lib/threatinator/feed_registry.rb +47 -0
- data/lib/threatinator/feed_runner.rb +118 -0
- data/lib/threatinator/fetcher.rb +22 -0
- data/lib/threatinator/fetchers/http.rb +46 -0
- data/lib/threatinator/filter.rb +12 -0
- data/lib/threatinator/filters/block.rb +18 -0
- data/lib/threatinator/filters/comments.rb +16 -0
- data/lib/threatinator/filters/whitespace.rb +19 -0
- data/lib/threatinator/output.rb +50 -0
- data/lib/threatinator/parser.rb +23 -0
- data/lib/threatinator/parsers/csv.rb +7 -0
- data/lib/threatinator/parsers/csv/parser.rb +77 -0
- data/lib/threatinator/parsers/getline.rb +8 -0
- data/lib/threatinator/parsers/getline/parser.rb +45 -0
- data/lib/threatinator/parsers/json.rb +8 -0
- data/lib/threatinator/parsers/json/adapters/oj.rb +65 -0
- data/lib/threatinator/parsers/json/parser.rb +45 -0
- data/lib/threatinator/parsers/json/record.rb +20 -0
- data/lib/threatinator/parsers/xml.rb +8 -0
- data/lib/threatinator/parsers/xml/node.rb +79 -0
- data/lib/threatinator/parsers/xml/node_builder.rb +39 -0
- data/lib/threatinator/parsers/xml/parser.rb +44 -0
- data/lib/threatinator/parsers/xml/path.rb +70 -0
- data/lib/threatinator/parsers/xml/pattern.rb +53 -0
- data/lib/threatinator/parsers/xml/record.rb +14 -0
- data/lib/threatinator/parsers/xml/sax_document.rb +64 -0
- data/lib/threatinator/plugin_loader.rb +115 -0
- data/lib/threatinator/plugins/output/csv.rb +47 -0
- data/lib/threatinator/plugins/output/null.rb +17 -0
- data/lib/threatinator/plugins/output/rubydebug.rb +16 -0
- data/lib/threatinator/property_definer.rb +101 -0
- data/lib/threatinator/record.rb +22 -0
- data/lib/threatinator/registry.rb +53 -0
- data/lib/threatinator/util.rb +15 -0
- data/spec/feeds/ET_compromised-ip_reputation_spec.rb +50 -0
- data/spec/feeds/alienvault-ip_reputation_spec.rb +50 -0
- data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +50 -0
- data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/autoshun_shunlist_spec.rb +42 -0
- data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +50 -0
- data/spec/feeds/ciarmy-ip_reputation_spec.rb +50 -0
- data/spec/feeds/cruzit-ip_reputation_spec.rb +50 -0
- data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +50 -0
- data/spec/feeds/data/ET_compromised-ip_reputation.txt +11 -0
- data/spec/feeds/data/alienvault-ip_reputation.txt +18 -0
- data/spec/feeds/data/arbor_domainlist.txt +11 -0
- data/spec/feeds/data/arbor_ssh.txt +16 -0
- data/spec/feeds/data/autoshun_shunlist.csv +20 -0
- data/spec/feeds/data/blocklist_de_apache-ip-reputation.txt +17 -0
- data/spec/feeds/data/blocklist_de_bots-ip-reputation.txt +15 -0
- data/spec/feeds/data/blocklist_de_ftp-ip-reputation.txt +7 -0
- data/spec/feeds/data/blocklist_de_imap-ip-reputation.txt +8 -0
- data/spec/feeds/data/blocklist_de_pop3-ip-reputation.txt +11 -0
- data/spec/feeds/data/blocklist_de_proftpd-ip-reputation.txt +12 -0
- data/spec/feeds/data/blocklist_de_sip-ip-reputation.txt +9 -0
- data/spec/feeds/data/blocklist_de_ssh-ip-reputation.txt +10 -0
- data/spec/feeds/data/blocklist_de_strongips-ip-reputation.txt +11 -0
- data/spec/feeds/data/ciarmy-ip-reputation.txt +11 -0
- data/spec/feeds/data/cruzit-ip-reputation.txt +14 -0
- data/spec/feeds/data/dan_me_uk_torlist-ip-reputation.txt +11 -0
- data/spec/feeds/data/dshield_topattackers.xml +4 -0
- data/spec/feeds/data/feodo_domainlist.txt +18 -0
- data/spec/feeds/data/feodo_iplist.txt +20 -0
- data/spec/feeds/data/infiltrated_iplist.txt +16 -0
- data/spec/feeds/data/malc0de_domainlist.txt +18 -0
- data/spec/feeds/data/malc0de_iplist.txt +14 -0
- data/spec/feeds/data/mirc_domainlist.txt +31 -0
- data/spec/feeds/data/nothink_irc_iplist.txt +14 -0
- data/spec/feeds/data/nothink_ssh_iplist.txt +10 -0
- data/spec/feeds/data/openbl_iplist.txt +12 -0
- data/spec/feeds/data/palevo_domainlist.txt +25 -0
- data/spec/feeds/data/palevo_iplist.txt +24 -0
- data/spec/feeds/data/phishtank-sample.json.gz +0 -0
- data/spec/feeds/data/spyeye_domainlist.txt +16 -0
- data/spec/feeds/data/spyeye_iplist.txt +19 -0
- data/spec/feeds/data/t-arend-de_ssh_iplist.txt +17 -0
- data/spec/feeds/data/the_haleys_ssh_iplist.txt +12 -0
- data/spec/feeds/data/yourcmc_ssh-ip_reputation.txt +27 -0
- data/spec/feeds/data/zeus-ip_reputation.txt +285 -0
- data/spec/feeds/data/zeus_domainlist.txt +27 -0
- data/spec/feeds/dshield_attackers-top1000_spec.rb +43 -0
- data/spec/feeds/feodo-domain_reputation_spec.rb +50 -0
- data/spec/feeds/feodo-ip_reputation_spec.rb +50 -0
- data/spec/feeds/infiltrated-ip_reputation_spec.rb +50 -0
- data/spec/feeds/malc0de-domain_reputation_spec.rb +50 -0
- data/spec/feeds/malc0de-ip_reputation_spec.rb +50 -0
- data/spec/feeds/mirc-domain_reputation_spec.rb +50 -0
- data/spec/feeds/nothink_irc-ip_reputation_spec.rb +50 -0
- data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/openbl-ip_reputation_spec.rb +50 -0
- data/spec/feeds/palevo-domain_reputation_spec.rb +50 -0
- data/spec/feeds/palevo-ip_reputation_spec.rb +50 -0
- data/spec/feeds/phishtank_spec.rb +45 -0
- data/spec/feeds/spyeye-domain_reputation_spec.rb +50 -0
- data/spec/feeds/spyeye-ip_reputation_spec.rb +50 -0
- data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/zeus-domain_reputation_spec.rb +50 -0
- data/spec/feeds/zeus-ip_reputation_spec.rb +50 -0
- data/spec/fixtures/feed/provider1/feed1.feed +6 -0
- data/spec/fixtures/parsers/test.xml +13 -0
- data/spec/fixtures/parsers/test_self_closing.xml +20 -0
- data/spec/fixtures/plugins/bad/threatinator/plugins/test_error1/plugin.rb +1 -0
- data/spec/fixtures/plugins/bad/threatinator/plugins/test_missing1/plugin.rb +0 -0
- data/spec/fixtures/plugins/fake.rb +19 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_a.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_b.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_c.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_d.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_e.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_f.rb +8 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/support/bad_feeds/missing_fetcher.feed +7 -0
- data/spec/support/bad_feeds/missing_name.feed +6 -0
- data/spec/support/bad_feeds/missing_parser.feed +3 -0
- data/spec/support/bad_feeds/missing_provider.feed +5 -0
- data/spec/support/factories/event.rb +27 -0
- data/spec/support/factories/feed.rb +32 -0
- data/spec/support/factories/feed_builder.rb +65 -0
- data/spec/support/factories/feed_registry.rb +8 -0
- data/spec/support/factories/output.rb +11 -0
- data/spec/support/factories/record.rb +17 -0
- data/spec/support/factories/xml_node.rb +33 -0
- data/spec/support/helpers/io.rb +11 -0
- data/spec/support/helpers/models.rb +13 -0
- data/spec/support/shared/action_builder.rb +47 -0
- data/spec/support/shared/decoder.rb +70 -0
- data/spec/support/shared/feeds.rb +218 -0
- data/spec/support/shared/fetcher.rb +48 -0
- data/spec/support/shared/filter.rb +14 -0
- data/spec/support/shared/io-like.rb +7 -0
- data/spec/support/shared/output.rb +120 -0
- data/spec/support/shared/parsers.rb +51 -0
- data/spec/support/shared/record.rb +111 -0
- data/spec/threatinator/actions/list/action_spec.rb +93 -0
- data/spec/threatinator/actions/run/action_spec.rb +89 -0
- data/spec/threatinator/actions/run/config_spec.rb +39 -0
- data/spec/threatinator/actions/run/coverage_observer_spec.rb +116 -0
- data/spec/threatinator/actions/run/output_config_spec.rb +89 -0
- data/spec/threatinator/cli/list_action_builder_spec.rb +57 -0
- data/spec/threatinator/cli/run_action_builder_spec.rb +133 -0
- data/spec/threatinator/cli_spec.rb +175 -0
- data/spec/threatinator/config/base_spec.rb +39 -0
- data/spec/threatinator/config/feed_search_spec.rb +76 -0
- data/spec/threatinator/decoders/gzip_spec.rb +75 -0
- data/spec/threatinator/event_builder_spec.rb +33 -0
- data/spec/threatinator/event_spec.rb +30 -0
- data/spec/threatinator/feed_builder_spec.rb +636 -0
- data/spec/threatinator/feed_registry_spec.rb +198 -0
- data/spec/threatinator/feed_runner_spec.rb +155 -0
- data/spec/threatinator/feed_spec.rb +169 -0
- data/spec/threatinator/fetcher_spec.rb +12 -0
- data/spec/threatinator/fetchers/http_spec.rb +32 -0
- data/spec/threatinator/filter_spec.rb +13 -0
- data/spec/threatinator/filters/block_spec.rb +16 -0
- data/spec/threatinator/filters/comments_spec.rb +13 -0
- data/spec/threatinator/filters/whitespace_spec.rb +12 -0
- data/spec/threatinator/parser_spec.rb +13 -0
- data/spec/threatinator/parsers/csv/parser_spec.rb +202 -0
- data/spec/threatinator/parsers/getline/parser_spec.rb +83 -0
- data/spec/threatinator/parsers/json/parser_spec.rb +106 -0
- data/spec/threatinator/parsers/json/record_spec.rb +30 -0
- data/spec/threatinator/parsers/xml/node_spec.rb +335 -0
- data/spec/threatinator/parsers/xml/parser_spec.rb +263 -0
- data/spec/threatinator/parsers/xml/path_spec.rb +209 -0
- data/spec/threatinator/parsers/xml/pattern_spec.rb +72 -0
- data/spec/threatinator/parsers/xml/record_spec.rb +27 -0
- data/spec/threatinator/plugin_loader_spec.rb +238 -0
- data/spec/threatinator/plugins/output/csv_spec.rb +46 -0
- data/spec/threatinator/plugins/output/null_spec.rb +17 -0
- data/spec/threatinator/plugins/output/rubydebug_spec.rb +37 -0
- data/spec/threatinator/property_definer_spec.rb +155 -0
- data/spec/threatinator/record_spec.rb +19 -0
- data/spec/threatinator/registry_spec.rb +97 -0
- data/spec/threatinator/runner_spec.rb +273 -0
- 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
|
+
|