Skip to content

Commit

Permalink
Replicate CVSS4.from_rh_vector (#68)
Browse files Browse the repository at this point in the history
* replicate cvss4.from_rh_vector

* add test cases for cvss4.from_rh_vector
  • Loading branch information
rhyw authored Feb 11, 2025
1 parent 9a4c2b0 commit 99d0193
Show file tree
Hide file tree
Showing 4 changed files with 1,161 additions and 2 deletions.
47 changes: 46 additions & 1 deletion cvss/cvss4.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@
METRICS_VALUE_NAMES,
OrderedDict,
)
from .exceptions import CVSS4MalformedError, CVSS4MandatoryError
from .exceptions import (
CVSS4MalformedError,
CVSS4MandatoryError,
CVSS4RHMalformedError,
CVSS4RHScoreDoesNotMatch,
)


def final_rounding(x):
Expand Down Expand Up @@ -91,6 +96,46 @@ def __init__(self, vector):
self.compute_base_score()
self.compute_severity()

@classmethod
def from_rh_vector(cls, vector):
"""
Creates a CVSS4 object from CVSS vector in Red Hat notation, e.g. containing base score.
Also checks if the score matches the vector.
Args:
vector (str): string specifying CVSS4 vector in Red Hat notation, fields may be out of
order, fields which are not mandatory may be missing
Returns:
CVSS4: the generated CVSS4 object created from the vector string
Raises:
CVSS4RHMalformedError: if vector is not in expected format for Red Hat notation
CVSS4RHScoreDoesNotMatch: if vector and score do not match
"""
try:
score, base_vector = vector.split("/", 1)
except ValueError:
raise CVSS4RHMalformedError(
'Malformed CVSS4 vector in Red Hat notation "{0}"'.format(vector)
)
try:
score_value = float(score)
except ValueError:
raise CVSS4RHMalformedError(
'Malformed CVSS4 vector in Red Hat notation "{0}"'.format(vector)
)
cvss_object = cls(base_vector)
if cvss_object.scores()[0] == score_value:
return cvss_object
else:
raise CVSS4RHScoreDoesNotMatch(
'CVSS4 vector in Red Hat notation "{0}" has score of '
'"{1}" which does not match specified score of "{2}"'.format(
base_vector, cvss_object.scores()[0], score
)
)

def check_mandatory(self):
"""
Checks if mandatory fields are in CVSS4 vector.
Expand Down
17 changes: 17 additions & 0 deletions cvss/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,20 @@ class CVSS4MandatoryError(CVSS4Error):
"""

pass


class CVSS4RHScoreDoesNotMatch(CVSS4Error):
"""
Exception when parsing CVSS4 vectors in Red Hat notation, which have score not matching the
computed score.
"""

pass


class CVSS4RHMalformedError(CVSS4Error):
"""
Exception for malformed input CVSS4 vectors in Red Hat notation.
"""

pass
41 changes: 40 additions & 1 deletion tests/test_cvss4.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import ast
import sys
import unittest
from os import path

sys.path.insert(0, path.dirname(path.dirname(path.abspath(__file__))))

from cvss.cvss4 import CVSS4
from cvss.exceptions import CVSS4MalformedError
from cvss.exceptions import (
CVSS4MalformedError,
CVSS4RHMalformedError,
CVSS4RHScoreDoesNotMatch,
)

WD = path.dirname(path.abspath(sys.argv[0])) # Manage to run script anywhere in the path

Expand All @@ -25,6 +30,16 @@ def run_tests_from_file(self, test_name):
expected_score, results_json_score, test_name + " - " + vector + " - JSON"
)

def run_rh_tests_from_file(self, test_name):
with open(path.join(WD, test_name)) as f:
for line in f:
vector, expected_scores = line.strip().split(" - ")
expected_scores = ast.literal_eval(expected_scores)
tested_rh_vector = str(expected_scores[0]) + "/" + vector
result = CVSS4.from_rh_vector(tested_rh_vector)
results_scores = result.scores()
self.assertEqual(expected_scores, results_scores, test_name + " - " + vector)

def test_base(self):
"""
All vector combinations with only mandatory fields, 104,976 bn vectors.
Expand Down Expand Up @@ -209,6 +224,30 @@ def test_duplicate_metric_key(self):
error = str(e)
self.assertEqual(error, 'Duplicate metric "SI"')

def test_rh_vector(self):
"""
Test for parsing Red Hat style of CVSS vectors, e.g. containing score.
"""
self.run_rh_tests_from_file("vectors_simple4")

# Bad values
v = "8.3/CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:N/SC:L/SI:L/SA:N"
self.assertRaises(CVSS4RHScoreDoesNotMatch, CVSS4.from_rh_vector, v)

v = "7.0/CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:N/SI:L/SA:N"
self.assertRaises(CVSS4RHScoreDoesNotMatch, CVSS4.from_rh_vector, v)

# Vector cannot be split to score/vector
v = ""
self.assertRaises(CVSS4RHMalformedError, CVSS4.from_rh_vector, v)

v = "8.3|AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:N/SC:H/SI:N/SA:L"
self.assertRaises(CVSS4RHMalformedError, CVSS4.from_rh_vector, v)

# Score is not float
v = "ABC|AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:N/SC:H/SI:N/SA:L"
self.assertRaises(CVSS4RHMalformedError, CVSS4.from_rh_vector, v)


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 99d0193

Please sign in to comment.