← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~lgp171188/launchpad:vulnerability-links-in-cve-page-if-present into launchpad:master

 

Guruprasad has proposed merging ~lgp171188/launchpad:vulnerability-links-in-cve-page-if-present into launchpad:master.

Commit message:
Link to the linked vulnerabilities on the CVE page

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~lgp171188/launchpad/+git/launchpad/+merge/430866
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~lgp171188/launchpad:vulnerability-links-in-cve-page-if-present into launchpad:master.
diff --git a/lib/lp/bugs/browser/tests/test_vulnerability.py b/lib/lp/bugs/browser/tests/test_vulnerability.py
index c0711c5..78564f6 100644
--- a/lib/lp/bugs/browser/tests/test_vulnerability.py
+++ b/lib/lp/bugs/browser/tests/test_vulnerability.py
@@ -530,3 +530,230 @@ class TestVulnerabilityListingPage(BrowserTestCase):
             )
         )
         self.assertBatches(distribution, link_matchers, True, 0, 5)
+
+
+class TestVulnerabilitiesLinksOnCVEPage(BrowserTestCase):
+    """Test for the vulnerabilities links on the CVE page."""
+
+    layer = DatabaseFunctionalLayer
+
+    def test_cve_page_when_no_linked_vulnerabilities(self):
+        cve = self.factory.makeCVE(sequence="2022-1234")
+        browser = self.getUserBrowser(
+            canonical_url(cve),
+            user=self.factory.makePerson(),
+        )
+        self.assertThat(
+            browser.contents,
+            HTMLContains(
+                Tag(
+                    "Linked vulnerabilities heading",
+                    "h2",
+                    text="Linked vulnerabilities",
+                ),
+                Tag(
+                    "Linked vulnerabilities details",
+                    "p",
+                    text="No linked vulnerabilities",
+                ),
+            ),
+        )
+
+    def test_cve_page_when_linked_vulnerabilities_present(self):
+        cve = self.factory.makeCVE(sequence="2022-1234")
+        distribution1 = self.factory.makeDistribution()
+        distribution2 = self.factory.makeDistribution()
+        vulnerability1 = self.factory.makeVulnerability(
+            cve=cve,
+            distribution=distribution1,
+        )
+        vulnerability2 = self.factory.makeVulnerability(
+            cve=cve,
+            distribution=distribution2,
+        )
+        browser = self.getUserBrowser(
+            canonical_url(cve), user=self.factory.makePerson()
+        )
+        login(ANONYMOUS)
+        self.assertThat(
+            browser.contents,
+            HTMLContains(
+                Tag(
+                    "Linked vulnerabilities heading",
+                    "h2",
+                    text="Linked vulnerabilities",
+                ),
+                Tag(
+                    "vulnerability1",
+                    "a",
+                    attrs={
+                        "href": canonical_url(
+                            vulnerability1, force_local_path=True
+                        )
+                    },
+                    text="{} vulnerability".format(distribution1.displayname),
+                ),
+                Tag(
+                    "vulnerability2",
+                    "a",
+                    attrs={
+                        "href": canonical_url(
+                            vulnerability2, force_local_path=True
+                        )
+                    },
+                    text="{} vulnerability".format(distribution2.displayname),
+                ),
+            ),
+        )
+
+    def test_query_count_constant_vs_number_of_linked_vulnerabilities(self):
+        cve = self.factory.makeCVE(sequence="2022-1234")
+
+        def create_vulnerability():
+            login(ANONYMOUS)
+            self.factory.makeVulnerability(
+                cve=cve, distribution=self.factory.makeDistribution()
+            )
+
+        recorder1, recorder2 = record_two_runs(
+            lambda: self.getMainText(cve),
+            create_vulnerability,
+            5,
+        )
+        self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
+
+    def test_non_public_vulnerabilities_not_linked_unauthorized_users(self):
+        cve = self.factory.makeCVE(sequence="2022-1234")
+        distribution = self.factory.makeDistribution(
+            bug_sharing_policy=BugSharingPolicy.PROPRIETARY
+        )
+        vulnerability = self.factory.makeVulnerability(
+            cve=cve,
+            distribution=distribution,
+            information_type=InformationType.PROPRIETARY,
+        )
+        person = self.factory.makePerson()
+        browser = self.getUserBrowser(canonical_url(cve), user=person)
+        login(ANONYMOUS)
+        self.assertThat(
+            browser.contents,
+            HTMLContains(
+                Tag(
+                    "Linked vulnerabilities heading",
+                    "h2",
+                    text="Linked vulnerabilities",
+                ),
+                Tag(
+                    "Linked vulnerabilities details",
+                    "p",
+                    text="No linked vulnerabilities",
+                ),
+            ),
+        )
+        distribution2 = self.factory.makeDistribution()
+        vulnerability2 = self.factory.makeVulnerability(
+            cve=cve,
+            distribution=distribution2,
+        )
+        browser = self.getUserBrowser(canonical_url(cve), user=person)
+        login(ANONYMOUS)
+        self.assertThat(
+            browser.contents,
+            MatchesAll(
+                HTMLContains(
+                    Tag(
+                        "Linked vulnerabilities heading",
+                        "h2",
+                        text="Linked vulnerabilities",
+                    ),
+                    Tag(
+                        "vulnerability2",
+                        "a",
+                        attrs={
+                            "href": canonical_url(
+                                vulnerability2,
+                                force_local_path=True,
+                            ),
+                        },
+                        text="{} vulnerability".format(
+                            distribution2.displayname
+                        ),
+                    ),
+                ),
+                Not(
+                    HTMLContains(
+                        Tag(
+                            "vulnerability",
+                            "a",
+                            attrs={
+                                "href": canonical_url(
+                                    removeSecurityProxy(vulnerability),
+                                    force_local_path=True,
+                                ),
+                            },
+                            text="{} vulnerability".format(
+                                distribution.displayname
+                            ),
+                        )
+                    )
+                ),
+            ),
+        )
+
+    def test_authorized_users_can_see_links_to_non_public_vulnerabilities(
+        self,
+    ):
+        cve = self.factory.makeCVE(sequence="2022-1234")
+        distribution1 = self.factory.makeDistribution()
+        distribution2 = self.factory.makeDistribution(
+            bug_sharing_policy=BugSharingPolicy.PROPRIETARY
+        )
+        vulnerability1 = self.factory.makeVulnerability(
+            cve=cve, distribution=distribution1
+        )
+        vulnerability2 = self.factory.makeVulnerability(
+            cve=cve,
+            distribution=distribution2,
+            information_type=InformationType.PROPRIETARY,
+        )
+        person_with_access = self.factory.makePerson()
+        grant_access_to_non_public_vulnerability(
+            vulnerability2, person_with_access
+        )
+        browser = self.getUserBrowser(
+            canonical_url(cve), user=person_with_access
+        )
+        with person_logged_in(person_with_access):
+            self.assertThat(
+                browser.contents,
+                HTMLContains(
+                    Tag(
+                        "Linked vulnerabilities heading",
+                        "h2",
+                        text="Linked vulnerabilities",
+                    ),
+                    Tag(
+                        "vulnerability1",
+                        "a",
+                        attrs={
+                            "href": canonical_url(
+                                vulnerability1,
+                                force_local_path=True,
+                            ),
+                        },
+                    ),
+                    Tag(
+                        "vulnerability2",
+                        "a",
+                        attrs={
+                            "href": canonical_url(
+                                vulnerability2,
+                                force_local_path=True,
+                            ),
+                        },
+                        text="{} vulnerability".format(
+                            distribution2.displayname
+                        ),
+                    ),
+                ),
+            )
diff --git a/lib/lp/bugs/interfaces/cve.py b/lib/lp/bugs/interfaces/cve.py
index 8795aa9..204dcd7 100644
--- a/lib/lp/bugs/interfaces/cve.py
+++ b/lib/lp/bugs/interfaces/cve.py
@@ -189,6 +189,9 @@ class ICve(Interface):
     def setCVSSVectorForAuthority(authority, vector_string):
         """Set the CVSS vector string from an authority."""
 
