cve_schema 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.github/workflows/ruby.yml +28 -0
  4. data/.gitignore +6 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/ChangeLog.md +26 -0
  8. data/Gemfile +14 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +50 -0
  11. data/Rakefile +23 -0
  12. data/benchmark.rb +47 -0
  13. data/cve_schema.gemspec +61 -0
  14. data/gemspec.yml +19 -0
  15. data/lib/cve_schema.rb +2 -0
  16. data/lib/cve_schema/cve.rb +257 -0
  17. data/lib/cve_schema/cve/affects.rb +55 -0
  18. data/lib/cve_schema/cve/configuration.rb +14 -0
  19. data/lib/cve_schema/cve/credit.rb +14 -0
  20. data/lib/cve_schema/cve/data_meta.rb +185 -0
  21. data/lib/cve_schema/cve/description.rb +24 -0
  22. data/lib/cve_schema/cve/exploit.rb +14 -0
  23. data/lib/cve_schema/cve/has_lang_value.rb +93 -0
  24. data/lib/cve_schema/cve/id.rb +79 -0
  25. data/lib/cve_schema/cve/impact.rb +75 -0
  26. data/lib/cve_schema/cve/impact/cvss_v2.rb +318 -0
  27. data/lib/cve_schema/cve/impact/cvss_v3.rb +388 -0
  28. data/lib/cve_schema/cve/na.rb +8 -0
  29. data/lib/cve_schema/cve/problem_type.rb +56 -0
  30. data/lib/cve_schema/cve/product.rb +79 -0
  31. data/lib/cve_schema/cve/reference.rb +82 -0
  32. data/lib/cve_schema/cve/solution.rb +14 -0
  33. data/lib/cve_schema/cve/source.rb +75 -0
  34. data/lib/cve_schema/cve/timeline.rb +65 -0
  35. data/lib/cve_schema/cve/timestamp.rb +25 -0
  36. data/lib/cve_schema/cve/vendor.rb +83 -0
  37. data/lib/cve_schema/cve/version.rb +126 -0
  38. data/lib/cve_schema/cve/work_around.rb +14 -0
  39. data/lib/cve_schema/exceptions.rb +20 -0
  40. data/lib/cve_schema/version.rb +6 -0
  41. data/spec/affects_spec.rb +28 -0
  42. data/spec/configuration_spec.rb +6 -0
  43. data/spec/credit_spec.rb +6 -0
  44. data/spec/cve_schema_spec.rb +8 -0
  45. data/spec/cve_spec.rb +414 -0
  46. data/spec/data_meta_spec.rb +167 -0
  47. data/spec/description.rb +24 -0
  48. data/spec/exploit_spec.rb +6 -0
  49. data/spec/fixtures/CVE-2020-1994.json +140 -0
  50. data/spec/fixtures/CVE-2020-2005.json +152 -0
  51. data/spec/fixtures/CVE-2020-2050.json +233 -0
  52. data/spec/fixtures/CVE-2020-4700.json +99 -0
  53. data/spec/has_lang_value_spec.rb +56 -0
  54. data/spec/id_spec.rb +91 -0
  55. data/spec/impact/cvss_v3_spec.rb +118 -0
  56. data/spec/impact_spec.rb +45 -0
  57. data/spec/na_spec.rb +14 -0
  58. data/spec/problem_type_spec.rb +26 -0
  59. data/spec/product_spec.rb +73 -0
  60. data/spec/reference_spec.rb +70 -0
  61. data/spec/shared_examples.rb +19 -0
  62. data/spec/solution_spec.rb +6 -0
  63. data/spec/source_spec.rb +84 -0
  64. data/spec/spec_helper.rb +4 -0
  65. data/spec/timeline_spec.rb +86 -0
  66. data/spec/timestamp_spec.rb +24 -0
  67. data/spec/vendor_spec.rb +73 -0
  68. data/spec/version_spec.rb +104 -0
  69. data/spec/work_around_spec.rb +6 -0
  70. metadata +133 -0
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cve_schema/exceptions'
4
+ require 'cve_schema/cve/na'
5
+
6
+ module CVESchema
7
+ class CVE
8
+ #
9
+ # Represents an element within the `"version_data"` JSON Array.
10
+ #
11
+ class Version
12
+
13
+ # @return [String]
14
+ attr_reader :version_value
15
+
16
+ VERSION_AFFECTED = {
17
+ '=' => :"=", # affects version_value
18
+ '<' => :"<", # affects versions prior to version_value
19
+ '>' => :">", # affects versions later than version_value
20
+ '<=' => :"<=", # affects version_value and prior versions
21
+ '>=' => :">=", # affects version_value and later versions
22
+ '!' => :"!", # doesn't affect version_value
23
+ '!<' => :"!<", # doesn't affect versions prior to version_value
24
+ '!>' => :"!>", # doesn't affect versions later than version_value
25
+ '!<=' => :"!<=",# doesn't affect version_value and prior versions
26
+ '!>=' => :"!>=",# doesn't affect version_value and later versions
27
+ '?' => :"?", # status of version_value is unknown
28
+ '?<' => :"?<", # status of versions prior to version_value is unknown
29
+ '?>' => :"?>", # status of versions later than version_value is unknown
30
+ '?<=' => :"?<=",# status of version_value and prior versions is unknown
31
+ '?>=' => :"?>=",# status of version_value and later versions is unknown
32
+ }
33
+
34
+ # @return [nil, :'=', :'<', :'>', :'<=', , :'>=', :'!', :'!<', :'!>', :'!<=', :'!>=', :'?', :'?<', :'?>', :'?<=', :'?>=']
35
+ attr_reader :version_affected
36
+
37
+ # @return [nil, String]
38
+ attr_reader :version_name
39
+
40
+ #
41
+ # Initializes the version.
42
+ #
43
+ # @param [String] version_value
44
+ #
45
+ # @param [String, nil] version_name
46
+ #
47
+ # @param [nil, :'=', :'<', :'>', :'<=', , :'>=', :'!', :'!<', :'!>', :'!<=', :'!>=', :'?', :'?<', :'?>', :'?<=', :'?>='] version_affected
48
+ # The version comparison operator. See {VERSION_AFFECTED}.
49
+ #
50
+ def initialize(version_value: , version_name: nil, version_affected: nil)
51
+ @version_value = version_value
52
+ @version_name = version_name
53
+ @version_affected = version_affected
54
+ end
55
+
56
+ #
57
+ # Maps the parsed JSON to a Symbol Hash for {#initialize}.
58
+ #
59
+ # @param [Hash{String => String}] json
60
+ # The parsed JSON.
61
+ #
62
+ # @return [Hash{Symbol => Object}]
63
+ # The mapped Symbol Hash.
64
+ #
65
+ # @raise [UnknownJSONValue]
66
+ # The `"version_affected"` JSON value was unknown.
67
+ #
68
+ # @api semipublic
69
+ #
70
+ def self.from_json(json)
71
+ {
72
+ version_affected: if (version_affected = json['version_affected'])
73
+ VERSION_AFFECTED.fetch(version_affected) do
74
+ raise UnknownJSONValue.new('version_affected',version_affected)
75
+ end
76
+ end,
77
+
78
+ version_name: json['version_name'],
79
+ version_value: json['version_value']
80
+ }
81
+ end
82
+
83
+ #
84
+ # Loads the version object from parsed JSON.
85
+ #
86
+ # @param [Hash{String => String}] json
87
+ # The parsed JSON.
88
+ #
89
+ # @return [Version]
90
+ # The loaded version object.
91
+ #
92
+ # @raise [UnknownJSONValue]
93
+ # The `"version_affected"` JSON value was unknown.
94
+ #
95
+ # @api semipublic
96
+ #
97
+ def self.load(json)
98
+ new(**from_json(json))
99
+ end
100
+
101
+ #
102
+ # Determines if the {#version_value} is `n/a`.
103
+ #
104
+ # @return [Boolean]
105
+ #
106
+ def na?
107
+ @version_value == NA
108
+ end
109
+
110
+ #
111
+ # Converts the version into a String.
112
+ #
113
+ # @return [String]
114
+ # The {#version_value} and additionally the {#version_affected}.
115
+ #
116
+ def to_s
117
+ if @version_affected
118
+ "#{@version_affected} #{@version_value}"
119
+ else
120
+ @version_value
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,14 @@
1
+ require 'cve_schema/cve/has_lang_value'
2
+
3
+ module CVESchema
4
+ class CVE
5
+ #
6
+ # Represents an entry within the `"work_around"` JSON Array.
7
+ #
8
+ class WorkAround
9
+
10
+ include HasLangValue
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ module CVESchema
2
+ class InvalidJSON < StandardError
3
+ end
4
+
5
+ class MissingJSONKey < InvalidJSON
6
+
7
+ def initialize(key)
8
+ super("missing #{key.inspect} key")
9
+ end
10
+
11
+ end
12
+
13
+ class UnknownJSONValue < InvalidJSON
14
+
15
+ def initialize(key,value)
16
+ super("unknown #{key.inspect} value: #{value.inspect}")
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CVESchema
4
+ # cve_schema version
5
+ VERSION = "0.1.0"
6
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ require 'shared_examples'
3
+ require 'cve_schema/cve/affects'
4
+
5
+ describe CVESchema::CVE::Affects do
6
+ describe "#initialize" do
7
+ let(:vendor) { double(:vendor) }
8
+
9
+ subject { described_class.new(vendor) }
10
+
11
+ it "must set #vendor" do
12
+ expect(subject.vendor).to be(vendor)
13
+ end
14
+ end
15
+
16
+ describe ".load" do
17
+ include_examples ".load"
18
+
19
+ let(:json_node) { json_tree['affects'] }
20
+
21
+ context '"vendor":' do
22
+ context '"vendor_data":' do
23
+ it { expect(subject.vendor).to_not be_empty }
24
+ it { expect(subject.vendor).to all(be_kind_of(CVESchema::CVE::Vendor)) }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+ require 'cve_schema/cve/configuration'
3
+
4
+ describe CVESchema::CVE::Configuration do
5
+ it { expect(described_class).to include(CVESchema::CVE::HasLangValue) }
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+ require 'cve_schema/cve/credit'
3
+
4
+ describe CVESchema::CVE::Credit do
5
+ it { expect(described_class).to include(CVESchema::CVE::HasLangValue) }
6
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'cve_schema'
3
+
4
+ describe CVESchema 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,414 @@
1
+ require 'spec_helper'
2
+ require 'shared_examples'
3
+ require 'cve_schema/cve'
4
+
5
+ describe CVESchema::CVE do
6
+ describe "#initialize" do
7
+ let(:data_type) { :CVE }
8
+ let(:data_format) { :MITRE }
9
+ let(:data_version) { :"4.0" }
10
+ let(:data_meta) { double(:DataMeta) }
11
+
12
+ context "required keywords" do
13
+ context "when the data_type: keyword is not given" do
14
+ it do
15
+ expect {
16
+ described_class.new(
17
+ data_format: data_format,
18
+ data_version: data_version,
19
+ data_meta: data_meta
20
+ )
21
+ }.to raise_error(ArgumentError)
22
+ end
23
+ end
24
+
25
+ context "when the data_format: keyword is not given" do
26
+ it do
27
+ expect {
28
+ described_class.new(
29
+ data_type: data_type,
30
+ data_version: data_version,
31
+ data_meta: data_meta
32
+ )
33
+ }.to raise_error(ArgumentError)
34
+ end
35
+ end
36
+
37
+ context "when the data_version: keyword is not given" do
38
+ it do
39
+ expect {
40
+ described_class.new(
41
+ data_type: data_type,
42
+ data_format: data_format,
43
+ data_meta: data_meta
44
+ )
45
+ }.to raise_error(ArgumentError)
46
+ end
47
+ end
48
+
49
+ context "when the data_meta: keyword is not given" do
50
+ it do
51
+ expect {
52
+ described_class.new(
53
+ data_type: data_type,
54
+ data_format: data_format,
55
+ data_version: data_version,
56
+ )
57
+ }.to raise_error(ArgumentError)
58
+ end
59
+ end
60
+ end
61
+
62
+ context "default values" do
63
+ subject do
64
+ described_class.new(
65
+ data_type: data_type,
66
+ data_format: data_format,
67
+ data_version: data_version,
68
+ data_meta: data_meta
69
+ )
70
+ end
71
+
72
+ it { expect(subject.affects).to eq(nil) }
73
+ it { expect(subject.configurations).to eq([]) }
74
+ it { expect(subject.problemtype).to eq([]) }
75
+ it { expect(subject.references).to eq([]) }
76
+ it { expect(subject.description).to eq([]) }
77
+ it { expect(subject.exploit).to eq([]) }
78
+ it { expect(subject.credit).to eq([]) }
79
+ it { expect(subject.impact).to eq(nil) }
80
+ it { expect(subject.solution).to eq([]) }
81
+ it { expect(subject.source).to eq(nil) }
82
+ it { expect(subject.work_around).to eq([]) }
83
+ end
84
+ end
85
+
86
+ describe ".load" do
87
+ include_examples ".load"
88
+
89
+ it "must return a new CVE object" do
90
+ expect(subject).to be_kind_of(described_class)
91
+ end
92
+
93
+ context '"data_type":' do
94
+ let(:json_value) { json_node['data_type'] }
95
+ let(:expected) { described_class::DATA_TYPES[json_value] }
96
+
97
+ it "must convert and set #data_type" do
98
+ expect(subject.data_type).to eq(expected)
99
+ end
100
+
101
+ context 'whne "data_type" key is missing' do
102
+ before { json_node.delete('data_type') }
103
+
104
+ it do
105
+ expect {
106
+ described_class.load(json_node)
107
+ }.to raise_error(described_class::MissingJSONKey)
108
+ end
109
+ end
110
+ end
111
+
112
+ context '"data_format":' do
113
+ let(:json_value) { json_node['data_format'] }
114
+ let(:expected) { described_class::DATA_FORMAT[json_value] }
115
+
116
+ it "must convert and set #data_format" do
117
+ expect(subject.data_format).to eq(expected)
118
+ end
119
+
120
+ context 'whne "data_format" key is missing' do
121
+ before { json_node.delete('data_format') }
122
+
123
+ it do
124
+ expect {
125
+ described_class.load(json_node)
126
+ }.to raise_error(described_class::MissingJSONKey)
127
+ end
128
+ end
129
+ end
130
+
131
+ context '"data_version":' do
132
+ let(:json_value) { json_node['data_version'] }
133
+ let(:expected) { described_class::DATA_VERSIONS[json_value] }
134
+
135
+ it "must convert and set #data_version" do
136
+ expect(subject.data_version).to eq(expected)
137
+ end
138
+
139
+ context 'whne "data_version" key is missing' do
140
+ before { json_node.delete('data_version') }
141
+
142
+ it do
143
+ expect {
144
+ described_class.load(json_node)
145
+ }.to raise_error(described_class::MissingJSONKey)
146
+ end
147
+ end
148
+ end
149
+
150
+ context '"data_meta":' do
151
+ let(:json_value) { json_node['CVE_data_meta'] }
152
+
153
+ it "must convert the JSON Hash into a DataMeta objects and set #data_meta" do
154
+ expect(subject.data_meta).to be_kind_of(described_class::DataMeta)
155
+ end
156
+
157
+ context 'when "CVE_data_meta" key is missing' do
158
+ before { json_node.delete('CVE_data_meta') }
159
+
160
+ it do
161
+ expect {
162
+ described_class.load(json_node)
163
+ }.to raise_error(described_class::MissingJSONKey)
164
+ end
165
+ end
166
+ end
167
+
168
+ context '"affects":' do
169
+ context "when present" do
170
+ describe "#affects" do
171
+ it do
172
+ expect(subject.affects).to be_kind_of(described_class::Affects)
173
+ end
174
+ end
175
+ end
176
+
177
+ context "when missing" do
178
+ before { json_node.delete('affects') }
179
+
180
+ describe "#affects" do
181
+ it { expect(subject.affects).to be_nil }
182
+ end
183
+ end
184
+ end
185
+
186
+ context '"configuration":' do
187
+ let(:cve_id) { 'CVE-2020-2005' }
188
+
189
+ context "when present" do
190
+ describe "#configuration" do
191
+ it { expect(subject.configuration).to be_kind_of(Array) }
192
+ it { expect(subject.configuration).to_not be_empty }
193
+ it do
194
+ expect(subject.configuration).to all(be_kind_of(described_class::Configuration))
195
+ end
196
+ end
197
+ end
198
+
199
+ context "when missing" do
200
+ before { json_node.delete('configuration') }
201
+
202
+ describe "#configuration" do
203
+ it { expect(subject.configuration).to eq([]) }
204
+ end
205
+ end
206
+ end
207
+
208
+ context '"problemtype":' do
209
+ context "when present" do
210
+ describe "#problemtype" do
211
+ it { expect(subject.problemtype).to be_kind_of(Array) }
212
+ it { expect(subject.problemtype).to_not be_empty }
213
+
214
+ it do
215
+ expect(subject.problemtype).to all(be_kind_of(described_class::ProblemType))
216
+ end
217
+ end
218
+ end
219
+
220
+ context "when missing" do
221
+ before { json_node.delete('problemtype') }
222
+
223
+ describe "#problemtype" do
224
+ it { expect(subject.problemtype).to eq([]) }
225
+ end
226
+ end
227
+ end
228
+
229
+ context '"references":' do
230
+ context "when present" do
231
+ describe "#references" do
232
+ it { expect(subject.references).to be_kind_of(Array) }
233
+ it { expect(subject.references).to_not be_empty }
234
+
235
+ it do
236
+ expect(subject.references).to all(be_kind_of(described_class::Reference))
237
+ end
238
+ end
239
+ end
240
+
241
+ context "when missing" do
242
+ before { json_node.delete('references') }
243
+
244
+ describe "#references" do
245
+ it { expect(subject.references).to eq([]) }
246
+ end
247
+ end
248
+ end
249
+
250
+ context '"description":' do
251
+ context "when present" do
252
+ describe "#description" do
253
+ it { expect(subject.description).to be_kind_of(Array) }
254
+ it { expect(subject.description).to_not be_empty }
255
+
256
+ it do
257
+ expect(subject.description).to all(be_kind_of(described_class::Description))
258
+ end
259
+ end
260
+ end
261
+
262
+ context "when missing" do
263
+ before { json_node.delete('description') }
264
+
265
+ describe "#description" do
266
+ it { expect(subject.description).to eq([]) }
267
+ end
268
+ end
269
+ end
270
+
271
+ context '"exploit":' do
272
+ let(:cve_id) { 'CVE-2020-2050' }
273
+
274
+ context "when present" do
275
+ describe "#exploit" do
276
+ it { expect(subject.exploit).to be_kind_of(Array) }
277
+ it { expect(subject.exploit).to_not be_empty }
278
+
279
+ it do
280
+ expect(subject.exploit).to all(be_kind_of(described_class::Exploit))
281
+ end
282
+ end
283
+ end
284
+
285
+ context "when missing" do
286
+ before { json_node.delete('exploit') }
287
+
288
+ describe "#exploit" do
289
+ it { expect(subject.exploit).to eq([]) }
290
+ end
291
+ end
292
+ end
293
+
294
+ context '"credit":' do
295
+ context "when present" do
296
+ describe "#credit" do
297
+ it { expect(subject.credit).to be_kind_of(Array) }
298
+ it { expect(subject.credit).to_not be_empty }
299
+
300
+ it do
301
+ expect(subject.credit).to all(be_kind_of(described_class::Credit))
302
+ end
303
+ end
304
+ end
305
+
306
+ context "when missing" do
307
+ before { json_node.delete('credit') }
308
+
309
+ describe "#credit" do
310
+ it { expect(subject.credit).to eq([]) }
311
+ end
312
+ end
313
+ end
314
+
315
+ context '"impact":' do
316
+ context "when present" do
317
+ describe "#impact" do
318
+ it { expect(subject.impact).to be_kind_of(described_class::Impact) }
319
+ end
320
+ end
321
+
322
+ context "when missing" do
323
+ before { json_node.delete('impact') }
324
+
325
+ describe "#impact" do
326
+ it { expect(subject.impact).to be_nil }
327
+ end
328
+ end
329
+ end
330
+
331
+ context '"solution":' do
332
+ context "when present" do
333
+ describe "#solution" do
334
+ it { expect(subject.solution).to be_kind_of(Array) }
335
+ it { expect(subject.solution).to_not be_empty }
336
+
337
+ it do
338
+ expect(subject.solution).to all(be_kind_of(described_class::Solution))
339
+ end
340
+ end
341
+ end
342
+
343
+ context "when missing" do
344
+ before { json_node.delete('solution') }
345
+
346
+ describe "#solution" do
347
+ it { expect(subject.solution).to eq([]) }
348
+ end
349
+ end
350
+ end
351
+
352
+ context '"source":' do
353
+ context "when present" do
354
+ describe "#source" do
355
+ it { expect(subject.source).to be_kind_of(described_class::Source) }
356
+ end
357
+ end
358
+
359
+ context "when missing" do
360
+ before { json_node.delete('source') }
361
+
362
+ describe "#source" do
363
+ it { expect(subject.source).to be_nil }
364
+ end
365
+ end
366
+ end
367
+
368
+ context '"work_around":' do
369
+ let(:cve_id) { 'CVE-2020-2005' }
370
+
371
+ context "when present" do
372
+ describe "#work_around" do
373
+ it { expect(subject.work_around).to be_kind_of(Array) }
374
+ it { expect(subject.work_around).to_not be_empty }
375
+
376
+ it do
377
+ expect(subject.work_around).to all(be_kind_of(described_class::WorkAround))
378
+ end
379
+ end
380
+ end
381
+
382
+ context "when missing" do
383
+ before { json_node.delete('work_around') }
384
+
385
+ describe "#work_around" do
386
+ it { expect(subject.work_around).to eq([]) }
387
+ end
388
+ end
389
+ end
390
+
391
+ context '"timeline":' do
392
+ let(:cve_id) { 'CVE-2020-2005' }
393
+
394
+ context "when present" do
395
+ describe "#timeline" do
396
+ it { expect(subject.timeline).to be_kind_of(Array) }
397
+ it { expect(subject.timeline).to_not be_empty }
398
+
399
+ it do
400
+ expect(subject.timeline).to all(be_kind_of(described_class::Timeline))
401
+ end
402
+ end
403
+ end
404
+
405
+ context "when missing" do
406
+ before { json_node.delete('timeline') }
407
+
408
+ describe "#timeline" do
409
+ it { expect(subject.timeline).to eq([]) }
410
+ end
411
+ end
412
+ end
413
+ end
414
+ end