@@ -56,11 +56,15 @@ class VulnSerializerRefsAndSummary(serializers.HyperlinkedModelSerializer):
5656 Used for nesting inside package focused APIs.
5757 """
5858
59+ fixed_packages = MinimalPackageSerializer (
60+ many = True , source = "filtered_fixed_packages" , read_only = True
61+ )
62+
5963 references = VulnerabilityReferenceSerializer (many = True , source = "vulnerabilityreference_set" )
6064
6165 class Meta :
6266 model = Vulnerability
63- fields = ["url" , "vulnerability_id" , "summary" , "references" ]
67+ fields = ["url" , "vulnerability_id" , "summary" , "references" , "fixed_packages" ]
6468
6569
6670class MinimalVulnerabilitySerializer (serializers .HyperlinkedModelSerializer ):
@@ -73,21 +77,6 @@ class Meta:
7377 fields = ["url" , "vulnerability_id" ]
7478
7579
76- class PackageSerializerFixedVulns (serializers .HyperlinkedModelSerializer ):
77- """
78- Used for nesting inside vulnerability focused APIs.
79- """
80-
81- purl = serializers .CharField (source = "package_url" )
82- fixing_vulnerabilities = MinimalVulnerabilitySerializer (
83- many = True , source = "resolved_to" , read_only = True
84- )
85-
86- class Meta :
87- model = Package
88- fields = ["url" , "purl" , "fixing_vulnerabilities" ]
89-
90-
9180class AliasSerializer (serializers .HyperlinkedModelSerializer ):
9281 """
9382 Used for nesting inside package focused APIs.
@@ -128,13 +117,57 @@ def to_representation(self, instance):
128117 return data
129118
130119 purl = serializers .CharField (source = "package_url" )
131- affected_by_vulnerabilities = VulnSerializerRefsAndSummary (
132- many = True , source = "vulnerable_to" , read_only = True
133- )
134- fixing_vulnerabilities = VulnSerializerRefsAndSummary (
135- many = True , source = "resolved_to" , read_only = True
136- )
137- fixed_packages = PackageSerializerFixedVulns (many = True , read_only = True )
120+
121+ affected_by_vulnerabilities = serializers .SerializerMethodField ("get_affected_vulnerabilities" )
122+
123+ fixing_vulnerabilities = serializers .SerializerMethodField ("get_fixed_vulnerabilities" )
124+
125+ def get_fixed_packages (self , package ):
126+ """
127+ Return a queryset of all packages that fixes a vulnerability with
128+ same type, namespace, name, subpath and qualifiers of the `package`
129+ """
130+ return Package .objects .filter (
131+ name = package .name ,
132+ namespace = package .namespace ,
133+ type = package .type ,
134+ qualifiers = package .qualifiers ,
135+ subpath = package .subpath ,
136+ packagerelatedvulnerability__fix = True ,
137+ ).distinct ()
138+
139+ def get_vulnerabilities_for_a_package (self , package , fix ):
140+ """
141+ Return a queryset of vulnerabilities related to the given `package`.
142+ Return vulnerabilities that affects the `package` if given `fix` flag is False,
143+ otherwise return vulnerabilities fixed by the `package`.
144+ """
145+ fixed_packages = self .get_fixed_packages (package = package )
146+ qs = package .vulnerabilities .filter (packagerelatedvulnerability__fix = fix )
147+ qs = qs .prefetch_related (
148+ Prefetch (
149+ "packages" ,
150+ queryset = fixed_packages ,
151+ to_attr = "filtered_fixed_packages" ,
152+ )
153+ )
154+ return VulnSerializerRefsAndSummary (
155+ instance = qs ,
156+ many = True ,
157+ context = {"request" : self .context ["request" ]},
158+ ).data
159+
160+ def get_fixed_vulnerabilities (self , package ):
161+ """
162+ Return a queryset of vulnerabilities fixed in the given `package`.
163+ """
164+ return self .get_vulnerabilities_for_a_package (package = package , fix = True )
165+
166+ def get_affected_vulnerabilities (self , package ):
167+ """
168+ Return a queryset of vulnerabilities that affects the given `package`.
169+ """
170+ return self .get_vulnerabilities_for_a_package (package = package , fix = False )
138171
139172 class Meta :
140173 model = Package
@@ -148,7 +181,6 @@ class Meta:
148181 "qualifiers" ,
149182 "subpath" ,
150183 "affected_by_vulnerabilities" ,
151- "fixed_packages" ,
152184 "fixing_vulnerabilities" ,
153185 ]
154186
@@ -177,7 +209,6 @@ def filter_purl(self, queryset, name, value):
177209class PackageViewSet (viewsets .ReadOnlyModelViewSet ):
178210 queryset = Package .objects .all ()
179211 serializer_class = PackageSerializer
180- paginate_by = 50
181212 filter_backends = (filters .DjangoFilterBackend ,)
182213 filterset_class = PackageFilterSet
183214
@@ -252,7 +283,6 @@ def get_queryset(self):
252283 )
253284
254285 serializer_class = VulnerabilitySerializer
255- paginate_by = 50
256286 filter_backends = (filters .DjangoFilterBackend ,)
257287 filterset_class = VulnerabilityFilterSet
258288
@@ -270,10 +300,33 @@ class CPEViewSet(viewsets.ReadOnlyModelViewSet):
270300 vulnerabilityreference__reference_id__startswith = "cpe"
271301 ).distinct ()
272302 serializer_class = VulnerabilitySerializer
273- paginate_by = 50
274303 filter_backends = (filters .DjangoFilterBackend ,)
275304 filterset_class = CPEFilterSet
276305
306+ @action (detail = False , methods = ["post" ])
307+ def bulk_search (self , request ):
308+ """
309+ This endpoint is used to search for vulnerabilities by more than one CPE.
310+ """
311+ response = []
312+ cpes = request .data .get ("cpes" , []) or []
313+ if not cpes or not isinstance (cpes , list ):
314+ return Response (
315+ status = 400 ,
316+ data = {"Error" : "A non-empty 'cpe' list of package URLs is required." },
317+ )
318+ for cpe in cpes :
319+ if not cpe .startswith ("cpe" ):
320+ return Response (status = 400 , data = {"Error" : f"Invalid CPE: { cpe } " })
321+ vulnerabilitiesResponse = Vulnerability .objects .filter (
322+ vulnerabilityreference__reference_id__in = cpes
323+ ).distinct ()
324+ return Response (
325+ VulnerabilitySerializer (
326+ vulnerabilitiesResponse , many = True , context = {"request" : request }
327+ ).data
328+ )
329+
277330
278331class AliasFilterSet (filters .FilterSet ):
279332 alias = filters .CharFilter (method = "filter_alias" )
@@ -286,6 +339,5 @@ def filter_alias(self, queryset, name, value):
286339class AliasViewSet (viewsets .ReadOnlyModelViewSet ):
287340 queryset = Vulnerability .objects .all ()
288341 serializer_class = VulnerabilitySerializer
289- paginate_by = 50
290342 filter_backends = (filters .DjangoFilterBackend ,)
291343 filterset_class = AliasFilterSet
0 commit comments