launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #29238
[Merge] ~lgp171188/launchpad:vulnerability-set-view into launchpad:master
Guruprasad has proposed merging ~lgp171188/launchpad:vulnerability-set-view into launchpad:master.
Commit message:
Add a distribution vulnerability listing pag
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~lgp171188/launchpad/+git/launchpad/+merge/430357
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~lgp171188/launchpad:vulnerability-set-view into launchpad:master.
diff --git a/lib/lp/bugs/browser/configure.zcml b/lib/lp/bugs/browser/configure.zcml
index de5d592..e5d11ae 100644
--- a/lib/lp/bugs/browser/configure.zcml
+++ b/lib/lp/bugs/browser/configure.zcml
@@ -907,6 +907,17 @@
class="lp.bugs.browser.vulnerability.VulnerabilityIndexView"
permission="zope.Public"
template="../templates/vulnerability-index.pt" />
+ <browser:page
+ name="+listing-detailed"
+ for="lp.bugs.interfaces.vulnerability.IVulnerability"
+ permission="zope.Public"
+ template="../templates/vulnerability-listing-detailed.pt"/>
+ <browser:page
+ name="+vulnerabilities"
+ for="lp.registry.interfaces.distribution.IDistribution"
+ class="lp.bugs.browser.vulnerability.VulnerabilitySetIndexView"
+ permission="zope.Public"
+ template="../templates/vulnerabilityset-index.pt" />
<browser:url
for="lp.bugs.interfaces.bugsubscription.IBugSubscription"
path_expression="string:+subscription/${person/name}"
diff --git a/lib/lp/bugs/browser/tests/test_vulnerability.py b/lib/lp/bugs/browser/tests/test_vulnerability.py
index 1a0fcc2..40815d1 100644
--- a/lib/lp/bugs/browser/tests/test_vulnerability.py
+++ b/lib/lp/bugs/browser/tests/test_vulnerability.py
@@ -2,14 +2,23 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Vulnerability browser tests"""
+import random
from datetime import datetime, timezone
from soupmatchers import HTMLContains, Tag, Within
from testtools.matchers import MatchesAll, Not
from lp.services.webapp import canonical_url
-from lp.testing import ANONYMOUS, BrowserTestCase, login, person_logged_in
+from lp.testing import (
+ ANONYMOUS,
+ BrowserTestCase,
+ login,
+ person_logged_in,
+ record_two_runs,
+)
from lp.testing.layers import DatabaseFunctionalLayer
+from lp.testing.matchers import HasQueryCount
+from lp.testing.views import create_initialized_view
class TestVulnerabilityPage(BrowserTestCase):
@@ -251,3 +260,203 @@ class TestVulnerabilityPage(BrowserTestCase):
),
),
)
+
+
+class TestVulnerabilityListingPage(BrowserTestCase):
+ """Tests for the vulnerability listing page."""
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super().setUp()
+ self.cve_index = 1
+
+ def makeVulnerabilitiesAndMatchers(
+ self, distribution, count, cve_sequence_start=1234
+ ):
+ vulnerability_matchers = []
+ for _ in range(count):
+ cve = self.factory.makeCVE("2022-%04i" % self.cve_index)
+ self.cve_index += 1
+ vulnerability_matchers.append(
+ Tag(
+ "vulnerability link",
+ "a",
+ attrs={
+ "href": canonical_url(
+ self.factory.makeVulnerability(
+ distribution=distribution, cve=cve
+ )
+ )
+ },
+ )
+ )
+ return vulnerability_matchers
+
+ def assertBatches(self, context, link_matchers, batched, start, size):
+ view = create_initialized_view(context, "+vulnerabilities")
+ listing_tag = Tag(
+ "vulnerability listing", "div", attrs={"class": "listing"}
+ )
+ batch_nav_tag = Tag(
+ "batch nav links", "td", attrs={"class": "batch-navigation-links"}
+ )
+ present_links = ([batch_nav_tag] if batched else []) + [
+ matcher
+ for i, matcher in enumerate(link_matchers)
+ if i in range(start, start + size)
+ ]
+ absent_links = ([] if batched else [batch_nav_tag]) + [
+ matcher
+ for i, matcher in enumerate(link_matchers)
+ if i not in range(start, start + size)
+ ]
+ self.assertThat(
+ view.render(),
+ MatchesAll(
+ HTMLContains(listing_tag, *present_links),
+ Not(HTMLContains(*absent_links)),
+ ),
+ )
+
+ def assertVulnerabilitiesQueryCount(self, context, create_func):
+ self.pushConfig("launchpad", default_batch_size=5)
+ recorder1, recorder2 = record_two_runs(
+ lambda: self.getMainText(context, "+vulnerabilities"),
+ create_func,
+ 5,
+ )
+ # Even though there are 10 vulnerabilities and CVEs in the 2nd run,
+ # only the CVEs related to the first 5 (batch size) vulnerabilities,
+ # if present, are queried due to the batching. If some vulnerabilities
+ # do not have a linked CVE, the query count will reduce by the total
+ # number of such vulnerabilities.
+ self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
+
+ def test_vulnerabilities_with_cves_listing_query_count(self):
+ distribution = self.factory.makeDistribution()
+ cve_sequences = set()
+
+ def create_vulnerability():
+ while True:
+ cve_sequence = random.randint(1000, 9999)
+ if cve_sequence not in cve_sequences:
+ cve_sequences.add(cve_sequence)
+ login(ANONYMOUS)
+ cve = self.factory.makeCVE(
+ sequence="2022-{}".format(cve_sequence)
+ )
+ self.factory.makeVulnerability(
+ distribution=distribution, cve=cve
+ )
+ break
+
+ self.assertVulnerabilitiesQueryCount(
+ distribution, create_vulnerability
+ )
+
+ def test_vulnerabilities_without_cve_listing_query_count(self):
+ distribution = self.factory.makeDistribution()
+
+ def create_vulnerability():
+ login(ANONYMOUS)
+ self.factory.makeVulnerability(distribution=distribution)
+
+ self.assertVulnerabilitiesQueryCount(
+ distribution, create_vulnerability
+ )
+
+ def test_vulnerability_listing(self):
+ distribution = self.factory.makeDistribution()
+ vulnerability1 = self.factory.makeVulnerability(
+ distribution=distribution
+ )
+ vulnerability2 = self.factory.makeVulnerability()
+ cve = self.factory.makeCVE(sequence="2022-1234")
+ vulnerability3 = self.factory.makeVulnerability(
+ distribution=distribution, cve=cve
+ )
+ browser = self.getUserBrowser(
+ canonical_url(distribution) + "/+vulnerabilities",
+ user=self.factory.makePerson(),
+ )
+ login(ANONYMOUS)
+ self.assertThat(
+ browser.contents,
+ Not(
+ HTMLContains(
+ Tag(
+ "vulnerability 2 link",
+ "a",
+ attrs={
+ "href": canonical_url(vulnerability2),
+ },
+ )
+ )
+ ),
+ )
+ for vulnerability in (vulnerability1, vulnerability3):
+ vulnerability_div = Tag(
+ "vulnerability div",
+ "div",
+ attrs={
+ "id": "vulnerability-{}".format(vulnerability.id),
+ },
+ )
+ vulnerability_img = Tag(
+ "vulnerability img",
+ "img",
+ attrs={
+ "src": "/@@/cve",
+ },
+ )
+ vulnerability_link = Tag(
+ "vulnerability link",
+ "a",
+ attrs={
+ "href": canonical_url(vulnerability),
+ },
+ )
+
+ description = ""
+ if vulnerability.description:
+ description = vulnerability.description
+ elif vulnerability.cve:
+ description = vulnerability.cve.description
+
+ if not description:
+ vulnerability_description = Tag(
+ "vulnerability description", "div"
+ )
+ else:
+ vulnerability_description = Tag(
+ "vulnerability description", "div", text=description
+ )
+ self.assertThat(
+ browser.contents,
+ HTMLContains(
+ Within(
+ vulnerability_div,
+ vulnerability_img,
+ ),
+ Within(
+ vulnerability_div,
+ vulnerability_link,
+ ),
+ Within(
+ vulnerability_div,
+ vulnerability_description,
+ ),
+ ),
+ )
+
+ def test_vulnerability_batched_listing(self):
+ distribution = self.factory.makeDistribution()
+ link_matchers = self.makeVulnerabilitiesAndMatchers(distribution, 3)
+ self.assertBatches(distribution, link_matchers, False, 0, 3)
+ link_matchers.extend(
+ self.makeVulnerabilitiesAndMatchers(
+ distribution, 7, cve_sequence_start=2345
+ )
+ )
+ self.assertBatches(distribution, link_matchers, True, 0, 5)
diff --git a/lib/lp/bugs/browser/vulnerability.py b/lib/lp/bugs/browser/vulnerability.py
index 3d40cc1..2071463 100644
--- a/lib/lp/bugs/browser/vulnerability.py
+++ b/lib/lp/bugs/browser/vulnerability.py
@@ -5,9 +5,12 @@
__all__ = [
"VulnerabilityIndexView",
+ "VulnerabilitySetIndexView",
]
from lp.bugs.browser.buglinktarget import BugLinksListingView
+from lp.services.webapp import LaunchpadView
+from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.breadcrumb import TitleBreadcrumb
@@ -28,3 +31,16 @@ class VulnerabilityIndexView(BugLinksListingView):
return "{} in the {} distribution".format(
displayname, self.context.distribution.displayname
)
+
+
+class VulnerabilitySetIndexView(LaunchpadView):
+ @property
+ def label(self):
+ return "{} vulnerabilities".format(self.context.displayname)
+
+ @property
+ def page_title(self):
+ return self.label
+
+ def getAllBatched(self):
+ return BatchNavigator(self.context.vulnerabilities, self.request)
diff --git a/lib/lp/bugs/templates/vulnerability-listing-detailed.pt b/lib/lp/bugs/templates/vulnerability-listing-detailed.pt
new file mode 100644
index 0000000..315587f
--- /dev/null
+++ b/lib/lp/bugs/templates/vulnerability-listing-detailed.pt
@@ -0,0 +1,23 @@
+<tal:root
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ omit-tag="">
+ <div tal:attributes="id string:vulnerability-${context/id}">
+ <img src="/@@/cve" />
+ <a tal:attributes="href context/fmt:url"
+ tal:content="context/title"></a>
+ <div style="margin-left: 25px"
+ tal:condition="context/description"
+ tal:content="context/description">
+ </div>
+ <div style="margin-left: 25px"
+ tal:condition="python: not context.description and context.cve"
+ tal:content="context/cve/description">
+ </div>
+ <div style="margin-left: 25px"
+ tal:condition="python: not context.description and not context.cve"
+ tal:content="context/description">
+ </div>
+ </div>
+ <hr />
+</tal:root>
diff --git a/lib/lp/bugs/templates/vulnerabilityset-index.pt b/lib/lp/bugs/templates/vulnerabilityset-index.pt
new file mode 100644
index 0000000..ab05b65
--- /dev/null
+++ b/lib/lp/bugs/templates/vulnerabilityset-index.pt
@@ -0,0 +1,25 @@
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ xml:lang="en"
+ lang="en"
+ dir="ltr"
+ metal:use-macro="view/macro:page/main_only"
+ i18n:domain="malone"
+>
+ <body>
+ <div metal:fill-slot="main"
+ tal:define="batchnav view/getAllBatched;
+ batch batchnav/currentBatch">
+ <tal:navigation
+ tal:condition="batchnav/has_multiple_pages"
+ replace="structure batchnav/@@+navigation-links-upper" />
+ <div class="listing">
+ <div tal:repeat="vulnerability batch"
+ tal:replace="structure vulnerability/@@+listing-detailed" />
+ </div>
+ </div>
+ </body>
+</html>
Follow ups