diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index cd6809b9..b217583a 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -611,14 +611,17 @@ def get_from_vector(vector: str) -> 'VulnerabilityScoreSource': Always returns an instance of `VulnerabilityScoreSource`. `VulnerabilityScoreSource.OTHER` is returned if the scheme is not obvious or known to us. """ + if vector.startswith('CVSS:4.'): + return VulnerabilityScoreSource.CVSS_V4 if vector.startswith('CVSS:3.'): + if vector.startswith('CVSS:3.1'): + return VulnerabilityScoreSource.CVSS_V3_1 return VulnerabilityScoreSource.CVSS_V3 - elif vector.startswith('CVSS:2.'): + if vector.startswith('CVSS:2.'): return VulnerabilityScoreSource.CVSS_V2 - elif vector.startswith('OWASP'): + if vector.startswith('OWASP'): return VulnerabilityScoreSource.OWASP - else: - return VulnerabilityScoreSource.OTHER + return VulnerabilityScoreSource.OTHER def get_localised_vector(self, vector: str) -> str: """ @@ -630,15 +633,16 @@ def get_localised_vector(self, vector: str) -> str: Returns: The vector without any scheme prefix as a `str`. """ - if self == VulnerabilityScoreSource.CVSS_V3 and vector.startswith('CVSS:3.'): - return re.sub('^CVSS:3\\.\\d/?', '', vector) - - if self == VulnerabilityScoreSource.CVSS_V2 and vector.startswith('CVSS:2.'): - return re.sub('^CVSS:2\\.\\d/?', '', vector) - - if self == VulnerabilityScoreSource.OWASP and vector.startswith('OWASP'): - return re.sub('^OWASP/?', '', vector) - + if self is VulnerabilityScoreSource.CVSS_V4 and vector.startswith('CVSS:4.'): + return re.sub(r'^CVSS:4\.\d/?', '', vector) + if ( + self in (VulnerabilityScoreSource.CVSS_V3_1, VulnerabilityScoreSource.CVSS_V3) + ) and vector.startswith('CVSS:3.'): + return re.sub(r'^CVSS:3\.\d/?', '', vector) + if self is VulnerabilityScoreSource.CVSS_V2 and vector.startswith('CVSS:2.'): + return re.sub(r'^CVSS:2\.\d/?', '', vector) + if self is VulnerabilityScoreSource.OWASP and vector.startswith('OWASP'): + return re.sub(r'^OWASP/?', '', vector) return vector def get_value_pre_1_4(self) -> str: @@ -649,7 +653,7 @@ def get_value_pre_1_4(self) -> str: Returns: `str` """ - if self == VulnerabilityScoreSource.OWASP: + if self is VulnerabilityScoreSource.OWASP: return 'OWASP Risk' return self.value # type:ignore[no-any-return] diff --git a/tests/test_model_vulnerability.py b/tests/test_model_vulnerability.py index 74af5a25..6c90a8ad 100644 --- a/tests/test_model_vulnerability.py +++ b/tests/test_model_vulnerability.py @@ -41,7 +41,7 @@ from tests import reorder -class TestModelVulnerability(TestCase): +class TestModelVulnerabilitySeverity(TestCase): def test_v_severity_from_cvss_scores_single_critical(self) -> None: self.assertEqual( @@ -85,87 +85,165 @@ def test_v_severity_from_cvss_scores_multiple_high(self) -> None: VulnerabilitySeverity.HIGH ) + +class TestModelVulnerabilityScoreSource(TestCase): + + def test_v_source_parse_other(self) -> None: + self.assertEqual( + VulnerabilityScoreSource.get_from_vector('loremIpsum'), + VulnerabilityScoreSource.OTHER + ) + + def test_v_source_parse_cvss4_0(self) -> None: + self.assertEqual( + VulnerabilityScoreSource.get_from_vector( + 'CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:U'), + VulnerabilityScoreSource.CVSS_V4 + ) + def test_v_source_parse_cvss3_1(self) -> None: self.assertEqual( - VulnerabilityScoreSource.get_from_vector('CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + VulnerabilityScoreSource.get_from_vector( + 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H'), + VulnerabilityScoreSource.CVSS_V3_1 + ) + + def test_v_source_parse_cvss3_0(self) -> None: + self.assertEqual( + VulnerabilityScoreSource.get_from_vector( + 'CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), VulnerabilityScoreSource.CVSS_V3 ) - def test_v_source_parse_cvss2_1(self) -> None: + def test_v_source_parse_cvss2_0(self) -> None: self.assertEqual( - VulnerabilityScoreSource.get_from_vector('CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:N/A:C'), + VulnerabilityScoreSource.get_from_vector( + 'CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:N/A:C'), VulnerabilityScoreSource.CVSS_V2 ) def test_v_source_parse_owasp_1(self) -> None: self.assertEqual( - VulnerabilityScoreSource.get_from_vector('OWASP/K9:M1:O0:Z2/D1:X1:W1:L3/C2:I1:A1:T1/F1:R1:S2:P3/50'), + VulnerabilityScoreSource.get_from_vector( + 'OWASP/K9:M1:O0:Z2/D1:X1:W1:L3/C2:I1:A1:T1/F1:R1:S2:P3/50'), VulnerabilityScoreSource.OWASP ) - def test_v_source_get_localised_vector_cvss3_1(self) -> None: + def test_v_source_get_localised_vector_cvss4_slash(self) -> None: + self.assertEqual( + VulnerabilityScoreSource.CVSS_V4.get_localised_vector( + 'CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'), + 'AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N' + ) + + def test_v_source_get_localised_vector_cvss4_noslash(self) -> None: + self.assertEqual( + VulnerabilityScoreSource.CVSS_V4.get_localised_vector( + 'CVSS:4.0AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'), + 'AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N' + ) + + def test_v_source_get_localised_vector_cvss4_none(self) -> None: + self.assertEqual( + VulnerabilityScoreSource.CVSS_V4.get_localised_vector( + 'AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'), + 'AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N' + ) + + def test_v_source_get_localised_vector_cvss3_1_slash(self) -> None: + self.assertEqual( + VulnerabilityScoreSource.CVSS_V3.get_localised_vector( + 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H'), + 'AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H' + ) + + def test_v_source_get_localised_vector_cvss3_1_noslash(self) -> None: + self.assertEqual( + VulnerabilityScoreSource.CVSS_V3_1.get_localised_vector( + 'CVSS:3.0AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' + ) + + def test_v_source_get_localised_vector_cvss3_1_none(self) -> None: + self.assertEqual( + VulnerabilityScoreSource.CVSS_V3_1.get_localised_vector( + 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' + ) + + def test_v_source_get_localised_vector_cvss3_slash(self) -> None: self.assertEqual( VulnerabilityScoreSource.CVSS_V3.get_localised_vector( - vector='CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' - ), + 'CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' ) - def test_v_source_get_localised_vector_cvss3_2(self) -> None: + def test_v_source_get_localised_vector_cvss3_noslash(self) -> None: self.assertEqual( - VulnerabilityScoreSource.CVSS_V3.get_localised_vector(vector='CVSS:3.0AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + VulnerabilityScoreSource.CVSS_V3.get_localised_vector( + 'CVSS:3.0AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' ) - def test_v_source_get_localised_vector_cvss3_3(self) -> None: + def test_v_source_get_localised_vector_cvss3_none(self) -> None: self.assertEqual( - VulnerabilityScoreSource.CVSS_V3.get_localised_vector(vector='AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + VulnerabilityScoreSource.CVSS_V3.get_localised_vector( + 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' ) - def test_v_source_get_localised_vector_cvss2_1(self) -> None: + def test_v_source_get_localised_vector_cvss2_slash(self) -> None: self.assertEqual( VulnerabilityScoreSource.CVSS_V2.get_localised_vector( - vector='CVSS:2.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + 'CVSS:2.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' ) - def test_v_source_get_localised_vector_cvss2_2(self) -> None: + def test_v_source_get_localised_vector_cvss2_noslash(self) -> None: self.assertEqual( - VulnerabilityScoreSource.CVSS_V2.get_localised_vector(vector='CVSS:2.1AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + VulnerabilityScoreSource.CVSS_V2.get_localised_vector( + 'CVSS:2.0AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' ) - def test_v_source_get_localised_vector_cvss2_3(self) -> None: + def test_v_source_get_localised_vector_cvss2_none(self) -> None: self.assertEqual( - VulnerabilityScoreSource.CVSS_V2.get_localised_vector(vector='AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + VulnerabilityScoreSource.CVSS_V2.get_localised_vector( + 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' ) - def test_v_source_get_localised_vector_owasp_1(self) -> None: + def test_v_source_get_localised_vector_owasp_slash(self) -> None: self.assertEqual( - VulnerabilityScoreSource.OWASP.get_localised_vector(vector='OWASP/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + VulnerabilityScoreSource.OWASP.get_localised_vector( + 'OWASP/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' ) - def test_v_source_get_localised_vector_owasp_2(self) -> None: + def test_v_source_get_localised_vector_owasp_noslash(self) -> None: self.assertEqual( - VulnerabilityScoreSource.OWASP.get_localised_vector(vector='OWASPAV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + VulnerabilityScoreSource.OWASP.get_localised_vector( + 'OWASPAV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' ) - def test_v_source_get_localised_vector_owasp_3(self) -> None: + def test_v_source_get_localised_vector_owasp_none(self) -> None: self.assertEqual( - VulnerabilityScoreSource.OWASP.get_localised_vector(vector='AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), + VulnerabilityScoreSource.OWASP.get_localised_vector( + 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'), 'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N' ) - def test_v_source_get_localised_vector_other_2(self) -> None: + def test_v_source_get_localised_vector_other(self) -> None: self.assertEqual( - VulnerabilityScoreSource.OTHER.get_localised_vector(vector='SOMETHING_OR_OTHER'), + VulnerabilityScoreSource.OTHER.get_localised_vector( + 'SOMETHING_OR_OTHER'), 'SOMETHING_OR_OTHER' ) + +class TestModelVulnerability(TestCase): + def test_empty_vulnerability(self) -> None: v = Vulnerability() self.assertIsNone(v.bom_ref.value)