Merge ~lgp171188/launchpad:security-tracker-cve-changes into launchpad:master

Proposed by Guruprasad
Status: Merged
Approved by: Guruprasad
Approved revision: 2d216ac658b2bd01c02d98bfcf1649d3fc767e48
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~lgp171188/launchpad:security-tracker-cve-changes
Merge into: launchpad:master
Diff against target: 374 lines (+256/-8)
4 files modified
lib/lp/bugs/interfaces/cve.py (+38/-1)
lib/lp/bugs/model/cve.py (+38/-4)
lib/lp/bugs/tests/test_cve.py (+168/-1)
lib/lp/testing/factory.py (+12/-2)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+415960@code.launchpad.net

Commit message

Add the new fields to ICve and Cve

The fields added are 'date_made_public', 'discoverer',
and 'cvss'.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/bugs/interfaces/cve.py b/lib/lp/bugs/interfaces/cve.py
index 2ba5a53..3a860da 100644
--- a/lib/lp/bugs/interfaces/cve.py
+++ b/lib/lp/bugs/interfaces/cve.py
@@ -30,12 +30,15 @@ from zope.interface import (
30from zope.schema import (30from zope.schema import (
31 Choice,31 Choice,
32 Datetime,32 Datetime,
33 Dict,
33 Int,34 Int,
35 Text,
34 TextLine,36 TextLine,
35 )37 )
3638
37from lp import _39from lp import _
38from lp.app.validators.validation import valid_cve_sequence40from lp.app.validators.validation import valid_cve_sequence
41from lp.services.fields import PersonChoice
3942
4043
41class CveStatus(DBEnumeratedType):44class CveStatus(DBEnumeratedType):
@@ -121,12 +124,45 @@ class ICve(Interface):
121 description=_("A title for the CVE")))124 description=_("A title for the CVE")))
122 references = Attribute("The set of CVE References for this CVE.")125 references = Attribute("The set of CVE References for this CVE.")
123126
127 date_made_public = exported(
128 Datetime(title=_('Date Made Public'), required=False, readonly=True),
129 as_of='devel'
130 )
131
132 discoverer = exported(
133 PersonChoice(
134 title=_('Discoverer'),
135 required=False,
136 readonly=True,
137 vocabulary='ValidPerson'
138 ),
139 as_of='devel'
140 )
141
142 cvss = exported(
143 Dict(
144 title=_('CVSS'),
145 description=_(
146 'The CVSS vector strings from various authorities '
147 'that publish it.'
148 ),
149 key_type=Text(title=_('The authority that published the score.')),
150 value_type=Text(title=_('The CVSS vector string.')),
151 required=False,
152 readonly=True,
153 ),
154 as_of='devel'
155 )
156
124 def createReference(source, content, url=None):157 def createReference(source, content, url=None):
125 """Create a new CveReference for this CVE."""158 """Create a new CveReference for this CVE."""
126159
127 def removeReference(ref):160 def removeReference(ref):
128 """Remove a CveReference."""161 """Remove a CveReference."""
129162
163 def setCVSSVectorForAuthority(authority, vector_string):
164 """Set the CVSS vector string from an authority."""
165
130166
131@exported_as_webservice_collection(ICve)167@exported_as_webservice_collection(ICve)
132class ICveSet(Interface):168class ICveSet(Interface):
@@ -140,7 +176,8 @@ class ICveSet(Interface):
140 def __iter__():176 def __iter__():
141 """Iterate through all the Cve records."""177 """Iterate through all the Cve records."""
142178
143 def new(sequence, description, cvestate=CveStatus.CANDIDATE):179 def new(sequence, description, cvestate=CveStatus.CANDIDATE,
180 date_made_public=None, discoverer=None, cvss=None):
144 """Create a new ICve."""181 """Create a new ICve."""
145182
146 @collection_default_content()183 @collection_default_content()
diff --git a/lib/lp/bugs/model/cve.py b/lib/lp/bugs/model/cve.py
index 27a40b5..f49ce00 100644
--- a/lib/lp/bugs/model/cve.py
+++ b/lib/lp/bugs/model/cve.py
@@ -9,10 +9,12 @@ __all__ = [
9import operator9import operator
1010
11import pytz11import pytz
12from storm.databases.postgres import JSON
12from storm.locals import (13from storm.locals import (
13 DateTime,14 DateTime,
14 Desc,15 Desc,
15 Int,16 Int,
17 Reference,
16 ReferenceSet,18 ReferenceSet,
17 Store,19 Store,
18 Unicode,20 Unicode,
@@ -60,11 +62,29 @@ class Cve(StormBase, BugLinkTargetMixin):
60 references = ReferenceSet(62 references = ReferenceSet(
61 id, 'CveReference.cve_id', order_by='CveReference.id')63 id, 'CveReference.cve_id', order_by='CveReference.id')
6264
63 def __init__(self, sequence, status, description):65 date_made_public = DateTime(tzinfo=pytz.UTC, allow_none=True)
66 discoverer_id = Int(name='discoverer', allow_none=True)
67 discoverer = Reference(discoverer_id, 'Person.id')
68 _cvss = JSON(name='cvss', allow_none=True)
69
70 @property
71 def cvss(self):
72 return self._cvss or {}
73
74 @cvss.setter
75 def cvss(self, value):
76 assert value is None or isinstance(value, dict)
77 self._cvss = value
78
79 def __init__(self, sequence, status, description,
80 date_made_public=None, discoverer=None, cvss=None):
64 super().__init__()81 super().__init__()
65 self.sequence = sequence82 self.sequence = sequence
66 self.status = status83 self.status = status
67 self.description = description84 self.description = description
85 self.date_made_public = date_made_public
86 self.discoverer = discoverer
87 self._cvss = cvss
6888
69 @property89 @property
70 def url(self):90 def url(self):
@@ -111,6 +131,12 @@ class Cve(StormBase, BugLinkTargetMixin):
111 getUtility(IXRefSet).delete(131 getUtility(IXRefSet).delete(
112 {('cve', self.sequence): [('bug', str(bug.id))]})132 {('cve', self.sequence): [('bug', str(bug.id))]})
113133
134 def setCVSSVectorForAuthority(self, authority, vector_string):
135 """See ICveReference."""
136 if self._cvss is None:
137 self._cvss = {}
138 self._cvss[authority] = vector_string
139
114140
115@implementer(ICveSet)141@implementer(ICveSet)
116class CveSet:142class CveSet:
@@ -136,10 +162,18 @@ class CveSet:
136 """See ICveSet."""162 """See ICveSet."""
137 return iter(IStore(Cve).find(Cve))163 return iter(IStore(Cve).find(Cve))
138164
139 def new(self, sequence, description, status=CveStatus.CANDIDATE):165 def new(self, sequence, description, status=CveStatus.CANDIDATE,
166 date_made_public=None, discoverer=None, cvss=None):
140 """See ICveSet."""167 """See ICveSet."""
141 cve = Cve(sequence=sequence, status=status,168 cve = Cve(
142 description=description)169 sequence=sequence,
170 status=status,
171 description=description,
172 date_made_public=date_made_public,
173 discoverer=discoverer,
174 cvss=cvss
175 )
176
143 IStore(Cve).add(cve)177 IStore(Cve).add(cve)
144 return cve178 return cve
145179
diff --git a/lib/lp/bugs/tests/test_cve.py b/lib/lp/bugs/tests/test_cve.py
index 90bffc7..6882b34 100644
--- a/lib/lp/bugs/tests/test_cve.py
+++ b/lib/lp/bugs/tests/test_cve.py
@@ -3,10 +3,19 @@
33
4"""CVE related tests."""4"""CVE related tests."""
55
6from datetime import datetime
7
8import pytz
9from testtools.matchers import MatchesStructure
10from testtools.testcase import ExpectedException
6from zope.component import getUtility11from zope.component import getUtility
12from zope.security.proxy import removeSecurityProxy
713
8from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams14from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
9from lp.bugs.interfaces.cve import ICveSet15from lp.bugs.interfaces.cve import (
16 CveStatus,
17 ICveSet,
18 )
10from lp.testing import (19from lp.testing import (
11 login_person,20 login_person,
12 person_logged_in,21 person_logged_in,
@@ -133,3 +142,161 @@ class TestBugLinks(TestCaseWithFactory):
133 self.assertContentEqual([bug1], cve2.bugs)142 self.assertContentEqual([bug1], cve2.bugs)
134 self.assertContentEqual([cve2], bug1.cves)143 self.assertContentEqual([cve2], bug1.cves)
135 self.assertContentEqual([], bug2.cves)144 self.assertContentEqual([], bug2.cves)
145
146
147class TestCve(TestCaseWithFactory):
148 """Tests for Cve fields and methods."""
149
150 layer = DatabaseFunctionalLayer
151
152 def test_cveset_new_method_optional_parameters(self):
153 cve = getUtility(ICveSet).new(
154 sequence='2099-1234',
155 description='A critical vulnerability',
156 status=CveStatus.CANDIDATE
157 )
158 self.assertThat(cve, MatchesStructure.byEquality(
159 sequence='2099-1234',
160 status=CveStatus.CANDIDATE,
161 description='A critical vulnerability',
162 date_made_public=None,
163 discoverer=None,
164 cvss={}
165 ))
166
167 def test_cveset_new_method_parameters(self):
168 person = self.factory.makePerson()
169 today = datetime.now(tz=pytz.UTC)
170 cvss = {
171 'nvd': 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H'
172 }
173 cve = getUtility(ICveSet).new(
174 sequence='2099-1234',
175 description='A critical vulnerability',
176 status=CveStatus.CANDIDATE,
177 date_made_public=today,
178 discoverer=person,
179 cvss=cvss
180 )
181 self.assertThat(cve, MatchesStructure.byEquality(
182 sequence='2099-1234',
183 status=CveStatus.CANDIDATE,
184 description='A critical vulnerability',
185 date_made_public=today,
186 discoverer=person,
187 cvss=cvss
188 ))
189
190 def test_cve_date_made_public_invalid_values(self):
191 invalid_values = ['', 'abcd', {'a': 1},
192 [1, 'a', '2', 'b'], '2022-01-01']
193 cve = self.factory.makeCVE(
194 sequence='2099-1234',
195 description='A critical vulnerability',
196 cvestate=CveStatus.CANDIDATE,
197 )
198 for invalid_value in invalid_values:
199 with ExpectedException(TypeError, 'Expected datetime,.*'):
200 removeSecurityProxy(cve).date_made_public = invalid_value
201
202 def test_cve_discoverer_id_invalid_values(self):
203 invalid_values = ['', 'abcd', '2022-01-01', datetime.now()]
204
205 cve = self.factory.makeCVE(
206 sequence='2099-1234',
207 description='A critical vulnerability',
208 cvestate=CveStatus.CANDIDATE,
209 )
210 for invalid_value in invalid_values:
211 with ExpectedException(TypeError, 'Expected int,.*'):
212 removeSecurityProxy(cve).discoverer_id = invalid_value
213
214 def test_cve_cvss_invalid_values(self):
215 invalid_values = ['', 'abcd', '2022-01-01', datetime.now()]
216 cve = self.factory.makeCVE(
217 sequence='2099-1234',
218 description='A critical vulnerability',
219 cvestate=CveStatus.CANDIDATE,
220 )
221 for invalid_value in invalid_values:
222 with ExpectedException(AssertionError):
223 removeSecurityProxy(cve).cvss = invalid_value
224
225 def test_cvss_value_returned_when_null(self):
226 cve = self.factory.makeCVE(
227 sequence='2099-1234',
228 description='A critical vulnerability',
229 cvestate=CveStatus.CANDIDATE,
230 )
231 cve = removeSecurityProxy(cve)
232 self.assertIsNone(cve._cvss)
233 self.assertEqual({}, cve.cvss)
234
235 def test_setCVSSVectorForAuthority_initially_unset(self):
236 cve = self.factory.makeCVE(
237 sequence='2099-1234',
238 description='A critical vulnerability',
239 cvestate=CveStatus.CANDIDATE,
240 )
241 unproxied_cve = removeSecurityProxy(cve)
242 self.assertIsNone(unproxied_cve._cvss)
243 self.assertEqual({}, unproxied_cve.cvss)
244
245 cve.setCVSSVectorForAuthority(
246 authority="nvd",
247 vector_string="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
248 )
249
250 self.assertEqual(
251 {"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},
252 unproxied_cve.cvss
253 )
254
255 def test_setCVSSVectorForAuthority_overwrite_existing_key_value(self):
256 cve = self.factory.makeCVE(
257 sequence='2099-1234',
258 description='A critical vulnerability',
259 cvestate=CveStatus.CANDIDATE,
260 cvss={"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}
261 )
262 unproxied_cve = removeSecurityProxy(cve)
263 self.assertEqual(
264 {"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},
265 unproxied_cve.cvss
266 )
267
268 cve.setCVSSVectorForAuthority(
269 authority="nvd",
270 vector_string="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
271 )
272
273 self.assertEqual(
274 {"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"},
275 unproxied_cve.cvss
276 )
277
278 def test_setCVSSVectorForAuthority_add_new_when_initial_value_set(self):
279 cve = self.factory.makeCVE(
280 sequence='2099-1234',
281 description='A critical vulnerability',
282 cvestate=CveStatus.CANDIDATE,
283 cvss={"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}
284 )
285 unproxied_cve = removeSecurityProxy(cve)
286 self.assertEqual(
287 {"nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},
288 unproxied_cve.cvss
289 )
290
291 cve.setCVSSVectorForAuthority(
292 authority="nist",
293 vector_string="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
294 )
295
296 self.assertEqual(
297 {
298 "nvd": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
299 "nist": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
300 },
301 unproxied_cve.cvss
302 )
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 041cb89..9e37780 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -4582,11 +4582,21 @@ class BareLaunchpadObjectFactory(ObjectFactory):
4582 return secret, token4582 return secret, token
45834583
4584 def makeCVE(self, sequence, description=None,4584 def makeCVE(self, sequence, description=None,
4585 cvestate=CveStatus.CANDIDATE):4585 cvestate=CveStatus.CANDIDATE,
4586 date_made_public=None, discoverer=None,
4587 cvss=None):
4586 """Create a new CVE record."""4588 """Create a new CVE record."""
4587 if description is None:4589 if description is None:
4588 description = self.getUniqueUnicode()4590 description = self.getUniqueUnicode()
4589 return getUtility(ICveSet).new(sequence, description, cvestate)4591
4592 return getUtility(ICveSet).new(
4593 sequence,
4594 description,
4595 cvestate,
4596 date_made_public,
4597 discoverer,
4598 cvss
4599 )
45904600
4591 def makePublisherConfig(self, distribution=None, root_dir=None,4601 def makePublisherConfig(self, distribution=None, root_dir=None,
4592 base_url=None, copy_base_url=None):4602 base_url=None, copy_base_url=None):

Subscribers

People subscribed via source and target branches

to status/vote changes: