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

Proposed by Guruprasad
Status: Merged
Approved by: Guruprasad
Approved revision: 161c569979c433ca77437532d7a102fcda56689a
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~lgp171188/launchpad:vulnerability-links-in-cve-page-if-present
Merge into: launchpad:master
Diff against target: 346 lines (+264/-5)
5 files modified
lib/lp/bugs/browser/tests/test_vulnerability.py (+216/-0)
lib/lp/bugs/interfaces/cve.py (+3/-0)
lib/lp/bugs/model/cve.py (+31/-3)
lib/lp/bugs/templates/cve-portlet-bugs2.pt (+11/-1)
lib/lp/registry/model/distribution.py (+3/-1)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+430866@code.launchpad.net

Commit message

Link to the linked vulnerabilities on the CVE page

To post a comment you must log in.
Revision history for this message
Guruprasad (lgp171188) wrote :
Revision history for this message
Colin Watson (cjwatson) :
review: Approve
Revision history for this message
Guruprasad (lgp171188) :
Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
Guruprasad (lgp171188) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/bugs/browser/tests/test_vulnerability.py b/lib/lp/bugs/browser/tests/test_vulnerability.py
2index c0711c5..169cf05 100644
3--- a/lib/lp/bugs/browser/tests/test_vulnerability.py
4+++ b/lib/lp/bugs/browser/tests/test_vulnerability.py
5@@ -530,3 +530,219 @@ class TestVulnerabilityListingPage(BrowserTestCase):
6 )
7 )
8 self.assertBatches(distribution, link_matchers, True, 0, 5)
9+
10+
11+class TestVulnerabilitiesLinksOnCVEPage(BrowserTestCase):
12+ """Test for the vulnerabilities links on the CVE page."""
13+
14+ layer = DatabaseFunctionalLayer
15+
16+ def test_cve_page_when_no_linked_vulnerabilities(self):
17+ cve = self.factory.makeCVE(sequence="2022-1234")
18+ browser = self.getUserBrowser(
19+ canonical_url(cve),
20+ user=self.factory.makePerson(),
21+ )
22+ self.assertThat(
23+ browser.contents,
24+ Not(
25+ HTMLContains(
26+ Tag(
27+ "Related distributions heading",
28+ "h2",
29+ text="Related distributions",
30+ ),
31+ ),
32+ ),
33+ )
34+
35+ def test_cve_page_when_linked_vulnerabilities_present(self):
36+ cve = self.factory.makeCVE(sequence="2022-1234")
37+ distribution1 = self.factory.makeDistribution()
38+ distribution2 = self.factory.makeDistribution()
39+ vulnerability1 = self.factory.makeVulnerability(
40+ cve=cve,
41+ distribution=distribution1,
42+ )
43+ vulnerability2 = self.factory.makeVulnerability(
44+ cve=cve,
45+ distribution=distribution2,
46+ )
47+ browser = self.getUserBrowser(
48+ canonical_url(cve), user=self.factory.makePerson()
49+ )
50+ login(ANONYMOUS)
51+ self.assertThat(
52+ browser.contents,
53+ HTMLContains(
54+ Tag(
55+ "Related distributions heading",
56+ "h2",
57+ text="Related distributions",
58+ ),
59+ Tag(
60+ "vulnerability1",
61+ "a",
62+ attrs={
63+ "href": canonical_url(
64+ vulnerability1, force_local_path=True
65+ )
66+ },
67+ text=distribution1.displayname,
68+ ),
69+ Tag(
70+ "vulnerability2",
71+ "a",
72+ attrs={
73+ "href": canonical_url(
74+ vulnerability2, force_local_path=True
75+ )
76+ },
77+ text=distribution2.displayname,
78+ ),
79+ ),
80+ )
81+
82+ def test_query_count_constant_vs_number_of_linked_vulnerabilities(self):
83+ cve = self.factory.makeCVE(sequence="2022-1234")
84+
85+ def create_vulnerability():
86+ login(ANONYMOUS)
87+ self.factory.makeVulnerability(
88+ cve=cve, distribution=self.factory.makeDistribution()
89+ )
90+
91+ recorder1, recorder2 = record_two_runs(
92+ lambda: self.getMainText(cve),
93+ create_vulnerability,
94+ 5,
95+ )
96+ self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
97+
98+ def test_non_public_vulnerabilities_not_linked_unauthorized_users(self):
99+ cve = self.factory.makeCVE(sequence="2022-1234")
100+ distribution = self.factory.makeDistribution(
101+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY
102+ )
103+ vulnerability = self.factory.makeVulnerability(
104+ cve=cve,
105+ distribution=distribution,
106+ information_type=InformationType.PROPRIETARY,
107+ )
108+ person = self.factory.makePerson()
109+ browser = self.getUserBrowser(canonical_url(cve), user=person)
110+ login(ANONYMOUS)
111+ self.assertThat(
112+ browser.contents,
113+ Not(
114+ HTMLContains(
115+ Tag(
116+ "Related distributions heading",
117+ "h2",
118+ text="Related distributions",
119+ ),
120+ ),
121+ ),
122+ )
123+ distribution2 = self.factory.makeDistribution()
124+ vulnerability2 = self.factory.makeVulnerability(
125+ cve=cve,
126+ distribution=distribution2,
127+ )
128+ browser = self.getUserBrowser(canonical_url(cve), user=person)
129+ login(ANONYMOUS)
130+ self.assertThat(
131+ browser.contents,
132+ MatchesAll(
133+ HTMLContains(
134+ Tag(
135+ "Related distributions heading",
136+ "h2",
137+ text="Related distributions",
138+ ),
139+ Tag(
140+ "vulnerability2",
141+ "a",
142+ attrs={
143+ "href": canonical_url(
144+ vulnerability2,
145+ force_local_path=True,
146+ ),
147+ },
148+ text=distribution2.displayname,
149+ ),
150+ ),
151+ Not(
152+ HTMLContains(
153+ Tag(
154+ "vulnerability",
155+ "a",
156+ attrs={
157+ "href": canonical_url(
158+ removeSecurityProxy(vulnerability),
159+ force_local_path=True,
160+ ),
161+ },
162+ text=distribution.displayname,
163+ )
164+ )
165+ ),
166+ ),
167+ )
168+
169+ def test_authorized_users_can_see_links_to_non_public_vulnerabilities(
170+ self,
171+ ):
172+ cve = self.factory.makeCVE(sequence="2022-1234")
173+ distribution1 = self.factory.makeDistribution()
174+ distribution2 = self.factory.makeDistribution(
175+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY
176+ )
177+ vulnerability1 = self.factory.makeVulnerability(
178+ cve=cve, distribution=distribution1
179+ )
180+ vulnerability2 = self.factory.makeVulnerability(
181+ cve=cve,
182+ distribution=distribution2,
183+ information_type=InformationType.PROPRIETARY,
184+ )
185+ person_with_access = self.factory.makePerson()
186+ grant_access_to_non_public_vulnerability(
187+ vulnerability2, person_with_access
188+ )
189+ browser = self.getUserBrowser(
190+ canonical_url(cve), user=person_with_access
191+ )
192+ with person_logged_in(person_with_access):
193+ self.assertThat(
194+ browser.contents,
195+ HTMLContains(
196+ Tag(
197+ "Related distributions heading",
198+ "h2",
199+ text="Related distributions",
200+ ),
201+ Tag(
202+ "vulnerability1",
203+ "a",
204+ attrs={
205+ "href": canonical_url(
206+ vulnerability1,
207+ force_local_path=True,
208+ ),
209+ },
210+ text=distribution1.displayname,
211+ ),
212+ Tag(
213+ "vulnerability2",
214+ "a",
215+ attrs={
216+ "href": canonical_url(
217+ vulnerability2,
218+ force_local_path=True,
219+ ),
220+ },
221+ text=distribution2.displayname,
222+ ),
223+ ),
224+ )
225diff --git a/lib/lp/bugs/interfaces/cve.py b/lib/lp/bugs/interfaces/cve.py
226index 8795aa9..204dcd7 100644
227--- a/lib/lp/bugs/interfaces/cve.py
228+++ b/lib/lp/bugs/interfaces/cve.py
229@@ -189,6 +189,9 @@ class ICve(Interface):
230 def setCVSSVectorForAuthority(authority, vector_string):
231 """Set the CVSS vector string from an authority."""
232
233+ def getVulnerabilitiesVisibleToUser(user):
234+ """Return the linked vulnerabilities visible to the given user."""
235+
236
237 @exported_as_webservice_collection(ICve)
238 class ICveSet(Interface):
239diff --git a/lib/lp/bugs/model/cve.py b/lib/lp/bugs/model/cve.py
240index 8a9f327..0cd8e0e 100644
241--- a/lib/lp/bugs/model/cve.py
242+++ b/lib/lp/bugs/model/cve.py
243@@ -20,12 +20,19 @@ from lp.bugs.interfaces.cve import CveStatus, ICve, ICveSet
244 from lp.bugs.model.bug import Bug
245 from lp.bugs.model.buglinktarget import BugLinkTargetMixin
246 from lp.bugs.model.cvereference import CveReference
247+from lp.bugs.model.vulnerability import (
248+ Vulnerability,
249+ get_vulnerability_privacy_filter,
250+)
251+from lp.registry.model.distribution import Distribution
252 from lp.services.database import bulk
253 from lp.services.database.constants import UTC_NOW
254+from lp.services.database.decoratedresultset import DecoratedResultSet
255 from lp.services.database.enumcol import DBEnum
256 from lp.services.database.interfaces import IStore
257 from lp.services.database.stormbase import StormBase
258 from lp.services.database.stormexpr import fti_search
259+from lp.services.webapp.interfaces import ILaunchBag
260 from lp.services.xref.interfaces import IXRefSet
261 from lp.services.xref.model import XRef
262
263@@ -47,9 +54,6 @@ class Cve(StormBase, BugLinkTargetMixin):
264 references = ReferenceSet(
265 id, "CveReference.cve_id", order_by="CveReference.id"
266 )
267- vulnerabilities = ReferenceSet(
268- id, "Vulnerability.cve_id", order_by="Vulnerability.id"
269- )
270
271 date_made_public = DateTime(tzinfo=pytz.UTC, allow_none=True)
272 discovered_by = Unicode(allow_none=True)
273@@ -108,6 +112,30 @@ class Cve(StormBase, BugLinkTargetMixin):
274 sorted(bulk.load(Bug, bug_ids), key=operator.attrgetter("id"))
275 )
276
277+ def getVulnerabilitiesVisibleToUser(self, user):
278+ """See `ICve`."""
279+ vulnerabilities = Store.of(self).find(
280+ Vulnerability,
281+ Vulnerability.cve == self,
282+ get_vulnerability_privacy_filter(user),
283+ )
284+ vulnerabilities.order_by(Desc(Vulnerability.date_created))
285+
286+ def preload_distributions(rows):
287+ bulk.load_related(Distribution, rows, ["distribution_id"])
288+
289+ return DecoratedResultSet(
290+ vulnerabilities,
291+ pre_iter_hook=preload_distributions,
292+ )
293+
294+ @property
295+ def vulnerabilities(self):
296+ """See `ICve`."""
297+ return self.getVulnerabilitiesVisibleToUser(
298+ getUtility(ILaunchBag).user
299+ )
300+
301 # CveReference's
302 def createReference(self, source, content, url=None):
303 """See ICveReference."""
304diff --git a/lib/lp/bugs/templates/cve-portlet-bugs2.pt b/lib/lp/bugs/templates/cve-portlet-bugs2.pt
305index 579544d..be7513f 100644
306--- a/lib/lp/bugs/templates/cve-portlet-bugs2.pt
307+++ b/lib/lp/bugs/templates/cve-portlet-bugs2.pt
308@@ -25,5 +25,15 @@
309 </li>
310 </ul>
311
312-</div>
313+ <tal:vulnerabilities define="vulnerabilities python:list(context.vulnerabilities)">
314+ <div tal:condition="vulnerabilities">
315+ <h2>Related distributions</h2>
316+ <ul tal:condition="vulnerabilities">
317+ <li tal:repeat="vulnerability vulnerabilities">
318+ <img src="/@@/cve" /> <a tal:attributes="href vulnerability/fmt:url" tal:content="string:${vulnerability/distribution/displayname}"></a>
319+ </li>
320+ </ul>
321+ </div>
322+ </tal:vulnerabilities>
323
324+</div>
325diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
326index e265607..354353b 100644
327--- a/lib/lp/registry/model/distribution.py
328+++ b/lib/lp/registry/model/distribution.py
329@@ -75,7 +75,6 @@ from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask
330 from lp.bugs.interfaces.vulnerability import IVulnerabilitySet
331 from lp.bugs.model.bugtarget import BugTargetBase, OfficialBugTagTargetMixin
332 from lp.bugs.model.bugtaskflat import BugTaskFlat
333-from lp.bugs.model.cve import Cve
334 from lp.bugs.model.structuralsubscription import (
335 StructuralSubscriptionTargetMixin,
336 )
337@@ -2272,6 +2271,9 @@ class Distribution(
338 vulnerabilities.order_by(Desc(Vulnerability.date_created))
339
340 def preload_cves(rows):
341+ # Avoid circular import
342+ from lp.bugs.model.cve import Cve
343+
344 load_related(Cve, rows, ["cve_id"])
345
346 return DecoratedResultSet(vulnerabilities, pre_iter_hook=preload_cves)

Subscribers

People subscribed via source and target branches

to status/vote changes: