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.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.github/workflows/ruby.yml +28 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +26 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.md +50 -0
- data/Rakefile +23 -0
- data/benchmark.rb +47 -0
- data/cve_schema.gemspec +61 -0
- data/gemspec.yml +19 -0
- data/lib/cve_schema.rb +2 -0
- data/lib/cve_schema/cve.rb +257 -0
- data/lib/cve_schema/cve/affects.rb +55 -0
- data/lib/cve_schema/cve/configuration.rb +14 -0
- data/lib/cve_schema/cve/credit.rb +14 -0
- data/lib/cve_schema/cve/data_meta.rb +185 -0
- data/lib/cve_schema/cve/description.rb +24 -0
- data/lib/cve_schema/cve/exploit.rb +14 -0
- data/lib/cve_schema/cve/has_lang_value.rb +93 -0
- data/lib/cve_schema/cve/id.rb +79 -0
- data/lib/cve_schema/cve/impact.rb +75 -0
- data/lib/cve_schema/cve/impact/cvss_v2.rb +318 -0
- data/lib/cve_schema/cve/impact/cvss_v3.rb +388 -0
- data/lib/cve_schema/cve/na.rb +8 -0
- data/lib/cve_schema/cve/problem_type.rb +56 -0
- data/lib/cve_schema/cve/product.rb +79 -0
- data/lib/cve_schema/cve/reference.rb +82 -0
- data/lib/cve_schema/cve/solution.rb +14 -0
- data/lib/cve_schema/cve/source.rb +75 -0
- data/lib/cve_schema/cve/timeline.rb +65 -0
- data/lib/cve_schema/cve/timestamp.rb +25 -0
- data/lib/cve_schema/cve/vendor.rb +83 -0
- data/lib/cve_schema/cve/version.rb +126 -0
- data/lib/cve_schema/cve/work_around.rb +14 -0
- data/lib/cve_schema/exceptions.rb +20 -0
- data/lib/cve_schema/version.rb +6 -0
- data/spec/affects_spec.rb +28 -0
- data/spec/configuration_spec.rb +6 -0
- data/spec/credit_spec.rb +6 -0
- data/spec/cve_schema_spec.rb +8 -0
- data/spec/cve_spec.rb +414 -0
- data/spec/data_meta_spec.rb +167 -0
- data/spec/description.rb +24 -0
- data/spec/exploit_spec.rb +6 -0
- data/spec/fixtures/CVE-2020-1994.json +140 -0
- data/spec/fixtures/CVE-2020-2005.json +152 -0
- data/spec/fixtures/CVE-2020-2050.json +233 -0
- data/spec/fixtures/CVE-2020-4700.json +99 -0
- data/spec/has_lang_value_spec.rb +56 -0
- data/spec/id_spec.rb +91 -0
- data/spec/impact/cvss_v3_spec.rb +118 -0
- data/spec/impact_spec.rb +45 -0
- data/spec/na_spec.rb +14 -0
- data/spec/problem_type_spec.rb +26 -0
- data/spec/product_spec.rb +73 -0
- data/spec/reference_spec.rb +70 -0
- data/spec/shared_examples.rb +19 -0
- data/spec/solution_spec.rb +6 -0
- data/spec/source_spec.rb +84 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/timeline_spec.rb +86 -0
- data/spec/timestamp_spec.rb +24 -0
- data/spec/vendor_spec.rb +73 -0
- data/spec/version_spec.rb +104 -0
- data/spec/work_around_spec.rb +6 -0
- metadata +133 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'cve_schema/cve/vendor'
|
2
|
+
|
3
|
+
module CVESchema
|
4
|
+
class CVE
|
5
|
+
#
|
6
|
+
# Represents the `"affects"` JSON object.
|
7
|
+
#
|
8
|
+
class Affects
|
9
|
+
|
10
|
+
# @return [Array<Vendor>]
|
11
|
+
attr_reader :vendor
|
12
|
+
|
13
|
+
alias vendors vendor
|
14
|
+
|
15
|
+
#
|
16
|
+
# Initializes the affects container.
|
17
|
+
#
|
18
|
+
# @param [Array<Vendor>] vendor
|
19
|
+
#
|
20
|
+
def initialize(vendor)
|
21
|
+
@vendor = vendor
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Maps the parsed JSON to an Array of {Vendor} objects for {#initialize}.
|
26
|
+
#
|
27
|
+
# @param [Hash{String => Object}] json
|
28
|
+
# The parsed JSON.
|
29
|
+
#
|
30
|
+
# @return [Array<Vendor>]
|
31
|
+
#
|
32
|
+
# @api semipublic
|
33
|
+
#
|
34
|
+
def self.from_json(json)
|
35
|
+
json['vendor']['vendor_data'].map(&Vendor.method(:load))
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Loads the affects object from parsed JSON.
|
40
|
+
#
|
41
|
+
# @param [Hash{String => Object}] json
|
42
|
+
# The parsed JSON.
|
43
|
+
#
|
44
|
+
# @return [Affects]
|
45
|
+
# The loaded affects object.
|
46
|
+
#
|
47
|
+
# @api semipublic
|
48
|
+
#
|
49
|
+
def self.load(json)
|
50
|
+
new(from_json(json))
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cve_schema/exceptions'
|
4
|
+
require 'cve_schema/cve/id'
|
5
|
+
require 'cve_schema/cve/timestamp'
|
6
|
+
|
7
|
+
module CVESchema
|
8
|
+
class CVE
|
9
|
+
#
|
10
|
+
# Represents the `"CVE_data_meta"` JSON object.
|
11
|
+
#
|
12
|
+
class DataMeta
|
13
|
+
|
14
|
+
# The CVE ID.
|
15
|
+
#
|
16
|
+
# @return [ID]
|
17
|
+
attr_reader :id
|
18
|
+
|
19
|
+
# The assigner's email address.
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
attr_reader :assigner
|
23
|
+
|
24
|
+
# Date last updated.
|
25
|
+
#
|
26
|
+
# @return [DateTime, nil]
|
27
|
+
attr_reader :updated
|
28
|
+
|
29
|
+
# @return [Integer, nil]
|
30
|
+
attr_reader :serial
|
31
|
+
|
32
|
+
# Date requested.
|
33
|
+
#
|
34
|
+
# @return [DateTime, nil]
|
35
|
+
attr_reader :date_requested
|
36
|
+
|
37
|
+
# Date assigned.
|
38
|
+
#
|
39
|
+
# @return [DateTime, nil]
|
40
|
+
attr_reader :date_assigned
|
41
|
+
|
42
|
+
# Date published publically.
|
43
|
+
#
|
44
|
+
# @return [DateTime, nil]
|
45
|
+
attr_reader :date_public
|
46
|
+
|
47
|
+
# Requester email address.
|
48
|
+
#
|
49
|
+
# @return [String, nil]
|
50
|
+
attr_reader :requester
|
51
|
+
|
52
|
+
# List of IDs that replaced the CVE.
|
53
|
+
#
|
54
|
+
# @return [Array<ID>, nil]
|
55
|
+
attr_reader :replaced_by
|
56
|
+
|
57
|
+
STATES = {
|
58
|
+
'PUBLIC' => :PUBLIC,
|
59
|
+
'RESERVED' => :RESERVED,
|
60
|
+
'REPLACED_BY' => :REPLACED_BY,
|
61
|
+
'SPLIT_FROM' => :SPLIT_FROM,
|
62
|
+
'MERGED_TO' => :MERGED_TO,
|
63
|
+
'REJECT' => :REJECT
|
64
|
+
}
|
65
|
+
|
66
|
+
# @return [:PUBLIC, :RESERVED, :REPLACED_BY, :SPLIT_FROM, :MERGED_TO, nil]
|
67
|
+
attr_reader :state
|
68
|
+
|
69
|
+
# @return [String, nil]
|
70
|
+
attr_reader :title
|
71
|
+
|
72
|
+
#
|
73
|
+
# Initializes the data-meta object.
|
74
|
+
#
|
75
|
+
# @param [ID] id
|
76
|
+
#
|
77
|
+
# @param [String] assigner
|
78
|
+
#
|
79
|
+
# @param [DateTime, nil] updated
|
80
|
+
#
|
81
|
+
# @param [Integer, nil] serial
|
82
|
+
#
|
83
|
+
# @param [DateTime, nil] date_requested
|
84
|
+
#
|
85
|
+
# @param [DateTime, nil] date_assigned
|
86
|
+
#
|
87
|
+
# @param [DateTime, nil] date_public
|
88
|
+
#
|
89
|
+
# @param [String, nil] requester
|
90
|
+
#
|
91
|
+
# @param [Array<ID>, nil] replaced_by
|
92
|
+
#
|
93
|
+
# @param [:PUBLIC, :RESERVED, :REPLACED_BY, :SPLIT_FROM, :MERGED_TO, nil] state
|
94
|
+
#
|
95
|
+
# @param [String, nil] title
|
96
|
+
def initialize(id: , assigner: , updated: nil,
|
97
|
+
serial: nil,
|
98
|
+
date_requested: nil,
|
99
|
+
date_assigned: nil,
|
100
|
+
date_public: nil,
|
101
|
+
requester: nil,
|
102
|
+
replaced_by: nil,
|
103
|
+
state: nil,
|
104
|
+
title: nil)
|
105
|
+
@id = id
|
106
|
+
@assigner = assigner
|
107
|
+
|
108
|
+
@updated = updated
|
109
|
+
@serial = serial
|
110
|
+
@date_requested = date_requested
|
111
|
+
@date_assigned = date_assigned
|
112
|
+
@date_public = date_public
|
113
|
+
@requester = requester
|
114
|
+
@replaced_by = replaced_by
|
115
|
+
@state = state
|
116
|
+
@title = title
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Maps the parsed JSON to a Symbol Hash for {#initialize}.
|
121
|
+
#
|
122
|
+
# @param [Hash{String => Object}] json
|
123
|
+
# The parsed JSON.
|
124
|
+
#
|
125
|
+
# @return [Hash{Symbol => Object}]
|
126
|
+
# The Symbol Hash.
|
127
|
+
#
|
128
|
+
# @raise [MissingJSONKey]
|
129
|
+
# The `"ID"` or `"ASSIGNER"` JSON keys were missing.
|
130
|
+
#
|
131
|
+
# @raise [UnknownJSONValue]
|
132
|
+
# The `"STATE"` JSON value was unknown.
|
133
|
+
#
|
134
|
+
# @api semipublic
|
135
|
+
#
|
136
|
+
def self.from_json(json)
|
137
|
+
{
|
138
|
+
id: if (id = json['ID'])
|
139
|
+
ID.parse(id)
|
140
|
+
else
|
141
|
+
raise MissingJSONKey.new('ID')
|
142
|
+
end,
|
143
|
+
|
144
|
+
assigner: json['ASSIGNER'] || raise(MissingJSONKey.new('ASSIGNER')),
|
145
|
+
|
146
|
+
updated: json['UPDATED'] && Timestamp.parse(json['UPDATED']),
|
147
|
+
serial: json['SERIAL'],
|
148
|
+
date_requested: json['DATE_REQUESTED'] && Timestamp.parse(json['DATE_REQUESTED']),
|
149
|
+
date_assigned: json['DATE_ASSIGNED'] && Timestamp.parse(json['DATE_ASSIGNED']),
|
150
|
+
date_public: json['DATE_PUBLIC'] && Timestamp.parse(json['DATE_PUBLIC']),
|
151
|
+
requester: json['REQUESTER'],
|
152
|
+
replaced_by: json['REPLACED_BY'] && json['REPLACED_BY'].split(/,\s*/).map { |id| ID.parse(id) },
|
153
|
+
state: if json['STATE']
|
154
|
+
STATES.fetch(json['STATE']) do
|
155
|
+
raise UnknownJSONValue.new('STATE',json['STATE'])
|
156
|
+
end
|
157
|
+
end,
|
158
|
+
title: json['TITLE']
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Loads the data-meta object from the parsed JSON.
|
164
|
+
#
|
165
|
+
# @param [Hash{String => Object}] json
|
166
|
+
# The parsed JSON.
|
167
|
+
#
|
168
|
+
# @return [self]
|
169
|
+
# The loaded data-meta object.
|
170
|
+
#
|
171
|
+
# @raise [MissingJSONKey]
|
172
|
+
# The `"ID"` or `"ASSIGNER"` JSON keys were missing.
|
173
|
+
#
|
174
|
+
# @raise [UnknownJSONValue]
|
175
|
+
# The `"STATE"` JSON value was unknown.
|
176
|
+
#
|
177
|
+
# @api semipublic
|
178
|
+
#
|
179
|
+
def self.load(json)
|
180
|
+
new(**from_json(json))
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'cve_schema/cve/has_lang_value'
|
2
|
+
require 'cve_schema/cve/na'
|
3
|
+
|
4
|
+
module CVESchema
|
5
|
+
class CVE
|
6
|
+
#
|
7
|
+
# Represents a description JSON object.
|
8
|
+
#
|
9
|
+
class Description
|
10
|
+
|
11
|
+
include HasLangValue
|
12
|
+
|
13
|
+
#
|
14
|
+
# Determines if the {#value} is `n/a`.
|
15
|
+
#
|
16
|
+
# @return [Boolean]
|
17
|
+
#
|
18
|
+
def na?
|
19
|
+
@value == NA
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CVESchema
|
4
|
+
class CVE
|
5
|
+
#
|
6
|
+
# Mixins for JSON objects containing `"lang"` and `"value"` keys.
|
7
|
+
#
|
8
|
+
module HasLangValue
|
9
|
+
|
10
|
+
#
|
11
|
+
# Adds {ClassMethods} to the class.
|
12
|
+
#
|
13
|
+
# @param [Class] base
|
14
|
+
# The class including {HasLangValue}.
|
15
|
+
#
|
16
|
+
def self.included(base)
|
17
|
+
base.extend ClassMethods
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Class methods.
|
22
|
+
#
|
23
|
+
module ClassMethods
|
24
|
+
LANG = {
|
25
|
+
'eng' => :eng, # English
|
26
|
+
'es' => :es, # Spanish
|
27
|
+
}
|
28
|
+
|
29
|
+
#
|
30
|
+
# Maps the parsed JSON to a Symbol Hash for {#initialize}.
|
31
|
+
#
|
32
|
+
# @param [Hash{String => Object}] json
|
33
|
+
# The parsed JSON.
|
34
|
+
#
|
35
|
+
# @return [Hash{Symbol => Object}]
|
36
|
+
# The mapped Symbol Hash.
|
37
|
+
#
|
38
|
+
def from_json(json)
|
39
|
+
{
|
40
|
+
lang: LANG.fetch(json['lang'],json['lang']),
|
41
|
+
value: json['value']
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Loads the objects from the parsed JSON.
|
47
|
+
#
|
48
|
+
# @param [Hash{String => Object}] json
|
49
|
+
# The parsed JSON.
|
50
|
+
#
|
51
|
+
# @return [HasLangValue]
|
52
|
+
# The loaded object.
|
53
|
+
#
|
54
|
+
def load(json)
|
55
|
+
new(**from_json(json))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Language identifier for {#value}.
|
60
|
+
#
|
61
|
+
# @return [:en, :es, String]
|
62
|
+
attr_reader :lang
|
63
|
+
|
64
|
+
# Text value.
|
65
|
+
#
|
66
|
+
# @return [String]
|
67
|
+
attr_reader :value
|
68
|
+
|
69
|
+
#
|
70
|
+
# Initializes {#lang} and {#value}.
|
71
|
+
#
|
72
|
+
# @param [:en, :es, String] lang
|
73
|
+
#
|
74
|
+
# @param [String] value
|
75
|
+
#
|
76
|
+
def initialize(lang: , value: )
|
77
|
+
@lang = lang
|
78
|
+
@value = value
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Converts the object to a String.
|
83
|
+
#
|
84
|
+
# @return [String]
|
85
|
+
# Returns the {#value}.
|
86
|
+
#
|
87
|
+
def to_s
|
88
|
+
@value
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module CVESchema
|
2
|
+
class CVE
|
3
|
+
#
|
4
|
+
# Represents a CVE ID (ex: `CVE-2021-1234`).
|
5
|
+
#
|
6
|
+
class ID
|
7
|
+
|
8
|
+
# The year the CVE ID was assigned.
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
attr_reader :year
|
12
|
+
|
13
|
+
# The CVE number.
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
attr_reader :number
|
17
|
+
|
18
|
+
#
|
19
|
+
# Initializes the CVE ID.
|
20
|
+
#
|
21
|
+
# @param [String] year
|
22
|
+
# The year the CVE ID was assigned.
|
23
|
+
#
|
24
|
+
# @param [String] number
|
25
|
+
# The CVE number.
|
26
|
+
#
|
27
|
+
def initialize(year,number)
|
28
|
+
@year = year
|
29
|
+
@number = number
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Parses the CVE ID.
|
34
|
+
#
|
35
|
+
# @param [String] id
|
36
|
+
# The CVE ID string.
|
37
|
+
#
|
38
|
+
# @raise [ArgumentError]
|
39
|
+
# The given ID was not a valid CVE.
|
40
|
+
#
|
41
|
+
def self.parse(id)
|
42
|
+
cve, year, number = id.split('-',3)
|
43
|
+
|
44
|
+
unless cve == 'CVE'
|
45
|
+
raise(ArgumentError,"invalid CVE #{id.inspect}")
|
46
|
+
end
|
47
|
+
|
48
|
+
new(year,number)
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Compares the ID with another ID.
|
53
|
+
#
|
54
|
+
# @param [ID] other
|
55
|
+
# The other ID.
|
56
|
+
#
|
57
|
+
# @return [Boolean]
|
58
|
+
# Identicates whether the IDs match.
|
59
|
+
#
|
60
|
+
def ==(other)
|
61
|
+
self.class == other.class && (
|
62
|
+
@year == other.year &&
|
63
|
+
@number == other.number
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Converts the CVE ID back into a String.
|
69
|
+
#
|
70
|
+
# @return [String]
|
71
|
+
# The full CVE ID (ex: `CVE-2021-1234`).
|
72
|
+
#
|
73
|
+
def to_s
|
74
|
+
"CVE-#{@year}-#{@number}"
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|