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,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