+    def getVulnerabilitiesVisibleToUser(user):
+        """Return the linked vulnerabilities visible to the given user."""
+
 
 @exported_as_webservice_collection(ICve)
 class ICveSet(Interface):
diff --git a/lib/lp/bugs/model/cve.py b/lib/lp/bugs/model/cve.py
index 8a9f327..86e9cae 100644
--- a/lib/lp/bugs/model/cve.py
+++ b/lib/lp/bugs/model/cve.py
@@ -20,12 +20,18 @@ from lp.bugs.interfaces.cve import CveStatus, ICve, ICveSet
 from lp.bugs.model.bug import Bug
 from lp.bugs.model.buglinktarget import BugLinkTargetMixin
 from lp.bugs.model.cvereference import CveReference
+from lp.bugs.model.vulnerability import (
+    Vulnerability,
+    get_vulnerability_privacy_filter,
+)
 from lp.services.database import bulk
 from lp.services.database.constants import UTC_NOW
+from lp.services.database.decoratedresultset import DecoratedResultSet
 from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IStore
 from lp.services.database.stormbase import StormBase
 from lp.services.database.stormexpr import fti_search
+from lp.services.webapp.interfaces import ILaunchBag
 from lp.services.xref.interfaces import IXRefSet
 from lp.services.xref.model import XRef
 
