Skip to content

Commit f75842e

Browse files
committed
Prepare v38.1.0 for release
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 0142cf1 commit f75842e

File tree

11 files changed

+119
-29
lines changed

11 files changed

+119
-29
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Release notes
22
=============
33

4+
Version v38.1.0
5+
---------------------
6+
7+
- Throttle UI to 15 requests per minute to avoid abuse and improve performance.
8+
- Handle errors in unfurl_version_range pipeline.
9+
- Remove Todo pipeline from v1 pipelines.
10+
- Add openAPI documentation for Package and Advisory viewset.
11+
412
Version v38.0.0
513
---------------------
614

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = vulnerablecode
3-
version = 38.0.0
3+
version = 38.1.0
44
license = Apache-2.0 AND CC-BY-SA-4.0
55

66
# description must be on ONE line https://github.com/pypa/setuptools/issues/1390

vulnerabilities/api_v3.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from django.db.models import OuterRef
1515
from django.db.models import Prefetch
1616
from django_filters import rest_framework as filters
17+
from drf_spectacular.utils import extend_schema
1718
from packageurl import PackageURL
1819
from rest_framework import serializers
1920
from rest_framework import viewsets
@@ -422,6 +423,7 @@ class PackageV3ViewSet(viewsets.GenericViewSet):
422423
filter_backends = [filters.DjangoFilterBackend]
423424
throttle_classes = [AnonRateThrottle, PermissionBasedUserRateThrottle]
424425

426+
@extend_schema(request=PackageQuerySerializer)
425427
def create(self, request, *args, **kwargs):
426428
serializer = PackageQuerySerializer(data=request.data)
427429
serializer.is_valid(raise_exception=True)
@@ -528,8 +530,9 @@ class AdvisoryV3ViewSet(viewsets.GenericViewSet):
528530
filter_backends = [filters.DjangoFilterBackend]
529531
throttle_classes = [AnonRateThrottle, PermissionBasedUserRateThrottle]
530532

533+
@extend_schema(request=AdvisoryQuerySerializer)
531534
def create(self, request, *args, **kwargs):
532-
serializer = PackageQuerySerializer(data=request.data)
535+
serializer = AdvisoryQuerySerializer(data=request.data)
533536
serializer.is_valid(raise_exception=True)
534537

535538
purls = serializer.validated_data["purls"]

vulnerabilities/importers/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from vulnerabilities.importers import github_osv
2222
from vulnerabilities.importers import istio
2323
from vulnerabilities.importers import mozilla
24-
from vulnerabilities.importers import openssl
2524
from vulnerabilities.importers import oss_fuzz
2625
from vulnerabilities.importers import postgresql
2726
from vulnerabilities.importers import project_kb_msr2019
@@ -38,7 +37,6 @@
3837
from vulnerabilities.pipelines import gitlab_importer
3938
from vulnerabilities.pipelines import nginx_importer
4039
from vulnerabilities.pipelines import npm_importer
41-
from vulnerabilities.pipelines import nvd_importer
4240
from vulnerabilities.pipelines import pypa_importer
4341
from vulnerabilities.pipelines import pysec_importer
4442
from vulnerabilities.pipelines.v2_importers import alpine_linux_importer as alpine_linux_importer_v2
@@ -118,7 +116,6 @@
118116
retiredotnet_importer_v2.RetireDotnetImporterPipeline,
119117
ubuntu_osv_importer_v2.UbuntuOSVImporterPipeline,
120118
alpine_linux_importer_v2.AlpineLinuxImporterPipeline,
121-
nvd_importer.NVDImporterPipeline,
122119
github_importer.GitHubAPIImporterPipeline,
123120
gitlab_importer.GitLabImporterPipeline,
124121
github_osv.GithubOSVImporter,
@@ -136,7 +133,6 @@
136133
alpine_linux_importer.AlpineLinuxImporterPipeline,
137134
ruby.RubyImporter,
138135
apache_kafka.ApacheKafkaImporter,
139-
openssl.OpensslImporter,
140136
openssl_importer_v2.OpenSSLImporterPipeline,
141137
redhat.RedhatImporter,
142138
archlinux.ArchlinuxImporter,

