cve_schema 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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