@@ -250,9 +250,6 @@ def get_queryset(self):
250250 fixing_vulnerabilities__vulnerability_id = fixing_vulnerability
251251 )
252252
253- queryset = queryset .with_is_vulnerable ()
254-
255- # Prefetch related vulnerabilities and their related data
256253 queryset = queryset .prefetch_related (
257254 Prefetch (
258255 "affected_by_vulnerabilities" ,
@@ -358,3 +355,165 @@ def lookup(self, request):
358355
359356 qs = self .get_queryset ().for_purls ([purl ]).with_is_vulnerable ()
360357 return Response (PackageV2Serializer (qs , many = True , context = {"request" : request }).data )
358+
359+ @extend_schema (
360+ request = PackageurlListSerializer ,
361+ responses = {200 : PackageV2Serializer (many = True )},
362+ )
363+ @action (
364+ detail = False ,
365+ methods = ["post" ],
366+ serializer_class = PackageurlListSerializer ,
367+ filter_backends = [],
368+ pagination_class = None ,
369+ )
370+ def bulk_lookup (self , request ):
371+ """
372+ Return the response for exact PackageURLs requested for.
373+ """
374+ serializer = self .serializer_class (data = request .data )
375+ if not serializer .is_valid ():
376+ return Response (
377+ status = status .HTTP_400_BAD_REQUEST ,
378+ data = {
379+ "error" : serializer .errors ,
380+ "message" : "A non-empty 'purls' list of PURLs is required." ,
381+ },
382+ )
383+ validated_data = serializer .validated_data
384+ purls = validated_data .get ("purls" )
385+
386+ # Fetch packages matching the provided purls
387+ packages = Package .objects .for_purls (purls ).with_is_vulnerable ()
388+
389+ # Collect vulnerabilities associated with these packages
390+ vulnerabilities = set ()
391+ for package in packages :
392+ vulnerabilities .update (package .affected_by_vulnerabilities .all ())
393+ vulnerabilities .update (package .fixing_vulnerabilities .all ())
394+
395+ # Serialize vulnerabilities with vulnerability_id as keys
396+ vulnerability_data = {
397+ vuln .vulnerability_id : VulnerabilityV2Serializer (vuln ).data for vuln in vulnerabilities
398+ }
399+
400+ # Serialize packages
401+ package_data = PackageV2Serializer (
402+ packages ,
403+ many = True ,
404+ context = {"request" : request },
405+ ).data
406+
407+ return Response (
408+ {
409+ "vulnerabilities" : vulnerability_data ,
410+ "packages" : package_data ,
411+ }
412+ )
413+
414+ @extend_schema (
415+ request = PackageBulkSearchRequestSerializer ,
416+ responses = {200 : PackageV2Serializer (many = True )},
417+ )
418+ @action (
419+ detail = False ,
420+ methods = ["post" ],
421+ serializer_class = PackageBulkSearchRequestSerializer ,
422+ filter_backends = [],
423+ pagination_class = None ,
424+ )
425+ def bulk_search (self , request ):
426+ """
427+ Lookup for vulnerable packages using many Package URLs at once.
428+ """
429+ serializer = self .serializer_class (data = request .data )
430+ if not serializer .is_valid ():
431+ return Response (
432+ status = status .HTTP_400_BAD_REQUEST ,
433+ data = {
434+ "error" : serializer .errors ,
435+ "message" : "A non-empty 'purls' list of PURLs is required." ,
436+ },
437+ )
438+ validated_data = serializer .validated_data
439+ purls = validated_data .get ("purls" )
440+ purl_only = validated_data .get ("purl_only" , False )
441+ plain_purl = validated_data .get ("plain_purl" , False )
442+
443+ if plain_purl :
444+ purl_objects = [PackageURL .from_string (purl ) for purl in purls ]
445+ plain_purl_objects = [
446+ PackageURL (
447+ type = purl .type ,
448+ namespace = purl .namespace ,
449+ name = purl .name ,
450+ version = purl .version ,
451+ )
452+ for purl in purl_objects
453+ ]
454+ plain_purls = [str (purl ) for purl in plain_purl_objects ]
455+
456+ query = (
457+ Package .objects .filter (plain_package_url__in = plain_purls )
458+ .order_by ("plain_package_url" )
459+ .distinct ("plain_package_url" )
460+ .with_is_vulnerable ()
461+ )
462+
463+ packages = query
464+
465+ # Collect vulnerabilities associated with these packages
466+ vulnerabilities = set ()
467+ for package in packages :
468+ vulnerabilities .update (package .affected_by_vulnerabilities .all ())
469+ vulnerabilities .update (package .fixing_vulnerabilities .all ())
470+
471+ vulnerability_data = {
472+ vuln .vulnerability_id : VulnerabilityV2Serializer (vuln ).data
473+ for vuln in vulnerabilities
474+ }
475+
476+ if not purl_only :
477+ package_data = PackageV2Serializer (
478+ packages , many = True , context = {"request" : request }
479+ ).data
480+ return Response (
481+ {
482+ "vulnerabilities" : vulnerability_data ,
483+ "packages" : package_data ,
484+ }
485+ )
486+
487+ # Using order by and distinct because there will be
488+ # many fully qualified purl for a single plain purl
489+ vulnerable_purls = query .vulnerable ().only ("plain_package_url" )
490+ vulnerable_purls = [str (package .plain_package_url ) for package in vulnerable_purls ]
491+ return Response (data = vulnerable_purls )
492+
493+ query = Package .objects .filter (package_url__in = purls ).distinct ().with_is_vulnerable ()
494+ packages = query
495+
496+ # Collect vulnerabilities associated with these packages
497+ vulnerabilities = set ()
498+ for package in packages :
499+ vulnerabilities .update (package .affected_by_vulnerabilities .all ())
500+ vulnerabilities .update (package .fixing_vulnerabilities .all ())
501+
502+ vulnerability_data = {
503+ vuln .vulnerability_id : VulnerabilityV2Serializer (vuln ).data for vuln in vulnerabilities
504+ }
505+
506+ if not purl_only :
507+ package_data = PackageV2Serializer (
508+ packages , many = True , context = {"request" : request }
509+ ).data
510+ return Response (
511+ {
512+ "vulnerabilities" : vulnerability_data ,
513+ "packages" : package_data ,
514+ }
515+ )
516+
517+ vulnerable_purls = query .vulnerable ().only ("package_url" )
518+ vulnerable_purls = [str (package .package_url ) for package in vulnerable_purls ]
519+ return Response (data = vulnerable_purls )
0 commit comments