vulnerabilities/improvers/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from vulnerabilities.improvers import valid_versions
1111
from vulnerabilities.improvers import vulnerability_status
1212
from vulnerabilities.pipelines import add_cvss31_to_CVEs
13-
from vulnerabilities.pipelines import compute_advisory_todo
1413
from vulnerabilities.pipelines import compute_package_risk
1514
from vulnerabilities.pipelines import compute_package_version_rank
1615
from vulnerabilities.pipelines import enhance_with_exploitdb
@@ -70,7 +69,6 @@
7069
compute_package_risk_v2.ComputePackageRiskPipeline,
7170
compute_version_rank_v2.ComputeVersionRankPipeline,
7271
unfurl_version_range_v2.UnfurlVersionRangePipeline,
73-
compute_advisory_todo.ComputeToDo,
7472
collect_ssvc_trees.CollectSSVCPipeline,
7573
relate_severities.RelateSeveritiesPipeline,
7674
group_advisories_for_packages.GroupAdvisoriesForPackages,

vulnerabilities/pipelines/v2_improvers/unfurl_version_range.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def unfurl_version_range(self):
5252
if purl.type not in RANGE_CLASS_BY_SCHEMES:
5353
continue
5454

55-
versions = get_purl_versions(purl, cached_versions)
55+
versions = get_purl_versions(purl, cached_versions) or []
5656
affected_purls = get_affected_purls(
5757
versions=versions,
5858
affecting_vers=impact.affecting_vers,
@@ -79,6 +79,8 @@ def get_affected_purls(versions, affecting_vers, base_purl, logger):
7979
version_class = affecting_version_range.version_class
8080

8181
try:
82+
if not versions:
83+
return []
8284
versions = [version_class(v) for v in versions]
8385
except Exception as e:
8486
logger(
@@ -107,8 +109,10 @@ def get_affected_purls(versions, affecting_vers, base_purl, logger):
107109

108110
def get_purl_versions(purl, cached_versions):
109111
if not purl in cached_versions:
110-
cached_versions[purl] = get_versions(purl)
111-
return cached_versions[purl]
112+
purls = get_versions(purl)
113+
if purls is not None:
114+
cached_versions[purl] = purls
115+
return cached_versions.get(purl) or []
112116

113117

114118
def bulk_create_with_m2m(purls, impact, relation, logger):

vulnerabilities/tests/test_view.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
import time
1212

1313
import pytest
14+
from django.core.cache import cache
1415
from django.test import Client
1516
from django.test import TestCase
17+
from django.urls import reverse
1618
from packageurl import PackageURL
1719
from univers import versions
1820

@@ -330,3 +332,25 @@ def test_aggregate_fixed_and_affected_packages(self):
330332
end_time = time.time()
331333
assert end_time - start_time < 0.05
332334
self.assertEqual(response.status_code, 200)
335+
336+
337+
class ThrottleTestCase(TestCase):
338+
def setUp(self):
339+
self.client = Client()
340+
cache.clear()
341+
342+
def test_throttle_after_15_requests(self):
343+
url = reverse("home")
344+
345+
responses = []
346+
347+
for i in range(16):
348+
response = self.client.get(
349+
url,
350+
HTTP_USER_AGENT="test-agent",
351+
)
352+
responses.append(response.status_code)
353+
354+
assert all(code == 200 for code in responses[:15])
355+
356+
assert responses[15] == 429

vulnerabilities/throttling.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ def get_throttle_rate(self, tier):
5151
raise ImproperlyConfigured(msg)
5252

5353

54+
class AnonUserUIThrottle(UserRateThrottle):
55+
scope = "ui"
56+
57+
def allow_request(self, request, view):
58+
self.rate = self.THROTTLE_RATES.get("ui")
59+
self.num_requests, self.duration = self.parse_rate(self.rate)
60+
return super().allow_request(request, view)
61+
62+
def get_cache_key(self, request, view):
63+
ident = self.get_ident(request)
64+
return f"throttle_ui_{ident}"
65+
66+
5467
def throttled_exception_handler(exception, context):
5568
"""
5669
Return this response whenever a request has been throttled

vulnerabilities/views.py

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from django.db.models import Exists
2121
from django.db.models import OuterRef
2222
from django.db.models import Prefetch
23+
from django.http import HttpResponse
2324
from django.http.response import Http404
2425
from django.shortcuts import get_object_or_404
2526
from django.shortcuts import redirect
@@ -47,6 +48,7 @@
4748
from vulnerabilities.pipelines.v2_importers.epss_importer_v2 import EPSSImporterPipeline
4849
from vulnerabilities.severity_systems import EPSS
4950
from vulnerabilities.severity_systems import SCORING_SYSTEMS
51+
from vulnerabilities.throttling import AnonUserUIThrottle
5052
from vulnerabilities.utils import TYPES_WITH_MULTIPLE_IMPORTERS
5153
from vulnerabilities.utils import get_advisories_from_groups
5254
from vulnerabilities.utils import merge_and_save_grouped_advisories
@@ -56,7 +58,47 @@
5658
PAGE_SIZE = 10
5759

5860

59-
class PackageSearch(ListView):
61+
class VulnerableCodeView(View):
62+
"""
63+
Base ListView for VulnerableCode views that includes throttling.
64+
"""
65+
66+
throttle_classes = [AnonUserUIThrottle]
67+
68+
def dispatch(self, request, *args, **kwargs):
69+
throttle = AnonUserUIThrottle()
70+
71+
if not throttle.allow_request(request, self):
72+
return HttpResponse("Rate limit exceeded", status=429)
73+
74+
return super().dispatch(request, *args, **kwargs)
75+
76+
77+
class VulnerableCodeDetailView(DetailView, VulnerableCodeView):
78+
"""
79+
Base DetailView for VulnerableCode views that includes throttling.
80+
"""
81+
82+
pass
83+
84+
85+
class VulnerableCodeListView(ListView, VulnerableCodeView):
86+
"""
87+
Base ListView for VulnerableCode views that includes throttling.
88+
"""
89+
90+
pass
91+
92+
93+
class VulnerableCodeCreateView(generic.CreateView, VulnerableCodeView):
94+
"""
95+
Base CreateView for VulnerableCode views that includes throttling.
96+
"""
97+
98+
pass
99+
100+
101+
class PackageSearch(VulnerableCodeListView):
60102
model = models.Package
61103
template_name = "packages.html"
62104
ordering = ["type", "namespace", "name", "version"]
@@ -84,7 +126,7 @@ def get_queryset(self, query=None):
84126
)
85127

86128

87-
class VulnerabilitySearch(ListView):
129+
class VulnerabilitySearch(VulnerableCodeListView):
88130
model = models.Vulnerability
89131
template_name = "vulnerabilities.html"
90132
ordering = ["vulnerability_id"]
@@ -102,7 +144,7 @@ def get_queryset(self, query=None):
102144
return self.model.objects.search(query=query).with_package_counts()
103145

104146

105-
class PackageDetails(DetailView):
147+
class PackageDetails(VulnerableCodeDetailView):
106148
model = models.Package
107149
template_name = "package_details.html"
108150
slug_url_kwarg = "purl"
@@ -143,7 +185,7 @@ def get_object(self, queryset=None):
143185
return package
144186

145187

146-
class PackageSearchV2(ListView):
188+
class PackageSearchV2(VulnerableCodeListView):
147189
model = models.PackageV2
148190
template_name = "packages_v2.html"
149191
ordering = ["type", "namespace", "name", "version"]
@@ -166,7 +208,7 @@ def get_queryset(self, query=None):
166208
return self.model.objects.search(query).prefetch_related().with_is_vulnerable()
167209

168210

169-
class AffectedByAdvisoriesListView(ListView):
211+
class AffectedByAdvisoriesListView(VulnerableCodeListView):
170212
model = models.AdvisoryV2
171213
template_name = "affected_by_advisories.html"
172214
paginate_by = PAGE_SIZE
@@ -187,7 +229,7 @@ def get_queryset(self):
187229
)
188230

189231

190-
class FixingAdvisoriesListView(ListView):
232+
class FixingAdvisoriesListView(VulnerableCodeListView):
191233
model = models.AdvisoryV2
192234
template_name = "fixing_advisories.html"
193235
paginate_by = PAGE_SIZE
@@ -201,7 +243,7 @@ def get_queryset(self):
201243
)
202244

203245

204-
class PackageV2Details(DetailView):
246+
class PackageV2Details(VulnerableCodeDetailView):
205247
model = models.PackageV2
206248
template_name = "package_details_v2.html"
207249
slug_url_kwarg = "purl"
@@ -439,7 +481,7 @@ def get_fixed_package_details(package):
439481
return fixed_pkg_details
440482

441483

442-
class VulnerabilityDetails(DetailView):
484+
class VulnerabilityDetails(VulnerableCodeDetailView):
443485
model = models.Vulnerability
444486
template_name = "vulnerability_details.html"
445487
slug_url_kwarg = "vulnerability_id"
@@ -543,7 +585,7 @@ def get_context_data(self, **kwargs):
543585
return context
544586

545587

546-
class AdvisoryDetails(DetailView):
588+
class AdvisoryDetails(VulnerableCodeDetailView):
547589
model = models.AdvisoryV2
548590
template_name = "advisory_detail.html"
549591
slug_url_kwarg = "avid"
@@ -717,7 +759,7 @@ def add_ssvc(ssvc):
717759
return context
718760

719761

720-
class HomePage(View):
762+
class HomePage(VulnerableCodeView):
721763
template_name = "index.html"
722764

723765
def get(self, request):
@@ -730,7 +772,7 @@ def get(self, request):
730772
return render(request=request, template_name=self.template_name, context=context)
731773

732774

733-
class HomePageV2(View):
775+
class HomePageV2(VulnerableCodeView):
734776
template_name = "index_v2.html"
735777

736778
def get(self, request):
@@ -770,7 +812,7 @@ def get(self, request):
770812
"""
771813

772814

773-
class ApiUserCreateView(generic.CreateView):
815+
class ApiUserCreateView(VulnerableCodeCreateView):
774816
model = models.ApiUser
775817
form_class = ApiUserCreationForm
776818
template_name = "api_user_creation_form.html"
@@ -800,7 +842,7 @@ def get_success_url(self):
800842
return reverse_lazy("api_user_request")
801843

802844

803-
class VulnerabilityPackagesDetails(DetailView):
845+
class VulnerabilityPackagesDetails(VulnerableCodeDetailView):
804846
"""
805847
View to display all packages affected by or fixing a specific vulnerability.
806848
URL: /vulnerabilities/{vulnerability_id}/packages
@@ -851,7 +893,7 @@ def get_context_data(self, **kwargs):
851893
return context
852894

853895

854-
class AdvisoryPackagesDetails(DetailView):
896+
class AdvisoryPackagesDetails(VulnerableCodeDetailView):
855897
"""
856898
View to display all packages affected by or fixing a specific vulnerability.
857899
URL: /advisories/{id}/packages
@@ -902,7 +944,7 @@ def get_queryset(self):
902944
)
903945

904946

905-
class PipelineScheduleListView(ListView, FormMixin):
947+
class PipelineScheduleListView(VulnerableCodeListView, FormMixin):
906948
model = PipelineSchedule
907949
context_object_name = "schedule_list"
908950
template_name = "pipeline_dashboard.html"
@@ -926,7 +968,7 @@ def get_context_data(self, **kwargs):
926968
return context
927969

928970

929-
class PipelineRunListView(ListView):
971+
class PipelineRunListView(VulnerableCodeListView):
930972
model = PipelineRun
931973
context_object_name = "run_list"
932974
template_name = "pipeline_run_list.html"
@@ -952,7 +994,7 @@ def get_context_data(self, **kwargs):
952994
return context
953995

954996

955-
class PipelineRunDetailView(DetailView):
997+
class PipelineRunDetailView(VulnerableCodeDetailView):
956998
model = PipelineRun
957999
template_name = "pipeline_run_details.html"
9581000
context_object_name = "run"

0 commit comments

Comments
 (0)