From 9c5dee84bef31d6446bbe9b69efd682a8d99d42b Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Thu, 23 Jan 2025 19:51:34 +0530 Subject: [PATCH] Add advisories API in V2 Signed-off-by: Tushar Goel --- vulnerabilities/api_v2.py | 22 +++++++++++ vulnerabilities/tests/test_api_v2.py | 59 ++++++++++++++++++++++++++++ vulnerablecode/urls.py | 2 + 3 files changed, 83 insertions(+) diff --git a/vulnerabilities/api_v2.py b/vulnerabilities/api_v2.py index 10ffb6d98..7d7782199 100644 --- a/vulnerabilities/api_v2.py +++ b/vulnerabilities/api_v2.py @@ -21,6 +21,7 @@ from rest_framework.response import Response from rest_framework.reverse import reverse +from vulnerabilities.models import Advisory from vulnerabilities.models import CodeFix from vulnerabilities.models import Package from vulnerabilities.models import Vulnerability @@ -606,3 +607,24 @@ def get_queryset(self): affected_package_vulnerability__vulnerability__vulnerability_id=vulnerability_id ) return queryset + + +class AdvisorySerializer(serializers.ModelSerializer): + class Meta: + model = Advisory + fields = ["aliases", "summary", "affected_packages", "references", "date_published", "url"] + + +class AdvisoryViewSet(viewsets.ReadOnlyModelViewSet): + serializer_class = AdvisorySerializer + + def get_queryset(self): + return Advisory.objects.only( + "aliases", "summary", "affected_packages", "references", "date_published", "url" + ).order_by("-date_published") + + def list(self, request, *args, **kwargs): + queryset = self.get_queryset() + page = self.paginate_queryset(queryset) + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) diff --git a/vulnerabilities/tests/test_api_v2.py b/vulnerabilities/tests/test_api_v2.py index e3434c6a9..14e08fe88 100644 --- a/vulnerabilities/tests/test_api_v2.py +++ b/vulnerabilities/tests/test_api_v2.py @@ -8,7 +8,9 @@ # from django.db.models import Prefetch +from django.test import TestCase from django.urls import reverse +from django.utils import timezone from packageurl import PackageURL from rest_framework import status from rest_framework.test import APIClient @@ -16,6 +18,7 @@ from vulnerabilities.api_v2 import PackageV2Serializer from vulnerabilities.api_v2 import VulnerabilityListSerializer +from vulnerabilities.models import Advisory from vulnerabilities.models import Alias from vulnerabilities.models import ApiUser from vulnerabilities.models import Package @@ -662,3 +665,59 @@ def test_lookup_with_invalid_purl_format(self): self.assertEqual(response.status_code, status.HTTP_200_OK) # No packages or vulnerabilities should be returned self.assertEqual(len(response.data), 0) + + +class AdvisoryAPITest(TestCase): + def setUp(self): + self.user = ApiUser.objects.create_api_user(username="test@test.com") + self.auth = f"Token {self.user.auth_token.key}" + self.client = APIClient(enforce_csrf_checks=True) + self.client.credentials(HTTP_AUTHORIZATION=self.auth) + + self.now = timezone.now() + self.advisories = [] + for i in range(10): + advisory = Advisory.objects.create( + aliases=[f"CVE-2020-{i}"], + summary=f"Test Advisory {i}", + affected_packages=[{"package_url": f"pkg:npm/package{i}@1.0.0"}], + references=[{"url": f"https://example.com/vuln/{i}"}], + date_published=self.now, + date_collected=self.now, + created_by="test_importer", + url=f"https://example.com/{i}", + ) + self.advisories.append(advisory) + + def test_advisory_list(self): + with self.assertNumQueries(5): # save + auth + count + data + release + response = self.client.get("/api/v2/advisories/", format="json") + self.assertEqual(200, response.status_code) + data = response.json() + self.assertEqual(10, data["count"]) + self.assertEqual(10, len(data["results"])) + + first_result = data["results"][0] + expected_fields = { + "aliases", + "summary", + "affected_packages", + "references", + "date_published", + "url", + } + self.assertEqual(expected_fields, set(first_result.keys())) + + def test_advisory_pagination(self): + with self.assertNumQueries(5): + response = self.client.get("/api/v2/advisories/?page_size=5", format="json") + self.assertEqual(200, response.status_code) + data = response.json() + self.assertEqual(10, data["count"]) + self.assertEqual(5, len(data["results"])) + self.assertIsNotNone(data["next"]) + self.assertIsNone(data["previous"]) + + def test_advisory_invalid_page(self): + response = self.client.get("/api/v2/advisories/?page=999", format="json") + self.assertEqual(404, response.status_code) diff --git a/vulnerablecode/urls.py b/vulnerablecode/urls.py index 54540a66d..403822c76 100644 --- a/vulnerablecode/urls.py +++ b/vulnerablecode/urls.py @@ -20,6 +20,7 @@ from vulnerabilities.api import CPEViewSet from vulnerabilities.api import PackageViewSet from vulnerabilities.api import VulnerabilityViewSet +from vulnerabilities.api_v2 import AdvisoryViewSet from vulnerabilities.api_v2 import CodeFixViewSet from vulnerabilities.api_v2 import PackageV2ViewSet from vulnerabilities.api_v2 import VulnerabilityV2ViewSet @@ -50,6 +51,7 @@ def __init__(self, *args, **kwargs): api_v2_router.register("packages", PackageV2ViewSet, basename="package-v2") api_v2_router.register("vulnerabilities", VulnerabilityV2ViewSet, basename="vulnerability-v2") api_v2_router.register("codefixes", CodeFixViewSet, basename="codefix") +api_v2_router.register("advisories", AdvisoryViewSet, basename="advisory") urlpatterns = [