Enrique Sánchez has proposed merging ~enriqueesanchz/launchpad:cve-cvss-fix into launchpad:master.
Commit message: Add CVSS formats and fix CVSS removing Support multiple CVSS formats from the same authority Change tests to fit CVSS vector_string is now a list Old version was not removing CVSS when removed from file, now we always create a new CVSS field Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~enriqueesanchz/launchpad/+git/launchpad/+merge/483925 -- Your team Launchpad code reviewers is requested to review the proposed merge of ~enriqueesanchz/launchpad:cve-cvss-fix into launchpad:master.
diff --git a/lib/lp/bugs/model/cve.py b/lib/lp/bugs/model/cve.py index dd19e0d..8e06697 100644 --- a/lib/lp/bugs/model/cve.py +++ b/lib/lp/bugs/model/cve.py @@ -7,6 +7,7 @@ __all__ = [ ] import operator +from collections import defaultdict from datetime import timezone from storm.databases.postgres import JSON @@ -164,11 +165,12 @@ class Cve(StormBase, BugLinkTargetMixin): {("cve", self.sequence): [("bug", str(bug.id))]} ) - def setCVSSVectorForAuthority(self, authority, vector_string): + def setCVSSVectorForAuthority(self, cvss): """See ICveReference.""" - if self._cvss is None: - self._cvss = {} - self._cvss[authority] = vector_string + self._cvss = defaultdict(list) + for c in cvss: + self._cvss[c.authority].append(c.vector_string) + self._cvss = dict(self._cvss) @implementer(ICveSet) diff --git a/lib/lp/bugs/scripts/tests/test_uct.py b/lib/lp/bugs/scripts/tests/test_uct.py index f029f28..8a9b1fc 100644 --- a/lib/lp/bugs/scripts/tests/test_uct.py +++ b/lib/lp/bugs/scripts/tests/test_uct.py @@ -1,6 +1,7 @@ # Copyright 2022 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). +from collections import defaultdict from datetime import datetime, timezone from pathlib import Path from typing import List @@ -821,8 +822,13 @@ class TestUCTImporterExporter(TestCaseWithFactory): self.assertEqual([bug], vulnerability.bugs) def checkLaunchpadCve(self, lp_cve: CveModel, cve: CVE): + cvss = defaultdict(list) + for c in cve.cvss: + cvss[c.authority].append(c.vector_string) + cvss = dict(cvss) + self.assertDictEqual( - {cvss.authority: cvss.vector_string for cvss in cve.cvss}, + cvss, lp_cve.cvss, ) self.assertEqual(cve.discovered_by, lp_cve.discovered_by) diff --git a/lib/lp/bugs/scripts/uct/uctexport.py b/lib/lp/bugs/scripts/uct/uctexport.py index 3745874..b3b2416 100644 --- a/lib/lp/bugs/scripts/uct/uctexport.py +++ b/lib/lp/bugs/scripts/uct/uctexport.py @@ -245,7 +245,8 @@ class UCTExporter: authority=authority, vector_string=vector_string, ) - for authority, vector_string in lp_cve.cvss.items() + for authority in lp_cve.cvss + for vector_string in lp_cve.cvss[authority] ], patch_urls=patch_urls, ) diff --git a/lib/lp/bugs/scripts/uct/uctimport.py b/lib/lp/bugs/scripts/uct/uctimport.py index 845b060..01fe79d 100644 --- a/lib/lp/bugs/scripts/uct/uctimport.py +++ b/lib/lp/bugs/scripts/uct/uctimport.py @@ -477,8 +477,5 @@ class UCTImporter: :param lp_cve: LP's `CVE` model to be updated :param cve: `CVE` with information from UCT """ - for cvss in cve.cvss: - lp_cve.setCVSSVectorForAuthority( - cvss.authority, cvss.vector_string - ) + lp_cve.setCVSSVectorForAuthority(cve.cvss) lp_cve.discovered_by = cve.discovered_by diff --git a/lib/lp/bugs/tests/test_cve.py b/lib/lp/bugs/tests/test_cve.py index 62c0de2..fdfdefb 100644 --- a/lib/lp/bugs/tests/test_cve.py +++ b/lib/lp/bugs/tests/test_cve.py @@ -12,6 +12,7 @@ from zope.security.proxy import removeSecurityProxy from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams from lp.bugs.interfaces.cve import CveStatus, ICveSet +from lp.bugs.scripts.uct.models import CVSS from lp.testing import ( TestCaseWithFactory, login_person, @@ -170,7 +171,7 @@ class TestCve(TestCaseWithFactory): def test_cveset_new_method_parameters(self): today = datetime.now(tz=timezone.utc) - cvss = {"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"} + cvss = {"nvd": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"]} cve = getUtility(ICveSet).new( sequence="2099-1234", description="A critical vulnerability", @@ -240,12 +241,18 @@ class TestCve(TestCaseWithFactory): self.assertEqual({}, unproxied_cve.cvss) cve.setCVSSVectorForAuthority( - authority="nvd", - vector_string="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + [ + CVSS( + authority="nvd", + vector_string=( + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + ), + ) + ] ) self.assertEqual( - {"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}, + {"nvd": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"]}, unproxied_cve.cvss, ) @@ -254,21 +261,27 @@ class TestCve(TestCaseWithFactory): sequence="2099-1234", description="A critical vulnerability", cvestate=CveStatus.CANDIDATE, - cvss={"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}, + cvss={"nvd": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"]}, ) unproxied_cve = removeSecurityProxy(cve) self.assertEqual( - {"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}, + {"nvd": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"]}, unproxied_cve.cvss, ) cve.setCVSSVectorForAuthority( - authority="nvd", - vector_string="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + [ + CVSS( + authority="nvd", + vector_string=( + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + ), + ) + ] ) self.assertEqual( - {"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"}, + {"nvd": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"]}, unproxied_cve.cvss, ) @@ -277,23 +290,76 @@ class TestCve(TestCaseWithFactory): sequence="2099-1234", description="A critical vulnerability", cvestate=CveStatus.CANDIDATE, - cvss={"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}, + cvss={"nvd": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"]}, ) unproxied_cve = removeSecurityProxy(cve) self.assertEqual( - {"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}, + {"nvd": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"]}, unproxied_cve.cvss, ) cve.setCVSSVectorForAuthority( - authority="nist", - vector_string="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + [ + CVSS( + authority="nist", + vector_string=( + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + ), + ), + CVSS( + authority="nvd", + vector_string=( + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + ), + ), + ] ) self.assertEqual( { - "nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "nist": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "nvd": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"], + "nist": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"], + }, + unproxied_cve.cvss, + ) + + def test_setCVSSVectorForAuthority_remove_one_when_initial_value_set(self): + cve = self.factory.makeCVE( + sequence="2099-1234", + description="A critical vulnerability", + cvestate=CveStatus.CANDIDATE, + cvss={ + "nvd": [ + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + ] + }, + ) + unproxied_cve = removeSecurityProxy(cve) + self.assertEqual( + { + "nvd": [ + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + ] + }, + unproxied_cve.cvss, + ) + + cve.setCVSSVectorForAuthority( + [ + CVSS( + authority="nvd", + vector_string=( + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + ), + ), + ] + ) + + self.assertEqual( + { + "nvd": ["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"], }, unproxied_cve.cvss, )
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp