← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~enriqueesanchz/launchpad:cve-cvss-fix into launchpad:master

 

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

Follow ups