2424import dataclasses
2525import datetime
2626import logging
27- from itertools import chain
2827from typing import Dict
2928from typing import List
3029from typing import Set
4948
5049
5150@dataclasses .dataclass (frozen = True )
52- class VulnerabilityReference_inserter :
51+ class VulnerabilityReferenceInserter :
5352 vulnerability : models .Vulnerability
5453 reference_id : Optional [str ] = ''
5554 url : Optional [str ] = ''
5655
5756 def __post_init__ (self ):
5857 if not any ([self .reference_id , self .url ]):
5958 raise TypeError (
60- "VulnerabilityReference_inserter expects either reference_id or url" )
59+ "VulnerabilityReferenceInserter expects either reference_id or url" )
6160
6261 def to_model_object (self ):
6362 return models .VulnerabilityReference (** dataclasses .asdict (self ))
@@ -69,7 +68,7 @@ def to_model_object(self):
6968
7069
7170@dataclasses .dataclass (frozen = True )
72- class PackageRelatedVulnerability_inserter :
71+ class PackageRelatedVulnerabilityInserter :
7372 vulnerability : models .Vulnerability
7473 is_vulnerable : bool
7574 package : models .Package
@@ -125,14 +124,10 @@ def run(self, cutoff_date: datetime.datetime = None) -> None:
125124
126125
127126def _process_updated_advisories (data_source : DataSource ) -> None :
128- """
129- TODO: Break this method into smaller functions
130- """
131127 bulk_create_vuln_refs = set ()
132128 bulk_create_vuln_pkg_refs = set ()
133129 for batch in data_source .updated_advisories ():
134130 for advisory in batch :
135-
136131 vuln , vuln_created , references = _create_vulnerability_and_references (
137132 advisory )
138133 bulk_create_vuln_refs .update (references )
@@ -142,6 +137,11 @@ def _process_updated_advisories(data_source: DataSource) -> None:
142137 vuln , vuln_created , advisory .resolved_package_urls , is_vulnerable = False )
143138 bulk_create_vuln_pkg_refs .update (inew_refs .union (rnew_refs ))
144139
140+ # FIXME: _create_pkg_vuln_refs handles conflicts between the data we encounter via
141+ # updated_advisories() and the data which already exists in the DB. It is not designed
142+ # to handle the conflicting data present within the entries of updated_advisories() itself.
143+ # This can be done by filtering bulk_create_vuln_pkg_refs for pairs (vulnerability, package)
144+ # occurring more than once. Also update the constraints in the models after this is fixed.
145145 models .VulnerabilityReference .objects .bulk_create (
146146 [i .to_model_object () for i in bulk_create_vuln_refs ])
147147 models .PackageRelatedVulnerability .objects .bulk_create (
@@ -170,63 +170,68 @@ def _create_vulnerability_and_references(advisory: Advisory):
170170 vuln_references = set ()
171171
172172 if vuln_created :
173- # This means vulnerability didn't previously exist in the db, so bulk create
174- # is used without any hesitation
175- for id_ in set (advisory .reference_ids ):
176- vuln_references .add (VulnerabilityReference_inserter (
177- vulnerability = vuln , reference_id = id_ ))
173+ # This means vulnerability didn't previously exist in the DB, so add
174+ # the references to bulk create queue without any hesitation
175+ for id_ in advisory .reference_ids :
176+ vuln_references .add (VulnerabilityReferenceInserter (vulnerability = vuln , reference_id = id_ )) # nopep8
178177
179- for url in set (advisory .reference_urls ):
180- vuln_references .add (VulnerabilityReference_inserter (
181- vulnerability = vuln , url = url ))
178+ for url in advisory .reference_urls :
179+ vuln_references .add (VulnerabilityReferenceInserter (vulnerability = vuln , url = url ))
182180
183181 else :
184- vuln_refs_qs = models .VulnerabilityReference .objects .filter (
185- vulnerability = vuln )
182+ vuln_refs_qs = models .VulnerabilityReference .objects .filter (vulnerability = vuln )
186183 vuln_ids = {ref .reference_id for ref in vuln_refs_qs }
187184 vuln_urls = {ref .url for ref in vuln_refs_qs }
188185
189186 for id_ in advisory .reference_ids :
190187 # Add the item preventing duplicates pass to through.
191188 if id_ not in vuln_ids :
192189 vuln_ids .add (id_ )
193- vuln_references .add (VulnerabilityReference_inserter (
190+ vuln_references .add (VulnerabilityReferenceInserter (
194191 vulnerability = vuln , reference_id = id_ ))
195192
196193 for url in advisory .reference_urls :
197194 # Add the item preventing duplicates pass to through.
198195 if url not in vuln_urls :
199196 vuln_urls .add (url )
200- vuln_references .add (VulnerabilityReference_inserter (
197+ vuln_references .add (VulnerabilityReferenceInserter (
201198 vulnerability = vuln , url = url ))
202199
203200 return vuln , vuln_created , vuln_references
204201
205202
206203def _create_pkg_vuln_refs (vuln : models .Vulnerability , vuln_created : bool , purls : Sequence [PackageURL ], is_vulnerable : bool ): # nopep8
207- new_refs , updated_refs = set (), set ()
204+ new_refs = set ()
208205 for purl in purls :
209206 pkg , pkg_created = _get_or_create_package (purl )
210- vuln_pkg_ref = PackageRelatedVulnerability_inserter (
211- package = pkg , vulnerability = vuln , is_vulnerable = is_vulnerable )
207+ vuln_pkg_ref = PackageRelatedVulnerabilityInserter (
208+ package = pkg , vulnerability = vuln , is_vulnerable = is_vulnerable )
209+
212210 if pkg_created or vuln_created :
213211 new_refs .add (vuln_pkg_ref )
214212
215213 else :
216- qs = models .PackageRelatedVulnerability .objects .filter (
214+ existing_pkg_vuln_refs = models .PackageRelatedVulnerability .objects .filter (
217215 package = pkg , vulnerability = vuln )
218- if not qs :
216+ if not existing_pkg_vuln_refs :
219217 new_refs .add (vuln_pkg_ref )
220218 else :
221219 # Note: PackageRelatedVulnerability has constraints
222220 # unique_together = ('package', 'vulnerability', 'is_vulnerable')
223- # This fact is used below.
224- vuln_impact = {i .is_vulnerable for i in qs }
225- if len (vuln_impact ) == 2 or is_vulnerable not in vuln_impact :
226- conflicts = [i for i in qs ]
221+ vuln_impact = {i .is_vulnerable for i in existing_pkg_vuln_refs }
222+ # is_vulnerable is a boolean which indicates the relationship of
223+ # a vulnerability's impact on a package. vuln_impact is a set of
224+ # all such booleans for a pair of (vulnerability, package). In cases
225+ # where vuln_impact == {True, False}, we know that conflicting relationships
226+ # of (vulnerability, package) ALREADY EXIST in the DB.
227+ # The other check `is_vulnerable not in vuln_impact` is used to know whether the
228+ # data we just found is not conflicting with the data already existing in DB.
229+ # In any of the above two cases we move the entries involved in ImportProblem
230+ if vuln_impact == {True , False } or is_vulnerable not in vuln_impact :
231+ conflicts = existing_pkg_vuln_refs [:]
227232 conflicts .append (vuln_pkg_ref .to_model_object ())
228233 handle_conflicts (conflicts )
229- qs .delete ()
234+ existing_pkg_vuln_refs .delete ()
230235
231236 return new_refs
232237
@@ -278,28 +283,32 @@ def _get_or_create_package(p: PackageURL) -> Tuple[models.Package, bool]:
278283
279284
280285def _bulk_insert_packages (
281- impacted : List [PackageURL ],
282- resolved : List [PackageURL ]
283- ) -> Tuple [Dict [PackageURL , int ], Dict [PackageURL , int ]]:
286+ impacted : Set [PackageURL ],
287+ resolved : Set [PackageURL ]
288+ ) -> Tuple [Dict [PackageURL , models .Package ], Dict [PackageURL , models .Package ]]:
289+
290+ packages = [_package_url_to_package (p ) for p in impacted .union (resolved )]
291+ packages = models .Package .objects .bulk_create (packages )
292+
293+ impacted_packages , resolved_packages = {}, {}
284294
285- impacted_packages = models .Package .objects .bulk_create (
286- [_package_url_to_package (p ) for p in impacted ])
287- resolved_packages = models .Package .objects .bulk_create (
288- [_package_url_to_package (p ) for p in resolved ])
295+ for pkg in packages :
296+ # Unfortunately, PackageURLMixin.package_url returns a string, not a PackageURL
297+ purl = PackageURL .from_string (pkg .package_url )
289298
290- impacted_packages = dict (
291- zip ( impacted , [ pkg . id for pkg in impacted_packages ]))
292- resolved_packages = dict (
293- zip ( resolved , [ pkg . id for pkg in resolved_packages ]))
299+ if purl in impacted :
300+ impacted_packages [ purl ] = pkg
301+ elif purl in resolved :
302+ resolved_packages [ purl ] = pkg
294303
295304 return impacted_packages , resolved_packages
296305
297306
298307def _bulk_insert_impacted_and_resolved_packages (
299308 batch : Set [Advisory ],
300309 vulnerabilities : Set [models .Vulnerability ],
301- impacted_packages : Dict [PackageURL , int ],
302- resolved_packages : Dict [PackageURL , int ],
310+ impacted_packages : Dict [PackageURL , models . Package ],
311+ resolved_packages : Dict [PackageURL , models . Package ],
303312) -> None :
304313
305314 refs : List [models .ImpactedPackage ] = []
@@ -313,11 +322,11 @@ def _bulk_insert_impacted_and_resolved_packages(
313322 if not p :
314323 p = _package_url_to_package (impacted_purl )
315324 p .save ()
316- impacted_packages [impacted_purl ] = p . id
325+ impacted_packages [impacted_purl ] = p
317326
318327 ip = models .PackageRelatedVulnerability (
319328 vulnerability = vuln ,
320- package_id = p ,
329+ package = p ,
321330 is_vulnerable = True
322331 )
323332 refs .append (ip )
@@ -328,11 +337,11 @@ def _bulk_insert_impacted_and_resolved_packages(
328337 if not p :
329338 p = _package_url_to_package (resolved_purl )
330339 p .save ()
331- resolved_packages [resolved_purl ] = p . id
340+ resolved_packages [resolved_purl ] = p
332341
333342 ip = models .PackageRelatedVulnerability (
334343 vulnerability = vuln ,
335- package_id = p ,
344+ package = p ,
336345 is_vulnerable = False
337346 )
338347 refs .append (ip )
@@ -390,12 +399,12 @@ def _advisory_to_vulnerability(
390399 raise RuntimeError (f'No Vulnerability model object found for this Advisory: { advisory .summary } ' )
391400
392401
393- def _collect_package_urls (batch : Set [Advisory ]) -> Tuple [List [PackageURL ], List [PackageURL ]]:
394- impacted , resolved = [], []
402+ def _collect_package_urls (batch : Set [Advisory ]) -> Tuple [Set [PackageURL ], Set [PackageURL ]]:
403+ impacted , resolved = set (), set ()
395404
396405 for advisory in batch :
397- impacted .extend (advisory .impacted_package_urls )
398- resolved .extend (advisory .resolved_package_urls )
406+ impacted .update (advisory .impacted_package_urls )
407+ resolved .update (advisory .resolved_package_urls )
399408
400409 return impacted , resolved
401410
0 commit comments