Skip to content

Commit a22984a

Browse files
committed
💡 Add swagger docs for the API
Signed-off-by: Shivam Sandbhor <shivam.sandbhor@gmail.com>
1 parent b0701eb commit a22984a

6 files changed

Lines changed: 47 additions & 33 deletions

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dj-database-url==0.4.2
1212
Django==3.0.7
1313
django-filter==2.2.0
1414
djangorestframework==3.11.0
15+
drf-yasg==1.17.1
1516
gunicorn==19.7.1
1617
importlib-metadata==1.3.0
1718
ipython==7.13.0

vulnerabilities/api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class Meta:
6060
fields = ["url", "vulnerability_id"]
6161

6262

63-
class VulnerabilitySerializer(serializers.ModelSerializer):
63+
class VulnerabilitySerializer(serializers.HyperlinkedModelSerializer):
6464
references = VulnerabilityReferenceSerializer(many=True, source="vulnerabilityreference_set")
6565
resolved_packages = HyperLinkedPackageSerializer(
6666
many=True, source="resolved_to", read_only=True
@@ -74,7 +74,7 @@ class Meta:
7474
fields = "__all__"
7575

7676

77-
class PackageSerializer(serializers.ModelSerializer):
77+
class PackageSerializer(serializers.HyperlinkedModelSerializer):
7878
unresolved_vulnerabilities = HyperLinkedVulnerabilitySerializer(
7979
many=True, source="vulnerable_to", read_only=True
8080
)
@@ -86,6 +86,7 @@ class PackageSerializer(serializers.ModelSerializer):
8686
class Meta:
8787
model = Package
8888
fields = [
89+
"url",
8990
"type",
9091
"namespace",
9192
"name",

vulnerabilities/models.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,17 @@ class Vulnerability(models.Model):
4646

4747
@property
4848
def vulnerable_to(self):
49-
qs = PackageRelatedVulnerability.objects.filter(
50-
vulnerability_id=self.id, is_vulnerable=True
51-
).select_related("package")
52-
return [rel.package for rel in qs]
49+
return self.package_set.filter(
50+
packagerelatedvulnerability__is_vulnerable=True,
51+
packagerelatedvulnerability__vulnerability_id=self.id,
52+
)
5353

5454
@property
5555
def resolved_to(self):
56-
qs = PackageRelatedVulnerability.objects.filter(
57-
vulnerability_id=self.id, is_vulnerable=False
58-
).select_related("package")
59-
return [rel.package for rel in qs]
56+
return self.package_set.filter(
57+
packagerelatedvulnerability__is_vulnerable=False,
58+
packagerelatedvulnerability__vulnerability_id=self.id,
59+
)
6060

6161
def __str__(self):
6262
return self.cve_id or self.summary
@@ -96,17 +96,17 @@ class Package(PackageURLMixin):
9696

9797
@property
9898
def vulnerable_to(self):
99-
qs = PackageRelatedVulnerability.objects.filter(
100-
package_id=self.id, is_vulnerable=True
101-
).select_related("vulnerability")
102-
return [rel.vulnerability for rel in qs]
99+
return self.vulnerabilities.filter(
100+
packagerelatedvulnerability__is_vulnerable=True,
101+
packagerelatedvulnerability__package_id=self.id,
102+
)
103103

104104
@property
105105
def resolved_to(self):
106-
qs = PackageRelatedVulnerability.objects.filter(
107-
package_id=self.id, is_vulnerable=False
108-
).select_related("vulnerability")
109-
return [rel.vulnerability for rel in qs]
106+
return self.vulnerabilities.filter(
107+
packagerelatedvulnerability__is_vulnerable=False,
108+
packagerelatedvulnerability__package_id=self.id,
109+
)
110110

111111
class Meta:
112112
unique_together = ("name", "namespace", "type", "version", "qualifiers", "subpath")

vulnerabilities/tests/test_api.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,25 @@ def test_query_qualifier_filtering(self):
5757

5858
# check filtering when qualifiers are not normalized
5959
test_purl = quote("pkg:deb/vlc@1.50-1.1?foo=bar&tar=ball")
60-
response = self.client.get(f"/api/packages?purl={test_purl}", format="json").data
60+
response = self.client.get(f"/api/packages/?purl={test_purl}", format="json").data
6161

6262
self.assertEqual(1, response["count"])
6363
self.assertEqual(pk_multi_qf.qualifiers, response["results"][0]["qualifiers"])
6464

6565
test_purl = quote("pkg:deb/vlc@1.50-1.1?tar=ball&foo=bar")
66-
response = self.client.get(f"/api/packages?purl={test_purl}", format="json").data
66+
response = self.client.get(f"/api/packages/?purl={test_purl}", format="json").data
6767

6868
self.assertEqual(1, response["count"])
6969
self.assertEqual(pk_multi_qf.qualifiers, response["results"][0]["qualifiers"])
7070

7171
# check filtering when there is intersection of qualifiers between packages
7272
test_purl = quote("pkg:deb/vlc@1.50-1.1?foo=bar")
73-
response = self.client.get(f"/api/packages?purl={test_purl}", format="json").data
73+
response = self.client.get(f"/api/packages/?purl={test_purl}", format="json").data
7474

7575
self.assertEqual(1, response["count"])
7676

7777
def test_query_by_name(self):
78-
response = self.client.get("/api/packages?name=mimetex", format="json").data
78+
response = self.client.get("/api/packages/?name=mimetex", format="json").data
7979

8080
self.assertEqual(3, response["count"])
8181

@@ -91,7 +91,7 @@ def test_query_by_name(self):
9191
self.assertIn("pkg:deb/debian/mimetex@1.74-1?distro=jessie", purls)
9292

9393
def test_query_by_invalid_package_url(self):
94-
url = "/api/packages?purl=invalid_purl"
94+
url = "/api/packages/?purl=invalid_purl"
9595
response = self.client.get(url, format="json")
9696

9797
self.assertEqual(400, response.status_code)
@@ -100,7 +100,7 @@ def test_query_by_invalid_package_url(self):
100100
self.assertIn("invalid_purl", error)
101101

102102
def test_query_by_package_url(self):
103-
url = "/api/packages?purl=pkg:deb/debian/mimetex@1.50-1.1?distro=jessie"
103+
url = "/api/packages/?purl=pkg:deb/debian/mimetex@1.50-1.1?distro=jessie"
104104
response = self.client.get(url, format="json").data
105105

106106
self.assertEqual(1, response["count"])
@@ -113,7 +113,7 @@ def test_query_by_package_url(self):
113113
self.assertNotIn("1.74-1", versions)
114114

115115
def test_query_by_package_url_without_namespace(self):
116-
url = "/api/packages?purl=pkg:deb/mimetex@1.50-1.1"
116+
url = "/api/packages/?purl=pkg:deb/mimetex@1.50-1.1"
117117
response = self.client.get(url, format="json").data
118118

119119
self.assertEqual(2, response["count"])
@@ -130,7 +130,7 @@ class TestUbuntuResponse(TestCase):
130130
fixtures = ["ubuntu.json"]
131131

132132
def test_ubuntu_response(self):
133-
response = self.client.get("/api/packages?name=automake", format="json")
133+
response = self.client.get("/api/packages/?name=automake", format="json")
134134

135135
result = response.data.get("results")[0]
136136
self.assertEqual("automake", result["name"])
@@ -144,7 +144,7 @@ def test_vulnerability_package_relations(self):
144144
test_pkgs = choices(Package.objects.all(), k=5)
145145
for test_pkg in test_pkgs:
146146

147-
pkg_response = self.client.get(f"/api/packages/{test_pkg.id}", format="json").data
147+
pkg_response = self.client.get(f"/api/packages/{test_pkg.id}/", format="json").data
148148
resolved_vulns = {
149149
vuln["vulnerability_id"] for vuln in pkg_response["resolved_vulnerabilities"]
150150
}
@@ -154,7 +154,7 @@ def test_vulnerability_package_relations(self):
154154

155155
for vuln in resolved_vulns:
156156
vuln_resp = self.client.get(
157-
f"/api/vulnerabilities?vulnerability_id={vuln}", format="json"
157+
f"/api/vulnerabilities/?vulnerability_id={vuln}", format="json"
158158
).data
159159
resolved_purls = {
160160
package["purl"] for package in vuln_resp["results"][0]["resolved_packages"]
@@ -163,7 +163,7 @@ def test_vulnerability_package_relations(self):
163163

164164
for vuln in unresolved_vulns:
165165
vuln_resp = self.client.get(
166-
f"/api/vulnerabilities?vulnerability_id={vuln}", format="json"
166+
f"/api/vulnerabilities/?vulnerability_id={vuln}", format="json"
167167
).data
168168
unresolved_purls = {
169169
package["purl"] for package in vuln_resp["results"][0]["unresolved_packages"]

vulnerablecode/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
'vulnerabilities',
4848
'rest_framework',
4949
'django_filters',
50+
'drf_yasg',
5051
]
5152

5253
MIDDLEWARE = [

vulnerablecode/urls.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,25 @@
2222
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2323

2424
from django.contrib import admin
25-
from django.urls import include, path, re_path
26-
25+
from django.urls import include, path
26+
from rest_framework import permissions
27+
from drf_yasg.views import get_schema_view
28+
from drf_yasg import openapi
2729
from rest_framework.routers import DefaultRouter
2830

2931
from vulnerabilities.api import PackageViewSet
3032
from vulnerabilities.api import VulnerabilityViewSet
3133

34+
schema_view = get_schema_view(
35+
openapi.Info(
36+
title="VulnerableCode API",
37+
default_version="v1"
38+
),
39+
public=True,
40+
permission_classes=(permissions.AllowAny,),
41+
)
3242

33-
api_router = DefaultRouter(trailing_slash=False)
43+
api_router = DefaultRouter()
3444
api_router.register(r"packages", PackageViewSet)
3545
# `DefaultRouter` requires `basename` when registering viewsets which don't
3646
# define a queryset.
@@ -39,5 +49,6 @@
3949

4050
urlpatterns = [
4151
path("admin/", admin.site.urls),
42-
re_path(r"^api/", include(api_router.urls)),
52+
path(r"api/", include(api_router.urls)),
53+
path(r'swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
4354
]

0 commit comments

Comments
 (0)