Merge ~lgp171188/launchpad:vulnerability-links-in-cve-page-if-present into launchpad:master
- Git
- lp:~lgp171188/launchpad
- vulnerability-links-in-cve-page-if-present
- Merge into 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) |
Related bugs: |
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
Description of the change
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
1 | diff --git a/lib/lp/bugs/browser/tests/test_vulnerability.py b/lib/lp/bugs/browser/tests/test_vulnerability.py |
2 | index 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 | + ) |
225 | diff --git a/lib/lp/bugs/interfaces/cve.py b/lib/lp/bugs/interfaces/cve.py |
226 | index 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): |
239 | diff --git a/lib/lp/bugs/model/cve.py b/lib/lp/bugs/model/cve.py |
240 | index 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.""" |
304 | diff --git a/lib/lp/bugs/templates/cve-portlet-bugs2.pt b/lib/lp/bugs/templates/cve-portlet-bugs2.pt |
305 | index 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> |
325 | diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py |
326 | index 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) |
How this looks like: /people. canonical. com/~guruprasad /cve_linked_ vulnerabilities .png /people. canonical. com/~guruprasad /cve_no_ linked_ vulnerabilities .png
1. https:/
2. https:/