launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #29266
[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