@@ -245,69 +245,97 @@ def create_valid_vulnerability_reference(url, reference_id=None):
245245 return reference
246246
247247
248- def get_or_create_vulnerability_and_aliases (alias_names , vulnerability_id = None , summary = None ):
248+ def get_or_create_vulnerability_and_aliases (
249+ aliases : List [str ], vulnerability_id = None , summary = None
250+ ):
249251 """
250252 Get or create vulnerabilitiy and aliases such that all existing and new
251253 aliases point to the same vulnerability
252254 """
253- existing_vulns = set ()
254- alias_names = set (alias_names )
255- new_alias_names = set ()
256- for alias_name in alias_names :
257- try :
258- alias = Alias .objects .get (alias = alias_name )
259- existing_vulns .add (alias .vulnerability )
260- except Alias .DoesNotExist :
261- new_alias_names .add (alias_name )
262-
263- # If given set of aliases point to different vulnerabilities in the
264- # database, request is malformed
265- # TODO: It is possible that all those vulnerabilities are actually
266- # the same at data level, figure out a way to merge them
267- if len (existing_vulns ) > 1 :
268- logger .warning (
269- f"Given aliases { alias_names } already exist and do not point "
270- f"to a single vulnerability. Cannot improve. Skipped."
271- )
272- return
255+ aliases = set (alias .strip () for alias in aliases if alias and alias .strip ())
256+ new_alias_names , existing_vulns = get_vulns_for_aliases_and_get_new_aliases (aliases )
257+
258+ # All aliases must point to the same vulnerability
259+ vulnerability = None
260+ if existing_vulns :
261+ if len (existing_vulns ) != 1 :
262+ vcids = ", " .join (v .vulnerability_id for v in existing_vulns )
263+ logger .error (
264+ f"Cannot create vulnerability. "
265+ f"Aliases { aliases } already exist and point "
266+ f"to multiple vulnerabilities { vcids } ."
267+ )
268+ return
269+ else :
270+ vulnerability = existing_vulns .pop ()
273271
274- existing_alias_vuln = existing_vulns .pop () if existing_vulns else None
275-
276- if (
277- existing_alias_vuln
278- and vulnerability_id
279- and existing_alias_vuln .vulnerability_id != vulnerability_id
280- ):
281- logger .warning (
282- f"Given aliases { alias_names !r} already exist and point to existing"
283- f"vulnerability { existing_alias_vuln } . Unable to create Vulnerability "
284- f"with vulnerability_id { vulnerability_id } . Skipped"
285- )
286- return
272+ if vulnerability_id and vulnerability .vulnerability_id != vulnerability_id :
273+ logger .error (
274+ f"Cannot create vulnerability. "
275+ f"Aliases { aliases } already exist and point to a different "
276+ f"vulnerability { vulnerability } than the requested "
277+ f"vulnerability { vulnerability_id } ."
278+ )
279+ return
287280
288- if existing_alias_vuln :
289- vulnerability = existing_alias_vuln
290- elif vulnerability_id :
281+ if vulnerability_id and not vulnerability :
291282 try :
292283 vulnerability = Vulnerability .objects .get (vulnerability_id = vulnerability_id )
293284 except Vulnerability .DoesNotExist :
294- logger .warning (
295- f"Given vulnerability_id: { vulnerability_id } does not exist in the database"
296- )
285+ logger .error (f"Cannot get requested vulnerability { vulnerability_id } ." )
297286 return
287+ if vulnerability :
288+ # TODO: We should keep multiple summaries, one for each advisory
289+ # if summary and summary != vulnerability.summary:
290+ # logger.warning(
291+ # f"Inconsistent summary for {vulnerability.vulnerability_id}. "
292+ # f"Existing: {vulnerability.summary!r}, provided: {summary!r}"
293+ # )
294+ associate_vulnerability_with_aliases (vulnerability = vulnerability , aliases = new_alias_names )
298295 else :
299- vulnerability = Vulnerability (summary = summary )
300- vulnerability .save ()
296+ try :
297+ vulnerability = create_vulnerability_and_add_aliases (
298+ aliases = new_alias_names , summary = summary
299+ )
300+ except Exception as e :
301+ logger .error (
302+ f"Cannot create vulnerability with summary { summary !r} and { new_alias_names !r} { e !r} .\n { traceback_format_exc ()} ."
303+ )
304+ return
305+
306+ return vulnerability
307+
308+
309+ def get_vulns_for_aliases_and_get_new_aliases (aliases ):
310+ """
311+ Return ``new_aliases`` that are not in the database and
312+ ``existing_vulns`` that point to the given ``aliases``.
313+ """
314+ new_aliases = set (aliases )
315+ existing_vulns = set ()
316+ for alias in Alias .objects .filter (alias__in = aliases ):
317+ existing_vulns .add (alias .vulnerability )
318+ new_aliases .remove (alias .alias )
319+ return new_aliases , existing_vulns
301320
302- if summary and summary != vulnerability .summary :
303- logger .warning (
304- f"Inconsistent summary for { vulnerability !r} . "
305- f"Existing: { vulnerability .summary } , provided: { summary } "
306- )
307321
308- for alias_name in new_alias_names :
322+ @transaction .atomic
323+ def create_vulnerability_and_add_aliases (aliases , summary ):
324+ """
325+ Return a new ``vulnerability`` created with ``summary``
326+ and associate the ``vulnerability`` with ``aliases``.
327+ Raise exception if no alias is associated with the ``vulnerability``.
328+ """
329+ vulnerability = Vulnerability (summary = summary )
330+ vulnerability .save ()
331+ associate_vulnerability_with_aliases (aliases , vulnerability )
332+ if not vulnerability .aliases .count ():
333+ raise Exception (f"Vulnerability { vulnerability .vcid } must have one or more aliases" )
334+ return vulnerability
335+
336+
337+ def associate_vulnerability_with_aliases (aliases , vulnerability ):
338+ for alias_name in aliases :
309339 alias = Alias (alias = alias_name , vulnerability = vulnerability )
310340 alias .save ()
311341 logger .info (f"New alias for { vulnerability !r} : { alias_name } " )
312-
313- return vulnerability
0 commit comments