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,39 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'threatinator/config/base'
|
|
3
|
+
|
|
4
|
+
describe Threatinator::Config::Base do
|
|
5
|
+
describe '.properties' do
|
|
6
|
+
class Foobar3 < Threatinator::Config::Base
|
|
7
|
+
attribute :deep1, String, description: "deep prop1"
|
|
8
|
+
attribute :deep2, String, description: "deep prop2"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Foobar2 < Threatinator::Config::Base
|
|
12
|
+
attribute :nested_prop1, String, description: "Nested prop1"
|
|
13
|
+
attribute :nested_prop2, String, description: "Nested prop2"
|
|
14
|
+
attribute :deep, Foobar3
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Foobar1 < Threatinator::Config::Base
|
|
18
|
+
attribute :prop1, String, description: "This is prop1"
|
|
19
|
+
attribute :prop2, Boolean, description: "This is prop2"
|
|
20
|
+
attribute :nested1, Foobar2
|
|
21
|
+
attribute :nested2, Foobar2
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "should return a hash of all properties and nested properties" do
|
|
25
|
+
expect(Foobar1.properties).to match({
|
|
26
|
+
"prop1" => ["This is prop1", Axiom::Types::String],
|
|
27
|
+
"prop2" => ["This is prop2", Axiom::Types::Boolean],
|
|
28
|
+
"nested1.nested_prop1" => ["Nested prop1", Axiom::Types::String],
|
|
29
|
+
"nested1.nested_prop2" => ["Nested prop2", Axiom::Types::String],
|
|
30
|
+
"nested1.deep.deep1" => ["deep prop1", Axiom::Types::String],
|
|
31
|
+
"nested1.deep.deep2" => ["deep prop2", Axiom::Types::String],
|
|
32
|
+
"nested2.nested_prop1" => ["Nested prop1", Axiom::Types::String],
|
|
33
|
+
"nested2.nested_prop2" => ["Nested prop2", Axiom::Types::String],
|
|
34
|
+
"nested2.deep.deep1" => ["deep prop1", Axiom::Types::String],
|
|
35
|
+
"nested2.deep.deep2" => ["deep prop2", Axiom::Types::String]
|
|
36
|
+
})
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'threatinator/config/feed_search'
|
|
3
|
+
|
|
4
|
+
describe Threatinator::Config::FeedSearch do
|
|
5
|
+
describe "#path" do
|
|
6
|
+
context "when :path not specified" do
|
|
7
|
+
it "returns an empty array" do
|
|
8
|
+
x = described_class.new
|
|
9
|
+
expect(x.path).to eq([])
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
context "when :path is an array of strings" do
|
|
13
|
+
it "returns the array of strings" do
|
|
14
|
+
x = described_class.new(path: ['foo', 'bar'])
|
|
15
|
+
expect(x.path).to eq(['foo', 'bar'])
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#exclude_default" do
|
|
21
|
+
context "when :exclude_default is not specified" do
|
|
22
|
+
it "returns false" do
|
|
23
|
+
expect(described_class.new.exclude_default).to eq(false)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "when :exclude_default is set to true" do
|
|
28
|
+
it "returns true" do
|
|
29
|
+
x = described_class.new(exclude_default: true)
|
|
30
|
+
expect(x.exclude_default).to eq(true)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "when :exclude_default is set to false" do
|
|
35
|
+
it "returns false" do
|
|
36
|
+
x = described_class.new(exclude_default: false)
|
|
37
|
+
expect(x.exclude_default).to eq(false)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#search_path" do
|
|
43
|
+
context "when neither :path nor :exclude_default are supplied" do
|
|
44
|
+
it "returns an array containing the default search path" do
|
|
45
|
+
x = described_class.new
|
|
46
|
+
expect(x.search_path).to eq([described_class::DEFAULT_FEED_PATH])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "when :path is an array of paths" do
|
|
51
|
+
let(:paths) { ["foo", "bar"] }
|
|
52
|
+
let(:config) { described_class.new(path: paths) }
|
|
53
|
+
specify "the first search paths are those specified by :path" do
|
|
54
|
+
expect(config.search_path[0..1]).to eq(['foo', 'bar'])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "when :exclude_default is not specified" do
|
|
58
|
+
specify "the default search path is appended" do
|
|
59
|
+
expect(config.search_path[-1]).to eq(described_class::DEFAULT_FEED_PATH)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
context "when :exclude_default is false" do
|
|
63
|
+
let(:config) { described_class.new(path: paths, exclude_default: false) }
|
|
64
|
+
specify "the default search path is appended" do
|
|
65
|
+
expect(config.search_path[-1]).to eq(described_class::DEFAULT_FEED_PATH)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
context "when :exclude_default is true" do
|
|
69
|
+
let(:config) { described_class.new(path: paths, exclude_default: true) }
|
|
70
|
+
specify "the default search path is not appended" do
|
|
71
|
+
expect(config.search_path[-1]).not_to eq(described_class::DEFAULT_FEED_PATH)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'threatinator/decoders/gzip'
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'zlib'
|
|
5
|
+
|
|
6
|
+
describe Threatinator::Decoders::Gzip do
|
|
7
|
+
let(:encode_data_proc) {
|
|
8
|
+
lambda do |data|
|
|
9
|
+
sio = StringIO.new
|
|
10
|
+
sio.set_encoding("binary")
|
|
11
|
+
gz = Zlib::GzipWriter.new(sio)
|
|
12
|
+
gz.write(data)
|
|
13
|
+
gz.close
|
|
14
|
+
sio.string
|
|
15
|
+
end
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
it_should_behave_like "a decoder" do
|
|
19
|
+
let(:decoder_opts) { {} }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
let(:uncompressed_data) {
|
|
23
|
+
ret = "".encode("binary")
|
|
24
|
+
'!'.upto('~') { |a| '!'.upto('~') { |b| ret << "#{a}#{b}" } }
|
|
25
|
+
ret
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let(:compressed_data) {
|
|
29
|
+
encode_data_proc.call(uncompressed_data)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let(:decoder) { Threatinator::Decoders::Gzip.new }
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
context "normal operation" do
|
|
36
|
+
it "should decompress a Gzip compressed stream" do
|
|
37
|
+
encoded_io = StringIO.new(compressed_data)
|
|
38
|
+
expect(decoder.decode(encoded_io).read).to eq(uncompressed_data)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context "handling truncated data" do
|
|
43
|
+
let(:truncated_data) {
|
|
44
|
+
data = compressed_data
|
|
45
|
+
data_len = data.length / 2
|
|
46
|
+
data[0..(data_len - 1)]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
it_should_behave_like "a decoder encountering an error during decoding" do
|
|
50
|
+
let(:input_io) {StringIO.new(truncated_data)}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "handling a gzip stream with no footer" do
|
|
55
|
+
let(:missing_footer_data) {
|
|
56
|
+
compressed_data[0..-9] # knock off the footer, which is 8 bytes long
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
it_should_behave_like "a decoder encountering an error during decoding" do
|
|
60
|
+
let(:input_io) {StringIO.new(missing_footer_data)}
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context "handling a gzip with an invalid footer" do
|
|
65
|
+
let(:missing_footer_data) {
|
|
66
|
+
ret = compressed_data
|
|
67
|
+
ret[-9..-1] = "\0\0\0\0\0\0\0\0"
|
|
68
|
+
ret
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
it_should_behave_like "a decoder encountering an error during decoding" do
|
|
72
|
+
let(:input_io) { StringIO.new(missing_footer_data) }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'threatinator/event_builder'
|
|
3
|
+
|
|
4
|
+
describe Threatinator::EventBuilder do
|
|
5
|
+
let(:feed) { build(:feed, provider: "my_provider", name: "my_feed" ) }
|
|
6
|
+
let(:event_builder) { described_class.new(feed) }
|
|
7
|
+
describe "#create_event" do
|
|
8
|
+
it "should yield an event" do
|
|
9
|
+
expect { |b| event_builder.create_event(&b) }.to yield_with_args(kind_of(Threatinator::Event))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "should increment the count each time it has been called" do
|
|
13
|
+
expect(event_builder.count).to eq(0)
|
|
14
|
+
event_builder.create_event { |e| }
|
|
15
|
+
expect(event_builder.count).to eq(1)
|
|
16
|
+
10.times do
|
|
17
|
+
event_builder.create_event { |e| }
|
|
18
|
+
end
|
|
19
|
+
expect(event_builder.count).to eq(11)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "the yielded event" do
|
|
23
|
+
let(:event) { e = nil; event_builder.create_event { |x| e = x }; e }
|
|
24
|
+
specify "#feed_name is set to the feed's provider" do
|
|
25
|
+
expect(event.feed_name).to eq('my_feed')
|
|
26
|
+
end
|
|
27
|
+
specify "#feed_provider is set to the feed's provider" do
|
|
28
|
+
expect(event.feed_provider).to eq('my_provider')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'threatinator/event'
|
|
3
|
+
|
|
4
|
+
describe Threatinator::Event do
|
|
5
|
+
describe "#type" do
|
|
6
|
+
it "should default to nil"
|
|
7
|
+
it "should return the value if set"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe "#type=" do
|
|
11
|
+
Threatinator::Event::VALID_TYPES.each do |type|
|
|
12
|
+
it "should be possible to set to #{type.inspect}"
|
|
13
|
+
end
|
|
14
|
+
it "should raise an InvalidAttributeError if set to something other than a symbol"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "#ipv4s" do
|
|
18
|
+
it "should return an array"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "#fqdns" do
|
|
22
|
+
it "should return an array"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "#add_ipv4" do
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "#add_fqdn" do
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'threatinator/feed_builder'
|
|
3
|
+
require 'threatinator/record'
|
|
4
|
+
|
|
5
|
+
describe Threatinator::FeedBuilder do
|
|
6
|
+
let (:provider) { 'FakeSecureCo' }
|
|
7
|
+
let (:name) { 'MaliciousDataFeed' }
|
|
8
|
+
let(:builder) { described_class.new }
|
|
9
|
+
|
|
10
|
+
context "without having been configured" do
|
|
11
|
+
describe "#build" do
|
|
12
|
+
it "should raise an error" do
|
|
13
|
+
expect { builder.build() }.to raise_error { |error|
|
|
14
|
+
expect(error).to be_kind_of(Threatinator::Exceptions::InvalidAttributeError)
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
shared_examples_for "a filter builder" do
|
|
21
|
+
it "should be a Proc" do
|
|
22
|
+
expect(filter_builder).to be_a(::Proc)
|
|
23
|
+
end
|
|
24
|
+
it "should generate a filter when called" do
|
|
25
|
+
expect(filter_builder.call).to respond_to(:filter?)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
shared_examples_for "a decoder builder" do
|
|
30
|
+
it "should be a Proc" do
|
|
31
|
+
expect(decoder_builder).to be_a(::Proc)
|
|
32
|
+
end
|
|
33
|
+
it "should generate a kind of Threatinator::Decoder when called" do
|
|
34
|
+
expect(decoder_builder.call).to be_kind_of Threatinator::Decoder
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
shared_examples_for "an alias of #decode_gzip" do
|
|
39
|
+
let(:method_name) { :decode_gzip }
|
|
40
|
+
let(:builder) { build(:feed_builder, :buildable) }
|
|
41
|
+
it "should return the builder" do
|
|
42
|
+
expect(builder.send(method_name)).to eq(builder)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
specify "calling the method should add a decoder_builder proc that generates a Threatinator::Decoders::Gzip" do
|
|
46
|
+
builder.send(method_name)
|
|
47
|
+
feed = builder.build
|
|
48
|
+
expect(feed.decoder_builders.count).to eq(1)
|
|
49
|
+
decoder_builder = feed.decoder_builders.first
|
|
50
|
+
expect(decoder_builder).to be_a(::Proc)
|
|
51
|
+
expect(decoder_builder.call).to be_a(Threatinator::Decoders::Gzip)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
specify "multiple calls should add as many decoder_builders in the built feed" do
|
|
55
|
+
5.times do
|
|
56
|
+
builder.send(method_name)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
feed = builder.build
|
|
60
|
+
expect(feed.decoder_builders.count).to eq(5)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "#decode_gzip" do
|
|
65
|
+
it_should_behave_like "an alias of #decode_gzip" do
|
|
66
|
+
let(:method_name) { :decode_gzip }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "#extract_gzip" do
|
|
71
|
+
it_should_behave_like "an alias of #decode_gzip" do
|
|
72
|
+
let(:method_name) { :extract_gzip }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "#gunzip" do
|
|
77
|
+
it_should_behave_like "an alias of #decode_gzip" do
|
|
78
|
+
let(:method_name) { :gunzip}
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "#filter_whitespace" do
|
|
83
|
+
let(:builder) { build(:feed_builder, :buildable) }
|
|
84
|
+
|
|
85
|
+
it "should return the builder" do
|
|
86
|
+
expect(builder.filter_whitespace).to eq(builder)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context "the built feed" do
|
|
90
|
+
let(:feed) {
|
|
91
|
+
builder.filter_whitespace
|
|
92
|
+
builder.build
|
|
93
|
+
}
|
|
94
|
+
describe "#filter_builders" do
|
|
95
|
+
it "should have one item" do
|
|
96
|
+
expect(feed.filter_builders.length).to eq(1)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe "the first item" do
|
|
100
|
+
let(:filter_builder) { feed.filter_builders[0] }
|
|
101
|
+
it_should_behave_like "a filter builder"
|
|
102
|
+
|
|
103
|
+
it "should build Threatinator::Filters::Whitespace when called" do
|
|
104
|
+
expect(filter_builder.call).to be_kind_of(Threatinator::Filters::Whitespace)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe "#filter_comments" do
|
|
112
|
+
let(:builder) { build(:feed_builder, :buildable) }
|
|
113
|
+
|
|
114
|
+
it "should return the builder" do
|
|
115
|
+
expect(builder.filter_comments).to eq(builder)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
context "the built feed" do
|
|
119
|
+
let(:feed) {
|
|
120
|
+
builder.filter_comments
|
|
121
|
+
builder.build
|
|
122
|
+
}
|
|
123
|
+
describe "#filter_builders" do
|
|
124
|
+
it "should have one item" do
|
|
125
|
+
expect(feed.filter_builders.length).to eq(1)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
describe "the first item" do
|
|
129
|
+
let(:filter_builder) { feed.filter_builders[0] }
|
|
130
|
+
it_should_behave_like "a filter builder"
|
|
131
|
+
it "should build a Threatinator::Filters::Comments when called" do
|
|
132
|
+
expect(filter_builder.call).to be_kind_of(Threatinator::Filters::Comments)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe "#filter" do
|
|
140
|
+
let(:builder) { build(:feed_builder, :buildable) }
|
|
141
|
+
|
|
142
|
+
it "should return the builder" do
|
|
143
|
+
expect(builder.filter() { }).to eq(builder)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
context "the built feed, when one filter is specified" do
|
|
147
|
+
let(:feed) {
|
|
148
|
+
builder.filter do |line|
|
|
149
|
+
line == "FILTER1"
|
|
150
|
+
end
|
|
151
|
+
builder.build
|
|
152
|
+
}
|
|
153
|
+
describe "#filter_builders" do
|
|
154
|
+
it "should have one item" do
|
|
155
|
+
expect(feed.filter_builders.length).to eq(1)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
describe "the first item" do
|
|
159
|
+
let(:filter_builder) { feed.filter_builders[0] }
|
|
160
|
+
it_should_behave_like "a filter builder"
|
|
161
|
+
|
|
162
|
+
it "should build a Threatinator::Filters::Block when called" do
|
|
163
|
+
expect(filter_builder.call).to be_kind_of(Threatinator::Filters::Block)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
context "the built feed, when three filters are specified" do
|
|
170
|
+
let(:feed) {
|
|
171
|
+
builder.filter do |record|
|
|
172
|
+
record.data == "FILTER1"
|
|
173
|
+
end
|
|
174
|
+
builder.filter_comments
|
|
175
|
+
builder.filter_whitespace
|
|
176
|
+
builder.build
|
|
177
|
+
}
|
|
178
|
+
describe "#filter_builders" do
|
|
179
|
+
it "should have three items" do
|
|
180
|
+
expect(feed.filter_builders.length).to eq(3)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
describe "the first filter builder" do
|
|
184
|
+
let(:filter_builder) { feed.filter_builders[0] }
|
|
185
|
+
it_should_behave_like "a filter builder"
|
|
186
|
+
describe "when called" do
|
|
187
|
+
subject {filter_builder.call}
|
|
188
|
+
it { should be_kind_of(Threatinator::Filters::Block) }
|
|
189
|
+
it "should be the first filter we added" do
|
|
190
|
+
expect(subject.filter?(build(:record, data:"FILTER1"))).to eq(true)
|
|
191
|
+
expect(subject.filter?(build(:record, data:"#comment"))).to eq(false)
|
|
192
|
+
expect(subject.filter?(build(:record, data:" "))).to eq(false)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
describe "the second filter builder" do
|
|
199
|
+
let(:filter_builder) { feed.filter_builders[1] }
|
|
200
|
+
it_should_behave_like "a filter builder"
|
|
201
|
+
describe "when called" do
|
|
202
|
+
subject {filter_builder.call}
|
|
203
|
+
it { should be_kind_of(Threatinator::Filters::Comments) }
|
|
204
|
+
it "should be the first filter we added" do
|
|
205
|
+
expect(subject.filter?(build(:record, data:"FILTER1"))).to eq(false)
|
|
206
|
+
expect(subject.filter?(build(:record, data:"#comment"))).to eq(true)
|
|
207
|
+
expect(subject.filter?(build(:record, data:" "))).to eq(false)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
describe "the third filter builder" do
|
|
213
|
+
let(:filter_builder) { feed.filter_builders[2] }
|
|
214
|
+
it_should_behave_like "a filter builder"
|
|
215
|
+
describe "when called" do
|
|
216
|
+
let(:filter) { filter_builder.call }
|
|
217
|
+
subject { filter }
|
|
218
|
+
it { should be_kind_of(Threatinator::Filters::Whitespace) }
|
|
219
|
+
it "should be the first filter we added" do
|
|
220
|
+
expect(subject.filter?(build(:record, data:"FILTER1"))).to eq(false)
|
|
221
|
+
expect(subject.filter?(build(:record, data:"#comment"))).to eq(false)
|
|
222
|
+
expect(subject.filter?(build(:record, data:" "))).to eq(true)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
context "the built feed, when no filters are specified" do
|
|
231
|
+
let(:feed) {
|
|
232
|
+
builder.build
|
|
233
|
+
}
|
|
234
|
+
describe "#filter_builders" do
|
|
235
|
+
it "should have no filter builders" do
|
|
236
|
+
expect(feed.filter_builders.length).to eq(0)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
describe "#provider" do
|
|
243
|
+
let(:builder) { build(:feed_builder, :without_provider) }
|
|
244
|
+
|
|
245
|
+
it "should return the builder" do
|
|
246
|
+
expect(builder.provider("asdf")).to eq(builder)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
context "the built feed" do
|
|
250
|
+
let(:feed) {
|
|
251
|
+
builder.provider(provider)
|
|
252
|
+
builder.build
|
|
253
|
+
}
|
|
254
|
+
it "#provider should be correct" do
|
|
255
|
+
expect(feed.provider).to eq(provider)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
describe "#name" do
|
|
261
|
+
let(:builder) { build(:feed_builder, :without_name) }
|
|
262
|
+
|
|
263
|
+
it "should return the builder" do
|
|
264
|
+
expect(builder.name("asdf")).to eq(builder)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
context "the built feed" do
|
|
268
|
+
let(:feed) {
|
|
269
|
+
builder.name(name)
|
|
270
|
+
builder.build
|
|
271
|
+
}
|
|
272
|
+
it "#name should be correct" do
|
|
273
|
+
expect(feed.name).to eq(name)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
describe "#fetch_http" do
|
|
279
|
+
let(:url) { 'http://foo.com/bar' }
|
|
280
|
+
let(:builder) { build(:feed_builder, :without_fetcher) }
|
|
281
|
+
|
|
282
|
+
it "should return the builder" do
|
|
283
|
+
expect(builder.fetch_http('http://foo.bar/')).to eq(builder)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
context "the built feed" do
|
|
287
|
+
let(:feed) {
|
|
288
|
+
builder.fetch_http(url)
|
|
289
|
+
builder.build
|
|
290
|
+
}
|
|
291
|
+
describe "#fetcher_builder" do
|
|
292
|
+
let(:fetcher_builder) { feed.fetcher_builder }
|
|
293
|
+
it "should be a Proc" do
|
|
294
|
+
expect(fetcher_builder).to be_a(::Proc)
|
|
295
|
+
end
|
|
296
|
+
it "should return an instance of Threatinator::Fetchers::Http when called" do
|
|
297
|
+
expect(fetcher_builder.call).to be_a(Threatinator::Fetchers::Http)
|
|
298
|
+
end
|
|
299
|
+
it "should return a brand new instance of a Threatinator::Fetchers::Http with each call" do
|
|
300
|
+
expect(fetcher_builder.call).not_to be(fetcher_builder.call)
|
|
301
|
+
end
|
|
302
|
+
it "should return instances that are eql? to each other" do
|
|
303
|
+
expect(fetcher_builder.call).to eql(fetcher_builder.call)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
shared_examples_for "a parser builder" do
|
|
310
|
+
it "should be a Proc" do
|
|
311
|
+
expect(parser_builder).to be_a(::Proc)
|
|
312
|
+
end
|
|
313
|
+
it "should return a kind of Threatinator::Parser when called" do
|
|
314
|
+
expect(parser_builder.call).to be_a(Threatinator::Parser)
|
|
315
|
+
end
|
|
316
|
+
it "should return a brand new instances of a parser with each call" do
|
|
317
|
+
expect(parser_builder.call).not_to be(parser_builder.call)
|
|
318
|
+
end
|
|
319
|
+
it "should return instances that are eql? to each other" do
|
|
320
|
+
expect(parser_builder.call).to eql(parser_builder.call)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
describe "#parse_xml" do
|
|
325
|
+
let(:builder) { build(:feed_builder, :without_parser) }
|
|
326
|
+
|
|
327
|
+
it "should return the builder" do
|
|
328
|
+
expect(builder.parse_xml('/some/path') {}).to eq(builder)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
context "the built feed" do
|
|
332
|
+
let(:parser_block) { lambda { } }
|
|
333
|
+
let(:feed) {
|
|
334
|
+
builder.parse_xml('/some/path', &parser_block)
|
|
335
|
+
builder.build
|
|
336
|
+
}
|
|
337
|
+
describe "#parser_builder" do
|
|
338
|
+
let(:parser_builder) { feed.parser_builder}
|
|
339
|
+
it_should_behave_like "a parser builder"
|
|
340
|
+
it "should return an instance of Threatinator::Parsers::XML::Parser when called" do
|
|
341
|
+
expect(parser_builder.call).to be_a(Threatinator::Parsers::XML::Parser)
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
describe "#parse_json" do
|
|
348
|
+
let(:builder) { build(:feed_builder, :without_parser) }
|
|
349
|
+
|
|
350
|
+
it "should return the builder" do
|
|
351
|
+
expect(builder.parse_json() {}).to eq(builder)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
context "the built feed" do
|
|
355
|
+
let(:parser_block) { lambda { } }
|
|
356
|
+
let(:feed) {
|
|
357
|
+
builder.parse_json(&parser_block)
|
|
358
|
+
builder.build
|
|
359
|
+
}
|
|
360
|
+
describe "#parser_builder" do
|
|
361
|
+
let(:parser_builder) { feed.parser_builder}
|
|
362
|
+
it_should_behave_like "a parser builder"
|
|
363
|
+
it "should return an instance of Threatinator::Parsers::JSON::Parser when called" do
|
|
364
|
+
expect(parser_builder.call).to be_a(Threatinator::Parsers::JSON::Parser)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
describe "#parse_eachline" do
|
|
371
|
+
let(:builder) { build(:feed_builder, :without_parser) }
|
|
372
|
+
|
|
373
|
+
it "should return the builder" do
|
|
374
|
+
expect(builder.parse_eachline() {}).to eq(builder)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
context "the built feed" do
|
|
378
|
+
let(:parser_block) { lambda { } }
|
|
379
|
+
let(:feed) {
|
|
380
|
+
builder.parse_eachline(separator: "\n", &parser_block)
|
|
381
|
+
builder.build
|
|
382
|
+
}
|
|
383
|
+
describe "#parser_builder" do
|
|
384
|
+
let(:parser_builder) { feed.parser_builder}
|
|
385
|
+
it_should_behave_like "a parser builder"
|
|
386
|
+
it "should return an instance of Threatinator::Parsers::Getline::Parser when called" do
|
|
387
|
+
expect(parser_builder.call).to be_a(Threatinator::Parsers::Getline::Parser)
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
describe "#parse_csv" do
|
|
394
|
+
let(:builder) { build(:feed_builder, :without_parser) }
|
|
395
|
+
|
|
396
|
+
it "should return the builder" do
|
|
397
|
+
expect(builder.parse_csv() {}).to eq(builder)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
context "the built feed" do
|
|
401
|
+
let(:parser_block) { lambda { } }
|
|
402
|
+
let(:feed) {
|
|
403
|
+
builder.parse_csv({}, &parser_block)
|
|
404
|
+
builder.build
|
|
405
|
+
}
|
|
406
|
+
describe "#parser_builder" do
|
|
407
|
+
let(:parser_builder) { feed.parser_builder}
|
|
408
|
+
it_should_behave_like "a parser builder"
|
|
409
|
+
it "should return an instance of Threatinator::Parsers::CSV::Parser when called" do
|
|
410
|
+
expect(parser_builder.call).to be_a(Threatinator::Parsers::CSV::Parser)
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
shared_examples_for "a DSL loader" do
|
|
417
|
+
# Expects :feed_loader as a proc
|
|
418
|
+
let(:feed_string) {
|
|
419
|
+
'provider "provider1"
|
|
420
|
+
name "feed1"
|
|
421
|
+
fetch_http("https://foobar/feed1.data")
|
|
422
|
+
|
|
423
|
+
parse_eachline(:separator => "\n") do |builder, line|
|
|
424
|
+
end'
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
it "should return an instance of Threatinator::FeedBuilder" do
|
|
428
|
+
expect(feed_loader.call(feed_string)).to be_a(Threatinator::FeedBuilder)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
it "should return a builder after parsing" do
|
|
432
|
+
builder = feed_loader.call(feed_string)
|
|
433
|
+
expect(builder).to be_a(Threatinator::FeedBuilder)
|
|
434
|
+
feed = builder.build
|
|
435
|
+
expect(feed.provider).to eq("provider1")
|
|
436
|
+
expect(feed.name).to eq("feed1")
|
|
437
|
+
expect(feed.parser_builder.call).to eq(Threatinator::Parsers::Getline::Parser.new(separator: "\n"))
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
context "when str contains invalid syntax" do
|
|
441
|
+
let(:feed_string) { 'provider "my_provider"
|
|
442
|
+
name "my_provider"
|
|
443
|
+
foo = 123 456'}
|
|
444
|
+
|
|
445
|
+
it "should raise an error on a syntax error" do
|
|
446
|
+
expect {
|
|
447
|
+
feed_loader.call(feed_string)
|
|
448
|
+
}.to raise_error { |e|
|
|
449
|
+
expect(e).to be_a(SyntaxError)
|
|
450
|
+
}
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
describe "#build" do
|
|
455
|
+
it "should raise an InvalidAttributeError if the feed is empty" do
|
|
456
|
+
expect { feed_loader.call("").build }.to raise_error { |error|
|
|
457
|
+
expect(error).to be_kind_of(Threatinator::Exceptions::InvalidAttributeError)
|
|
458
|
+
}
|
|
459
|
+
end
|
|
460
|
+
it "should raise a InvalidAttributeError if the feed is missing a provider" do
|
|
461
|
+
feed_string = '
|
|
462
|
+
name "feed1"
|
|
463
|
+
fetch_http("https://foobar/feed1.data")
|
|
464
|
+
parse_eachline(:separator => "\n") {}'
|
|
465
|
+
expect do
|
|
466
|
+
feed_loader.call(feed_string).build
|
|
467
|
+
end.to raise_error { |e|
|
|
468
|
+
expect(e).to be_a(Threatinator::Exceptions::InvalidAttributeError)
|
|
469
|
+
expect(e.attribute).to eq(:provider)
|
|
470
|
+
}
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
it "should raise a InvalidAttributeError if the feed is missing a name" do
|
|
474
|
+
feed_string = '
|
|
475
|
+
provider "provider1"
|
|
476
|
+
fetch_http("https://foobar/feed1.data")
|
|
477
|
+
parse_eachline(:separator => "\n") {}'
|
|
478
|
+
expect do
|
|
479
|
+
feed_loader.call(feed_string).build
|
|
480
|
+
end.to raise_error { |e|
|
|
481
|
+
expect(e).to be_a(Threatinator::Exceptions::InvalidAttributeError)
|
|
482
|
+
expect(e.attribute).to eq(:name)
|
|
483
|
+
}
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
it "should raise a InvalidAttributeError if the feed is missing a fetcher statement" do
|
|
487
|
+
feed_string = '
|
|
488
|
+
provider "provider1"
|
|
489
|
+
name "feed1"
|
|
490
|
+
parse_eachline(:separator => "\n") {}'
|
|
491
|
+
expect do
|
|
492
|
+
feed_loader.call(feed_string).build
|
|
493
|
+
end.to raise_error { |e|
|
|
494
|
+
expect(e).to be_a(Threatinator::Exceptions::InvalidAttributeError)
|
|
495
|
+
expect(e.attribute).to eq(:fetcher_builder)
|
|
496
|
+
}
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
it "should raise a InvalidAttributeError if the feed is missing a parser statement" do
|
|
500
|
+
feed_string = '
|
|
501
|
+
provider "provider1"
|
|
502
|
+
name "feed1"
|
|
503
|
+
fetch_http("https://foobar/feed1.data")'
|
|
504
|
+
expect do
|
|
505
|
+
feed_loader.call(feed_string).build
|
|
506
|
+
end.to raise_error { |e|
|
|
507
|
+
expect(e).to be_a(Threatinator::Exceptions::InvalidAttributeError)
|
|
508
|
+
}
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
context "when configured to fetch a url and parse each line, the feed" do
|
|
512
|
+
let(:url) { "http://foo.com/bar" }
|
|
513
|
+
let(:feed_string) {
|
|
514
|
+
'provider "my_feed_provider"
|
|
515
|
+
name "my_feed_name"
|
|
516
|
+
|
|
517
|
+
fetch_http("http://foo.com/bar")
|
|
518
|
+
|
|
519
|
+
filter do |line|
|
|
520
|
+
line =~ /Bad stuff/
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
filter_whitespace
|
|
524
|
+
filter_comments
|
|
525
|
+
|
|
526
|
+
parse_eachline(separator: "\0") do |*args|
|
|
527
|
+
# parsing stuff
|
|
528
|
+
end'
|
|
529
|
+
}
|
|
530
|
+
let(:feed) { feed_loader.call(feed_string).build() }
|
|
531
|
+
|
|
532
|
+
it "#provider should be correct" do
|
|
533
|
+
expect(feed.provider).to eq("my_feed_provider")
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
it "#name should be correct" do
|
|
537
|
+
expect(feed.name).to eq("my_feed_name")
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
it "#parser_builder should generate the proper Threatinator::Parsers::Getline::Parser" do
|
|
541
|
+
expect(feed.parser_builder.call).to eq(Threatinator::Parsers::Getline::Parser.new(separator:"\0"))
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
describe "#filter_builders" do
|
|
545
|
+
subject { feed.filter_builders }
|
|
546
|
+
|
|
547
|
+
it "should have three filter builders" do
|
|
548
|
+
expect(subject.length).to eq(3)
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
describe "filter 1" do
|
|
552
|
+
subject {feed.filter_builders[0].call}
|
|
553
|
+
it { should be_a(Threatinator::Filters::Block) }
|
|
554
|
+
end
|
|
555
|
+
describe "filter 2" do
|
|
556
|
+
subject {feed.filter_builders[1].call}
|
|
557
|
+
it { should be_a(Threatinator::Filters::Whitespace) }
|
|
558
|
+
end
|
|
559
|
+
describe "filter 3" do
|
|
560
|
+
subject {feed.filter_builders[2].call }
|
|
561
|
+
it { should be_a(Threatinator::Filters::Comments) }
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
describe :from_string do
|
|
570
|
+
it_should_behave_like "a DSL loader" do
|
|
571
|
+
let(:feed_loader) { lambda { |arg| Threatinator::FeedBuilder.from_string(arg) } }
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
describe :from_file do
|
|
576
|
+
before :each do
|
|
577
|
+
@tempdir = Dir.mktmpdir
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
after :each do
|
|
581
|
+
FileUtils.remove_entry_secure @tempdir
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
it_should_behave_like "a DSL loader" do
|
|
585
|
+
let(:feed_loader) {
|
|
586
|
+
lambda do |arg|
|
|
587
|
+
filename = File.join(@tempdir, "file.feed")
|
|
588
|
+
File.open(filename, "w") do |fio|
|
|
589
|
+
fio.write(arg)
|
|
590
|
+
end
|
|
591
|
+
Threatinator::FeedBuilder.from_file(filename)
|
|
592
|
+
end
|
|
593
|
+
}
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
let(:feedfile) {FEED_FIXTURES.join("provider1", "feed1.feed").to_s}
|
|
597
|
+
let(:missing_file) {FEED_FIXTURES.join("provider1","non-existant.feed").to_s}
|
|
598
|
+
|
|
599
|
+
it "should return a builder after parsing the file" do
|
|
600
|
+
builder = Threatinator::FeedBuilder.from_file(feedfile)
|
|
601
|
+
expect(builder).to be_a(Threatinator::FeedBuilder)
|
|
602
|
+
feed = builder.build
|
|
603
|
+
expect(feed.provider).to eq("provider1")
|
|
604
|
+
expect(feed.name).to eq("feed1")
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
it "should raise Threatinator::Exceptions::FeedFileNotFoundError if the feed file cannot be found" do
|
|
608
|
+
expect {
|
|
609
|
+
Threatinator::FeedBuilder.from_file(missing_file)
|
|
610
|
+
}.to raise_error(Threatinator::Exceptions::FeedFileNotFoundError)
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
it "should call from_string(data, filename, lineno)" do
|
|
614
|
+
data = File.read(feedfile)
|
|
615
|
+
expect(Threatinator::FeedBuilder).to receive(:from_string).with(data, feedfile, 0)
|
|
616
|
+
Threatinator::FeedBuilder.from_file(feedfile)
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
describe :from_dsl do
|
|
622
|
+
it_should_behave_like "a DSL loader" do
|
|
623
|
+
let(:feed_loader) {
|
|
624
|
+
lambda do |arg|
|
|
625
|
+
Threatinator::FeedBuilder.from_dsl do
|
|
626
|
+
eval(arg)
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
}
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
|