nvd-json_feeds 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.github/workflows/ruby.yml +29 -0
  4. data/.gitignore +9 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/ChangeLog.md +25 -0
  8. data/Gemfile +13 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +136 -0
  11. data/Rakefile +31 -0
  12. data/gemspec.yml +22 -0
  13. data/lib/nvd/json_feeds.rb +25 -0
  14. data/lib/nvd/json_feeds/exceptions.rb +15 -0
  15. data/lib/nvd/json_feeds/feed.rb +50 -0
  16. data/lib/nvd/json_feeds/feed_file.rb +95 -0
  17. data/lib/nvd/json_feeds/feed_uri.rb +131 -0
  18. data/lib/nvd/json_feeds/gz_feed_file.rb +60 -0
  19. data/lib/nvd/json_feeds/gz_feed_uri.rb +25 -0
  20. data/lib/nvd/json_feeds/json_feed_file.rb +21 -0
  21. data/lib/nvd/json_feeds/meta.rb +122 -0
  22. data/lib/nvd/json_feeds/meta_feed_uri.rb +22 -0
  23. data/lib/nvd/json_feeds/schema/configurations.rb +61 -0
  24. data/lib/nvd/json_feeds/schema/configurations/node.rb +98 -0
  25. data/lib/nvd/json_feeds/schema/cpe/has_uri.rb +66 -0
  26. data/lib/nvd/json_feeds/schema/cpe/match.rb +117 -0
  27. data/lib/nvd/json_feeds/schema/cpe/name.rb +67 -0
  28. data/lib/nvd/json_feeds/schema/cve_feed.rb +142 -0
  29. data/lib/nvd/json_feeds/schema/cve_item.rb +94 -0
  30. data/lib/nvd/json_feeds/schema/cvss_v2.rb +298 -0
  31. data/lib/nvd/json_feeds/schema/cvss_v3.rb +332 -0
  32. data/lib/nvd/json_feeds/schema/has_data_version.rb +54 -0
  33. data/lib/nvd/json_feeds/schema/impact.rb +73 -0
  34. data/lib/nvd/json_feeds/schema/impact/base_metric_v2.rb +132 -0
  35. data/lib/nvd/json_feeds/schema/impact/base_metric_v3.rb +79 -0
  36. data/lib/nvd/json_feeds/schema/timestamp.rb +9 -0
  37. data/lib/nvd/json_feeds/version.rb +6 -0
  38. data/lib/nvd/json_feeds/zip_feed_file.rb +64 -0
  39. data/lib/nvd/json_feeds/zip_feed_uri.rb +25 -0
  40. data/nvd-json_feeds.gemspec +61 -0
  41. data/spec/feed_file_examples.rb +27 -0
  42. data/spec/feed_file_spec.rb +42 -0
  43. data/spec/feed_spec.rb +56 -0
  44. data/spec/feed_uri_spec.rb +81 -0
  45. data/spec/fixtures/gz_feed_file/nvdcve-1.1-recent.json.gz +0 -0
  46. data/spec/fixtures/nvdcve-1.1-recent.json +180 -0
  47. data/spec/fixtures/zip_feed_file/nvdcve-1.1-recent.json.zip +0 -0
  48. data/spec/gz_feed_file_spec.rb +66 -0
  49. data/spec/gz_feed_uri_spec.rb +35 -0
  50. data/spec/json_feed_file_spec.rb +18 -0
  51. data/spec/json_feeds_spec.rb +8 -0
  52. data/spec/meta_spec.rb +141 -0
  53. data/spec/schema/configurations/node_spec.rb +87 -0
  54. data/spec/schema/configurations_spec.rb +57 -0
  55. data/spec/schema/cpe/match_spec.rb +188 -0
  56. data/spec/schema/cpe/name_spec.rb +54 -0
  57. data/spec/schema/cve_feed_spec.rb +162 -0
  58. data/spec/schema/cve_item_spec.rb +116 -0
  59. data/spec/schema/impact/base_metric_v2_spec.rb +183 -0
  60. data/spec/schema/impact/base_metric_v3_spec.rb +80 -0
  61. data/spec/schema/impact_spec.rb +53 -0
  62. data/spec/schema/shared_examples.rb +136 -0
  63. data/spec/schema/timestamp_spec.rb +8 -0
  64. data/spec/spec_helper.rb +8 -0
  65. data/spec/zip_feed_file_spec.rb +66 -0
  66. data/spec/zip_feed_uri_spec.rb +35 -0
  67. metadata +156 -0
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'feed_file_examples'
3
+ require 'nvd/json_feeds/gz_feed_file'
4
+
5
+ require 'fileutils'
6
+ require 'shellwords'
7
+
8
+ describe NVD::JSONFeeds::GzFeedFile do
9
+ let(:fixtures_dir) { File.expand_path('../fixtures',__FILE__) }
10
+ let(:json_filename) { 'nvdcve-1.1-recent.json' }
11
+ let(:json_file) { File.join(fixtures_dir,json_filename) }
12
+
13
+ let(:gz_filename) { "#{json_filename}.gz" }
14
+ let(:dir) { File.join(fixtures_dir,'gz_feed_file') }
15
+ let(:path) { File.join(dir,gz_filename) }
16
+
17
+ subject { described_class.new(path) }
18
+
19
+ include_examples "FeedFile"
20
+
21
+ describe "#json_filename" do
22
+ it "must return the '.json' filename without the '.gz' extension" do
23
+ expect(subject.json_filename).to eq(json_filename)
24
+ end
25
+ end
26
+
27
+ describe "#read" do
28
+ it "must read the ungziped contents" do
29
+ expect(subject.read).to be == File.read(json_file)
30
+ end
31
+
32
+ context "when gunzip is not installed" do
33
+ before do
34
+ expect(subject).to receive(:`).and_raise(Errno::ENOENT)
35
+ end
36
+
37
+ it do
38
+ expect { subject.read }.to raise_error(ReadFailed)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "#extract" do
44
+ let(:extracted_json_file) { File.join(dir,json_filename) }
45
+
46
+ it "must gunzip the '.gz' file and return a JSONFeedfile" do
47
+ feed_file = subject.extract
48
+
49
+ expect(feed_file).to be_kind_of(JSONFeedFile)
50
+ expect(feed_file.path).to eq(extracted_json_file)
51
+ expect(File.file?(extracted_json_file)).to be(true)
52
+ end
53
+
54
+ context "when gunzip fails" do
55
+ before do
56
+ allow(subject).to receive(:system).and_return(false)
57
+ end
58
+
59
+ it do
60
+ expect { subject.extract }.to raise_error(ExtractFailed)
61
+ end
62
+ end
63
+
64
+ after { FileUtils.rm_f(extracted_json_file) }
65
+ end
66
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+ require 'nvd/json_feeds/gz_feed_uri'
3
+
4
+ require 'fileutils'
5
+
6
+ describe NVD::JSONFeeds::GzFeedURI do
7
+ let(:fixtures_dir) { File.expand_path('../fixtures',__FILE__) }
8
+
9
+ let(:name) { :recent }
10
+ let(:ext) { '.json.gz' }
11
+
12
+ subject { described_class.new(name,ext) }
13
+
14
+ describe "#download", :integration do
15
+ let(:download_dir) { File.join(fixtures_dir,'download') }
16
+ let(:dest) { File.join(download_dir,subject.filename) }
17
+
18
+ before do
19
+ FileUtils.mkdir_p(download_dir)
20
+ FileUtils.rm_f(dest)
21
+ end
22
+
23
+ it "must return a GzFeedFile object for the newly downloaded file" do
24
+ feed_file = subject.download(dest)
25
+
26
+ expect(feed_file).to be_kind_of(GzFeedFile)
27
+ expect(feed_file.path).to eq(dest)
28
+ expect(File.file?(dest)).to be(true)
29
+ end
30
+
31
+ after do
32
+ FileUtils.rm_f(dest)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'feed_file_examples'
3
+ require 'nvd/json_feeds/json_feed_file'
4
+
5
+ describe JSONFeedFile do
6
+ let(:fixtures_dir) { File.expand_path('../fixtures',__FILE__) }
7
+ let(:path) { File.join(fixtures_dir,'nvdcve-1.1-recent.json') }
8
+
9
+ subject { described_class.new(path) }
10
+
11
+ include_examples "FeedFile"
12
+
13
+ describe "#read" do
14
+ it "must read the contents of the json file" do
15
+ expect(subject.read).to eq(File.read(path))
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'nvd/json_feeds'
3
+
4
+ describe NVD::JSONFeeds do
5
+ it "should have a VERSION constant" do
6
+ expect(subject.const_get('VERSION')).to_not be_empty
7
+ end
8
+ end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+ require 'nvd/json_feeds/meta'
3
+
4
+ describe Meta do
5
+ let(:last_modified_date_string) { '2021-01-18T08:01:40-05:00' }
6
+ let(:last_modified_date) { DateTime.parse(last_modified_date_string) }
7
+ let(:size) { 3499526 }
8
+ let(:zip_size) {212317 }
9
+ let(:gz_size) { 212173 }
10
+ let(:sha256) { '9013088A0E882B4FBB590A25D8CC00431AE4CA465CB66EF9E2F7F231EE54BE6F' }
11
+
12
+ describe "#initialize" do
13
+ subject do
14
+ described_class.new(last_modified_date,size,zip_size,gz_size,sha256)
15
+ end
16
+
17
+ it "must set #last_modified_date" do
18
+ expect(subject.last_modified_date).to eq(last_modified_date)
19
+ end
20
+
21
+ it "must set #size" do
22
+ expect(subject.size).to eq(size)
23
+ end
24
+
25
+ it "must set #zip_size" do
26
+ expect(subject.zip_size).to eq(zip_size)
27
+ end
28
+
29
+ it "must set #gz_size" do
30
+ expect(subject.gz_size).to eq(gz_size)
31
+ end
32
+
33
+ it "must set #sha256" do
34
+ expect(subject.sha256).to eq(sha256)
35
+ end
36
+ end
37
+
38
+ describe ".parse" do
39
+ let(:string) do
40
+ [
41
+ "lastModifiedDate:#{last_modified_date_string}",
42
+ "size:#{size}",
43
+ "zipSize:#{zip_size}",
44
+ "gzSize:#{gz_size}",
45
+ "sha256:#{sha256}"
46
+ ].join("\r\n")
47
+ end
48
+
49
+ subject { described_class }
50
+
51
+ it "must return a #{described_class} object" do
52
+ expect(subject.parse(string)).to be_kind_of(described_class)
53
+ end
54
+
55
+ context "lastModifiedDate" do
56
+ subject { described_class.parse(string) }
57
+
58
+ it "must parse into a DatEtime" do
59
+ expect(subject.last_modified_date).to be_kind_of(DateTime)
60
+ end
61
+
62
+ it { expect(subject.last_modified_date).to eq(last_modified_date) }
63
+ end
64
+
65
+ context "size" do
66
+ subject { described_class.parse(string) }
67
+
68
+ it "must convert into an Integer" do
69
+ expect(subject.size).to be_kind_of(Integer)
70
+ end
71
+
72
+ it { expect(subject.size).to eq(size) }
73
+ end
74
+
75
+ context "zipSize" do
76
+ subject { described_class.parse(string) }
77
+
78
+ it "must convert into an Integer" do
79
+ expect(subject.zip_size).to be_kind_of(Integer)
80
+ end
81
+
82
+ it { expect(subject.zip_size).to eq(zip_size) }
83
+ end
84
+
85
+ context "gzSize" do
86
+ subject { described_class.parse(string) }
87
+
88
+ it "must convert into an Integer" do
89
+ expect(subject.gz_size).to be_kind_of(Integer)
90
+ end
91
+
92
+ it { expect(subject.gz_size).to eq(gz_size) }
93
+ end
94
+
95
+ context "sha256" do
96
+ subject { described_class.parse(string) }
97
+
98
+ it { expect(subject.sha256).to eq(sha256) }
99
+ end
100
+
101
+ context "when lastModifiedDate is missing" do
102
+ let(:string) { super().sub(/^lastModifiedDate:.*$/,'') }
103
+
104
+ it do
105
+ expect { subject.parse(string) }.to raise_error(MetaParseError)
106
+ end
107
+ end
108
+
109
+ context "when size is missing" do
110
+ let(:string) { super().sub(/^size:.*$/,'') }
111
+
112
+ it do
113
+ expect { subject.parse(string) }.to raise_error(MetaParseError)
114
+ end
115
+ end
116
+
117
+ context "when zipSize is missing" do
118
+ let(:string) { super().sub(/^zipSize:.*$/,'') }
119
+
120
+ it do
121
+ expect { subject.parse(string) }.to raise_error(MetaParseError)
122
+ end
123
+ end
124
+
125
+ context "when gzSize is missing" do
126
+ let(:string) { super().sub(/^gzSize:.*$/,'') }
127
+
128
+ it do
129
+ expect { subject.parse(string) }.to raise_error(MetaParseError)
130
+ end
131
+ end
132
+
133
+ context "when sha256 is missing" do
134
+ let(:string) { super().sub(/^sha256:.*$/,'') }
135
+
136
+ it do
137
+ expect { subject.parse(string) }.to raise_error(MetaParseError)
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+ require 'schema/shared_examples'
3
+ require 'nvd/json_feeds/schema/configurations/node'
4
+
5
+ describe Schema::Configurations::Node do
6
+ describe "#initialize" do
7
+ context "when operator: is given" do
8
+ let(:operator) { :AND }
9
+
10
+ subject { described_class.new(operator: operator) }
11
+
12
+ it "must set #operator" do
13
+ expect(subject.operator).to eq(operator)
14
+ end
15
+ end
16
+
17
+ context "when operator: is not given" do
18
+ it { expect(subject.operator).to be(nil) }
19
+ end
20
+
21
+ context "when negate: is given" do
22
+ let(:negate) { true }
23
+
24
+ subject { described_class.new(negate: negate) }
25
+
26
+ it "must set #negate" do
27
+ expect(subject.negate).to eq(negate)
28
+ end
29
+ end
30
+
31
+ context "when negate: is not given" do
32
+ it { expect(subject.negate).to be(nil) }
33
+ end
34
+
35
+ context "when children: is given" do
36
+ let(:children) { [double(:Node1), double(:Node2)] }
37
+
38
+ subject { described_class.new(children: children) }
39
+
40
+ it "must set #children" do
41
+ expect(subject.children).to eq(children)
42
+ end
43
+ end
44
+
45
+ context "when children: is not given" do
46
+ it { expect(subject.children).to eq([]) }
47
+ end
48
+
49
+ context "when cpe_match: is given" do
50
+ let(:cpe_match) { [double("CPE::Match1"), double("CPE::Match2")] }
51
+
52
+ subject { described_class.new(cpe_match: cpe_match) }
53
+
54
+ it "must set #cpe_match" do
55
+ expect(subject.cpe_match).to eq(cpe_match)
56
+ end
57
+ end
58
+
59
+ context "when cpe_match: is not given" do
60
+ it { expect(subject.cpe_match).to eq([]) }
61
+ end
62
+ end
63
+
64
+ describe ".load" do
65
+ include_examples ".load"
66
+
67
+ let(:json_node) { json_tree['CVE_Items'][0]['configurations']['nodes'][0] }
68
+
69
+ include_examples "JSON field", json_key: 'operator',
70
+ method: :operator,
71
+ map: described_class::OPERATORS
72
+
73
+ include_examples "JSON field", json_key: 'negate',
74
+ method: :negate
75
+
76
+ pending 'need an example containing \"children\"'do
77
+ include_examples "JSON Array field", json_key: 'children',
78
+ method: :children,
79
+ element_class: described_class
80
+ end
81
+
82
+ include_examples "JSON Array field", json_key: 'cpe_match',
83
+ method: :cpe_match,
84
+ element_class: Schema::CPE::Match
85
+
86
+ end
87
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'schema/shared_examples'
3
+ require 'nvd/json_feeds/schema/configurations'
4
+
5
+ describe Schema::Configurations do
6
+ describe "#initialize" do
7
+ let(:data_version) { :"4.0" }
8
+
9
+ context "when data_version: is given" do
10
+ subject { described_class.new(data_version: data_version) }
11
+
12
+ it "must set #data_version" do
13
+ expect(subject.data_version).to eq(data_version)
14
+ end
15
+
16
+ context "and when nodes: is given" do
17
+ let(:nodes) { [double(:Node1), double(:Node2)] }
18
+
19
+ subject do
20
+ described_class.new(data_version: data_version, nodes: nodes)
21
+ end
22
+
23
+ it "must set #nodes" do
24
+ expect(subject.nodes).to eq(nodes)
25
+ end
26
+ end
27
+
28
+ context "but when nodes: is not given" do
29
+ it { expect(subject.nodes).to eq([]) }
30
+ end
31
+ end
32
+
33
+ context "when data_version: is not given" do
34
+ it do
35
+ expect {
36
+ described_class.new
37
+ }.to raise_error(ArgumentError)
38
+ end
39
+ end
40
+ end
41
+
42
+ describe ".load" do
43
+ include_examples ".load"
44
+
45
+ let(:json_node) { json_tree['CVE_Items'][0]['configurations'] }
46
+
47
+ include_examples "JSON field", json_key: 'CVE_data_version',
48
+ required: true,
49
+ method: :data_version,
50
+ map: described_class::DATA_VERSIONS
51
+
52
+ include_examples "JSON Array field", json_key: 'nodes',
53
+ method: :nodes,
54
+ element_class: described_class::Node
55
+
56
+ end
57
+ end
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+ require 'schema/shared_examples'
3
+ require 'nvd/json_feeds/schema/cpe/match'
4
+
5
+ describe Schema::CPE::Match do
6
+ describe "#initialize" do
7
+ context "when cpe23uri: is given" do
8
+ let(:cpe23uri) { 'cpe:2.3:a:bitcoinsv:bitcoin_sv:*:*:*:*:*:*:*:*' }
9
+
10
+ context "and when vulnerable: is given" do
11
+ let(:vulnerable) { true }
12
+
13
+ subject do
14
+ described_class.new(
15
+ cpe23uri: cpe23uri,
16
+ vulnerable: vulnerable
17
+ )
18
+ end
19
+
20
+ it "must set #vulnerable" do
21
+ expect(subject.vulnerable).to eq(vulnerable)
22
+ end
23
+
24
+ context "and when version_start_excluding: is given" do
25
+ let(:version) { '1.2.3' }
26
+
27
+ subject do
28
+ described_class.new(
29
+ cpe23uri: cpe23uri,
30
+ vulnerable: vulnerable,
31
+ version_start_excluding: version
32
+ )
33
+ end
34
+
35
+ it "must set #version_start_excluding" do
36
+ expect(subject.version_start_excluding).to eq(version)
37
+ end
38
+ end
39
+
40
+ context "but when version_start_excluding: is not given" do
41
+ it { expect(subject.version_start_excluding).to be(nil) }
42
+ end
43
+
44
+ context "and when version_start_including: is given" do
45
+ let(:version) { '1.2.3' }
46
+
47
+ subject do
48
+ described_class.new(
49
+ cpe23uri: cpe23uri,
50
+ vulnerable: vulnerable,
51
+ version_start_including: version
52
+ )
53
+ end
54
+
55
+ it "must set #version_start_including" do
56
+ expect(subject.version_start_including).to eq(version)
57
+ end
58
+ end
59
+
60
+ context "but when version_start_including: is not given" do
61
+ it { expect(subject.version_start_including).to be(nil) }
62
+ end
63
+
64
+ context "and when version_end_excluding: is given" do
65
+ let(:version) { '1.2.3' }
66
+
67
+ subject do
68
+ described_class.new(
69
+ cpe23uri: cpe23uri,
70
+ vulnerable: vulnerable,
71
+ version_end_excluding: version
72
+ )
73
+ end
74
+
75
+ it "must set #version_end_excluding" do
76
+ expect(subject.version_end_excluding).to eq(version)
77
+ end
78
+ end
79
+
80
+ context "but when version_end_excluding: is not given" do
81
+ it { expect(subject.version_end_excluding).to be(nil) }
82
+ end
83
+
84
+ context "and when version_end_including: is given" do
85
+ let(:version) { '1.2.3' }
86
+
87
+ subject do
88
+ described_class.new(
89
+ cpe23uri: cpe23uri,
90
+ vulnerable: vulnerable,
91
+ version_end_including: version
92
+ )
93
+ end
94
+
95
+ it "must set #version_end_including" do
96
+ expect(subject.version_end_including).to eq(version)
97
+ end
98
+ end
99
+
100
+ context "but when version_end_including: is not given" do
101
+ it { expect(subject.version_end_including).to be(nil) }
102
+ end
103
+
104
+ context "and when cpe_name: is given" do
105
+ let(:cpe_name) { [double("CPE::Name1"), double("CPE::Name2")] }
106
+
107
+ subject do
108
+ described_class.new(
109
+ cpe23uri: cpe23uri,
110
+ vulnerable: vulnerable,
111
+ cpe_name: cpe_name
112
+ )
113
+ end
114
+
115
+ it "must set #cpe_name" do
116
+ expect(subject.cpe_name).to eq(cpe_name)
117
+ end
118
+ end
119
+
120
+ context "but when cpe_name: is not given" do
121
+ it { expect(subject.cpe_name).to eq([]) }
122
+ end
123
+ end
124
+
125
+ context "but when vulnerable: is not given" do
126
+ it do
127
+ expect {
128
+ described_class.new
129
+ }.to raise_error(ArgumentError)
130
+ end
131
+ end
132
+ end
133
+
134
+ context "but when cpe23uri: is not given" do
135
+ it do
136
+ expect {
137
+ described_class.new
138
+ }.to raise_error(ArgumentError)
139
+ end
140
+ end
141
+ end
142
+
143
+ describe ".load" do
144
+ include_examples ".load"
145
+
146
+ let(:json_node) do
147
+ json_tree['CVE_Items'][0]['configurations']['nodes'][0]['cpe_match'][0]
148
+ end
149
+
150
+ include_examples "JSON field", json_key: 'vulnerable',
151
+ required: true,
152
+ method: :vulnerable
153
+
154
+ include_examples "JSON field", json_key: 'cpe23Uri',
155
+ required: true,
156
+ method: :cpe23uri
157
+
158
+ pending 'need to find an example containing the "cpe22Uri" key' do
159
+ include_examples "JSON field", json_key: 'cpe22Uri',
160
+ required: true,
161
+ method: :cpe22uri
162
+ end
163
+
164
+ pending 'need to find an example containing the "versionStartExcluding" key' do
165
+ include_examples "JSON field", json_key: 'versionStartExcluding',
166
+ method: :version_start_excluding
167
+ end
168
+
169
+ pending 'need to find an example containing the "versionStartIncluding" key' do
170
+ include_examples "JSON field", json_key: 'versionStartIncluding',
171
+ method: :version_start_including
172
+ end
173
+
174
+ include_examples "JSON field", json_key: 'versionEndExcluding',
175
+ method: :version_end_excluding
176
+
177
+ pending 'need to find an example containing the "versionEndIncluding" key' do
178
+ include_examples "JSON field", json_key: 'versionEndIncluding',
179
+ method: :version_end_including
180
+ end
181
+
182
+ pending 'need to find an example containing the "cpe_name" key' do
183
+ include_examples "JSON Array field", json_key: 'cpe_name',
184
+ method: :cpe_name,
185
+ element_class: Schema::CPE::Name
186
+ end
187
+ end
188
+ end