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,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,14 @@
1
+ require 'cve_schema/cve/has_lang_value'
2
+
3
+ module CVESchema
4
+ class CVE
5
+ #
6
+ # Represents a configuration within the `"configuration"` JSON Array.
7
+ #
8
+ class Configuration
9
+
10
+ include HasLangValue
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'cve_schema/cve/has_lang_value'
2
+
3
+ module CVESchema
4
+ class CVE
5
+ #
6
+ # Represents a credit within the `"credit"` JSON Array.
7
+ #
8
+ class Credit
9
+
10
+ include HasLangValue
11
+
12
+ end
13
+ end
14
+ 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,14 @@
1
+ require 'cve_schema/cve/has_lang_value'
2
+
3
+ module CVESchema
4
+ class CVE
5
+ #
6
+ # Represents an exploit JSON object within the `"exploits"` JSON Array.
7
+ #
8
+ class Exploit
9
+
10
+ include HasLangValue
11
+
12
+ end
13
+ end
14
+ 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