@@ -47,9 +53,6 @@ class Cve(StormBase, BugLinkTargetMixin):
     references = ReferenceSet(
         id, "CveReference.cve_id", order_by="CveReference.id"
     )
-    vulnerabilities = ReferenceSet(
-        id, "Vulnerability.cve_id", order_by="Vulnerability.id"
-    )
 
     date_made_public = DateTime(tzinfo=pytz.UTC, allow_none=True)
     discovered_by = Unicode(allow_none=True)
@@ -108,6 +111,32 @@ class Cve(StormBase, BugLinkTargetMixin):
             sorted(bulk.load(Bug, bug_ids), key=operator.attrgetter("id"))
         )
 
+    def getVulnerabilitiesVisibleToUser(self, user):
+        """See `ICve`."""
+        vulnerabilities = Store.of(self).find(
+            Vulnerability,
+            Vulnerability.cve == self,
+            get_vulnerability_privacy_filter(user),
+        )
+        vulnerabilities.order_by(Desc(Vulnerability.date_created))
+
+        def preload_distributions(rows):
+            from lp.registry.model.distribution import Distribution
+
+            bulk.load_related(Distribution, rows, ["distribution_id"])
+
+        return DecoratedResultSet(
+            vulnerabilities,
+            pre_iter_hook=preload_distributions,
+        )
+
+    @property
+    def vulnerabilities(self):
+        """See `ICve`."""
+        return self.getVulnerabilitiesVisibleToUser(
+            getUtility(ILaunchBag).user
+        )
+
     # CveReference's
     def createReference(self, source, content, url=None):
         """See ICveReference."""
diff --git a/lib/lp/bugs/templates/cve-portlet-bugs2.pt b/lib/lp/bugs/templates/cve-portlet-bugs2.pt
index 579544d..e0c8a6e 100644
--- a/lib/lp/bugs/templates/cve-portlet-bugs2.pt
+++ b/lib/lp/bugs/templates/cve-portlet-bugs2.pt
@@ -25,5 +25,13 @@
       </li>
     </ul>
 
-</div>
+    <h2>Linked vulnerabilities</h2>
+    <ul tal:condition="python: context.vulnerabilities.count() > 0">
+      <li tal:repeat="vulnerability context/vulnerabilities">
+        <img src="/@@/cve" /> <a tal:attributes="href vulnerability/fmt:url" tal:content="string:${vulnerability/distribution/displayname} vulnerability"></a>
+      </li>
+    </ul>
 
+    <p tal:condition="python: context.vulnerabilities.count() == 0"><i>No linked vulnerabilities</i></p>
+
+</div>

Follow ups