From f1b53d3296c40cde2e3cc8ced76bbecbf0e66f59 Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Fri, 4 Nov 2022 01:26:57 +0530 Subject: [PATCH 1/3] Override throttle rate for each endpoint Signed-off-by: Tushar Goel --- vulnerabilities/api.py | 27 ++++- vulnerabilities/tests/test_throttling.py | 128 ++++++++++++++++++++++- vulnerabilities/throttling.py | 43 +++++++- vulnerablecode/settings.py | 26 ++++- 4 files changed, 215 insertions(+), 9 deletions(-) diff --git a/vulnerabilities/api.py b/vulnerabilities/api.py index 5c949cda4..4ad899407 100644 --- a/vulnerabilities/api.py +++ b/vulnerabilities/api.py @@ -23,6 +23,13 @@ from vulnerabilities.models import VulnerabilityReference from vulnerabilities.models import VulnerabilitySeverity from vulnerabilities.models import get_purl_query_lookups +from vulnerabilities.throttling import AliasesAPIThrottle +from vulnerabilities.throttling import BulkSearchCPEAPIThrottle +from vulnerabilities.throttling import BulkSearchPackagesAPIThrottle +from vulnerabilities.throttling import CPEAPIThrottle +from vulnerabilities.throttling import PackagesAPIThrottle +from vulnerabilities.throttling import VulnerabilitiesAPIThrottle +from vulnerabilities.throttling import VulnerablePackagesAPIThrottle class VulnerabilitySeveritySerializer(serializers.ModelSerializer): @@ -221,6 +228,15 @@ class PackageViewSet(viewsets.ReadOnlyModelViewSet): filter_backends = (filters.DjangoFilterBackend,) filterset_class = PackageFilterSet + def get_throttles(self): + if self.action == "bulk_search": + throttle_classes = [BulkSearchPackagesAPIThrottle] + elif self.action == "all": + throttle_classes = [VulnerablePackagesAPIThrottle] + else: + throttle_classes = [PackagesAPIThrottle] + return [throttle() for throttle in throttle_classes] + # TODO: Fix the swagger documentation for this endpoint @action(detail=False, methods=["post"]) def bulk_search(self, request): @@ -246,7 +262,7 @@ def bulk_search(self, request): if purl_data: purl_response = PackageSerializer(purl_data[0], context={"request": request}).data else: - purl_response = purl + purl_response = purl.to_dict() purl_response["unresolved_vulnerabilities"] = [] purl_response["resolved_vulnerabilities"] = [] purl_response["purl"] = purl_string @@ -302,6 +318,7 @@ def get_queryset(self): serializer_class = VulnerabilitySerializer filter_backends = (filters.DjangoFilterBackend,) filterset_class = VulnerabilityFilterSet + throttle_classes = [VulnerabilitiesAPIThrottle] class CPEFilterSet(filters.FilterSet): @@ -320,6 +337,13 @@ class CPEViewSet(viewsets.ReadOnlyModelViewSet): filter_backends = (filters.DjangoFilterBackend,) filterset_class = CPEFilterSet + def get_throttles(self): + if self.action == "bulk_search": + throttle_classes = [BulkSearchCPEAPIThrottle] + else: + throttle_classes = [CPEAPIThrottle] + return [throttle() for throttle in throttle_classes] + @action(detail=False, methods=["post"]) def bulk_search(self, request): """ @@ -357,3 +381,4 @@ class AliasViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = VulnerabilitySerializer filter_backends = (filters.DjangoFilterBackend,) filterset_class = AliasFilterSet + throttle_classes = [AliasesAPIThrottle] diff --git a/vulnerabilities/tests/test_throttling.py b/vulnerabilities/tests/test_throttling.py index ade4726ef..632ba97d3 100644 --- a/vulnerabilities/tests/test_throttling.py +++ b/vulnerabilities/tests/test_throttling.py @@ -7,6 +7,8 @@ # See https://aboutcode.org for more information about nexB OSS projects. # +import json + from django.contrib.auth import get_user_model from rest_framework.test import APIClient from rest_framework.test import APITestCase @@ -30,10 +32,10 @@ def setUp(self): self.staff_csrf_client = APIClient(enforce_csrf_checks=True) self.staff_csrf_client.credentials(HTTP_AUTHORIZATION=self.staff_auth) - def test_api_throttling(self): + def test_packages_endpoint_throttling(self): - # A basic user can only access API 5 times a day - for i in range(0, 5): + # A basic user can only access /packages endpoint 10 times a day + for i in range(0, 10): response = self.csrf_client.get("/api/packages") self.assertEqual(response.status_code, 200) response = self.staff_csrf_client.get("/api/packages") @@ -46,3 +48,123 @@ def test_api_throttling(self): response = self.staff_csrf_client.get("/api/packages", format="json") # 200 - staff user can access API unlimited times self.assertEqual(response.status_code, 200) + + def test_cpes_endpoint_throttling(self): + + # A basic user can only access /cpes endpoint 5 times a day + for i in range(0, 4): + response = self.csrf_client.get("/api/cpes") + self.assertEqual(response.status_code, 200) + response = self.staff_csrf_client.get("/api/cpes") + self.assertEqual(response.status_code, 200) + + response = self.csrf_client.get("/api/cpes") + # 429 - too many requests for basic user + self.assertEqual(response.status_code, 429) + + response = self.staff_csrf_client.get("/api/cpes", format="json") + # 200 - staff user can access API unlimited times + self.assertEqual(response.status_code, 200) + + def test_all_vulnerable_packages_endpoint_throttling(self): + + # A basic user can only access /packages/all 1 time a day + for i in range(0, 1): + response = self.csrf_client.get("/api/packages/all") + self.assertEqual(response.status_code, 200) + response = self.staff_csrf_client.get("/api/packages/all") + self.assertEqual(response.status_code, 200) + + response = self.csrf_client.get("/api/packages/all") + # 429 - too many requests for basic user + self.assertEqual(response.status_code, 429) + + response = self.staff_csrf_client.get("/api/packages/all", format="json") + # 200 - staff user can access API unlimited times + self.assertEqual(response.status_code, 200) + + def test_vulnerabilities_endpoint_throttling(self): + + # A basic user can only access /vulnerabilities 10 times a day + for i in range(0, 8): + response = self.csrf_client.get("/api/vulnerabilities") + self.assertEqual(response.status_code, 200) + response = self.staff_csrf_client.get("/api/vulnerabilities") + self.assertEqual(response.status_code, 200) + + response = self.csrf_client.get("/api/vulnerabilities") + # 429 - too many requests for basic user + self.assertEqual(response.status_code, 429) + + response = self.staff_csrf_client.get("/api/vulnerabilities", format="json") + # 200 - staff user can access API unlimited times + self.assertEqual(response.status_code, 200) + + def test_aliases_endpoint_throttling(self): + + # A basic user can only access /alias 2 times a day + for i in range(0, 2): + response = self.csrf_client.get("/api/alias") + self.assertEqual(response.status_code, 200) + response = self.staff_csrf_client.get("/api/alias") + self.assertEqual(response.status_code, 200) + + response = self.csrf_client.get("/api/alias") + # 429 - too many requests for basic user + self.assertEqual(response.status_code, 429) + + response = self.staff_csrf_client.get("/api/alias", format="json") + # 200 - staff user can access API unlimited times + self.assertEqual(response.status_code, 200) + + def test_bulk_search_packages_endpoint_throttling(self): + data = json.dumps({"purls": ["pkg:foo/bar"]}) + + # A basic user can only access /packages/bulk_search 5 times a day + for i in range(0, 6): + response = self.csrf_client.post( + "/api/packages/bulk_search", data=data, content_type="application/json" + ) + self.assertEqual(response.status_code, 200) + response = self.staff_csrf_client.post( + "/api/packages/bulk_search", data=data, content_type="application/json" + ) + self.assertEqual(response.status_code, 200) + + response = self.csrf_client.post( + "/api/packages/bulk_search", data=data, content_type="application/json" + ) + # 429 - too many requests for basic user + self.assertEqual(response.status_code, 429) + + response = self.staff_csrf_client.post( + "/api/packages/bulk_search", data=data, content_type="application/json" + ) + # 200 - staff user can access API unlimited times + self.assertEqual(response.status_code, 200) + + def test_bulk_search_cpes_endpoint_throttling(self): + data = json.dumps({"cpes": ["cpe:foo/bar"]}) + + # A basic user can only access /cpes/bulk_search 5 times a day + for i in range(0, 5): + response = self.csrf_client.post( + "/api/cpes/bulk_search", data=data, content_type="application/json" + ) + self.assertEqual(response.status_code, 200) + response = self.staff_csrf_client.post( + "/api/cpes/bulk_search", data=data, content_type="application/json" + ) + self.assertEqual(response.status_code, 200) + + response = self.csrf_client.post( + "/api/cpes/bulk_search", data=data, content_type="application/json" + ) + # 429 - too many requests for basic user + self.assertEqual(response.status_code, 429) + + response = self.staff_csrf_client.post( + "/api/cpes/bulk_search", data=data, content_type="application/json" + ) + # 200 - staff user can access API unlimited times + self.assertEqual(response.status_code, 200) diff --git a/vulnerabilities/throttling.py b/vulnerabilities/throttling.py index e98db3806..ff5017711 100644 --- a/vulnerabilities/throttling.py +++ b/vulnerabilities/throttling.py @@ -8,12 +8,12 @@ # from django.contrib.auth import get_user_model -from rest_framework.throttling import UserRateThrottle +from rest_framework.throttling import SimpleRateThrottle User = get_user_model() -class StaffUserRateThrottle(UserRateThrottle): +class StaffUserRateThrottle(SimpleRateThrottle): def allow_request(self, request, view): """ Do not apply throttling for superusers and admins. @@ -22,3 +22,42 @@ def allow_request(self, request, view): return True return super().allow_request(request, view) + + def get_cache_key(self, request, view): + """ + Return the cache key to use for this request. + """ + if request.user.is_authenticated: + ident = request.user.pk + else: + ident = self.get_ident(request) + + return self.cache_format % {"scope": self.scope, "ident": ident} + + +class VulnerablePackagesAPIThrottle(StaffUserRateThrottle): + scope = "vulnerable_packages" + + +class BulkSearchPackagesAPIThrottle(StaffUserRateThrottle): + scope = "bulk_search_packages" + + +class PackagesAPIThrottle(StaffUserRateThrottle): + scope = "packages" + + +class VulnerabilitiesAPIThrottle(StaffUserRateThrottle): + scope = "vulnerabilities" + + +class AliasesAPIThrottle(StaffUserRateThrottle): + scope = "aliases" + + +class CPEAPIThrottle(StaffUserRateThrottle): + scope = "cpes" + + +class BulkSearchCPEAPIThrottle(StaffUserRateThrottle): + scope = "bulk_search_cpes" diff --git a/vulnerablecode/settings.py b/vulnerablecode/settings.py index 99b52a23a..0652599c7 100644 --- a/vulnerablecode/settings.py +++ b/vulnerablecode/settings.py @@ -150,11 +150,23 @@ LOGIN_REDIRECT_URL = "/" LOGOUT_REDIRECT_URL = "/" -THROTTLING_RATE = env.str("THROTTLING_RATE", default="1000/day") +TEST_PACKAGE_THROTTLING_RATE = None +TEST_BULK_SEARCH_PACKAGE_THROTTLING_RATE = None +TEST_ALL_VULNERABLE_PACKAGE_THROTTLING_RATE = None +TEST_VULNERABILITIES_THROTTLING_RATE = None +TEST_CPES_THROTTLING_RATE = None +TEST_BULK_SEARCH_CPES_THROTTLING_RATE = None +TEST_ALIASES_THROTTLING_RATE = None if IS_TESTS: VULNERABLECODEIO_REQUIRE_AUTHENTICATION = True - THROTTLING_RATE = "5/day" + TEST_PACKAGE_THROTTLING_RATE = "10/day" + TEST_BULK_SEARCH_PACKAGE_THROTTLING_RATE = "6/day" + TEST_ALL_VULNERABLE_PACKAGE_THROTTLING_RATE = "1/day" + TEST_VULNERABILITIES_THROTTLING_RATE = "8/day" + TEST_CPES_THROTTLING_RATE = "4/day" + TEST_BULK_SEARCH_CPES_THROTTLING_RATE = "5/day" + TEST_ALIASES_THROTTLING_RATE = "2/day" USE_L10N = True @@ -190,7 +202,15 @@ "DEFAULT_THROTTLE_CLASSES": [ "vulnerabilities.throttling.StaffUserRateThrottle", ], - "DEFAULT_THROTTLE_RATES": {"user": THROTTLING_RATE}, + "DEFAULT_THROTTLE_RATES": { + "vulnerable_packages": TEST_ALL_VULNERABLE_PACKAGE_THROTTLING_RATE or "1/hour", + "bulk_search_packages": TEST_BULK_SEARCH_PACKAGE_THROTTLING_RATE or "5/hour", + "packages": TEST_PACKAGE_THROTTLING_RATE or "10/minute", + "vulnerabilities": TEST_VULNERABILITIES_THROTTLING_RATE or "10/minute", + "aliases": TEST_ALIASES_THROTTLING_RATE or "5/minute", + "cpes": TEST_CPES_THROTTLING_RATE or "5/minute", + "bulk_search_cpes": TEST_BULK_SEARCH_CPES_THROTTLING_RATE or "5/hour", + }, "DEFAULT_PAGINATION_CLASS": "vulnerabilities.pagination.SmallResultSetPagination", # Limit the load on the Database returning a small number of records by default. https://github.com/nexB/vulnerablecode/issues/819 "PAGE_SIZE": 10, From 8429fc5eeacaf9b59b8e8e83e26a6fd6135a3f1e Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Fri, 4 Nov 2022 01:30:58 +0530 Subject: [PATCH 2/3] Add CHANGELOG Signed-off-by: Tushar Goel --- CHANGELOG.rst | 3 +++ vulnerabilities/tests/test_throttling.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 814febff3..414cae374 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ Version v30.2.2 - We enabled API throttling for a basic user and for a staff user they can have unlimited access on API. +- We added throttle rate for each API endpoint and it can be + configured from the settings #991 https://github.com/nexB/vulnerablecode/issues/991. + Version v30.2.1 ---------------- diff --git a/vulnerabilities/tests/test_throttling.py b/vulnerabilities/tests/test_throttling.py index 632ba97d3..fe25137a0 100644 --- a/vulnerabilities/tests/test_throttling.py +++ b/vulnerabilities/tests/test_throttling.py @@ -51,7 +51,7 @@ def test_packages_endpoint_throttling(self): def test_cpes_endpoint_throttling(self): - # A basic user can only access /cpes endpoint 5 times a day + # A basic user can only access /cpes endpoint 4 times a day for i in range(0, 4): response = self.csrf_client.get("/api/cpes") self.assertEqual(response.status_code, 200) @@ -85,7 +85,7 @@ def test_all_vulnerable_packages_endpoint_throttling(self): def test_vulnerabilities_endpoint_throttling(self): - # A basic user can only access /vulnerabilities 10 times a day + # A basic user can only access /vulnerabilities 8 times a day for i in range(0, 8): response = self.csrf_client.get("/api/vulnerabilities") self.assertEqual(response.status_code, 200) @@ -120,7 +120,7 @@ def test_aliases_endpoint_throttling(self): def test_bulk_search_packages_endpoint_throttling(self): data = json.dumps({"purls": ["pkg:foo/bar"]}) - # A basic user can only access /packages/bulk_search 5 times a day + # A basic user can only access /packages/bulk_search 6 times a day for i in range(0, 6): response = self.csrf_client.post( "/api/packages/bulk_search", data=data, content_type="application/json" From 2f1cfc57f3266645423ee1b815b764e566f36b35 Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Fri, 4 Nov 2022 17:33:48 +0530 Subject: [PATCH 3/3] Address review comments Signed-off-by: Tushar Goel --- vulnerabilities/api.py | 40 +++++++++-------------------- vulnerabilities/throttling.py | 47 ++--------------------------------- vulnerablecode/settings.py | 43 +++++++++++++++----------------- 3 files changed, 34 insertions(+), 96 deletions(-) diff --git a/vulnerabilities/api.py b/vulnerabilities/api.py index 4ad899407..bb6398399 100644 --- a/vulnerabilities/api.py +++ b/vulnerabilities/api.py @@ -23,13 +23,7 @@ from vulnerabilities.models import VulnerabilityReference from vulnerabilities.models import VulnerabilitySeverity from vulnerabilities.models import get_purl_query_lookups -from vulnerabilities.throttling import AliasesAPIThrottle -from vulnerabilities.throttling import BulkSearchCPEAPIThrottle -from vulnerabilities.throttling import BulkSearchPackagesAPIThrottle -from vulnerabilities.throttling import CPEAPIThrottle -from vulnerabilities.throttling import PackagesAPIThrottle -from vulnerabilities.throttling import VulnerabilitiesAPIThrottle -from vulnerabilities.throttling import VulnerablePackagesAPIThrottle +from vulnerabilities.throttling import StaffUserRateThrottle class VulnerabilitySeveritySerializer(serializers.ModelSerializer): @@ -227,18 +221,11 @@ class PackageViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = PackageSerializer filter_backends = (filters.DjangoFilterBackend,) filterset_class = PackageFilterSet - - def get_throttles(self): - if self.action == "bulk_search": - throttle_classes = [BulkSearchPackagesAPIThrottle] - elif self.action == "all": - throttle_classes = [VulnerablePackagesAPIThrottle] - else: - throttle_classes = [PackagesAPIThrottle] - return [throttle() for throttle in throttle_classes] + throttle_classes = [StaffUserRateThrottle] + throttle_scope = "packages" # TODO: Fix the swagger documentation for this endpoint - @action(detail=False, methods=["post"]) + @action(detail=False, methods=["post"], throttle_scope="bulk_search_packages") def bulk_search(self, request): """ See https://github.com/nexB/vulnerablecode/pull/369#issuecomment-796877606 for docs @@ -270,7 +257,7 @@ def bulk_search(self, request): return Response(response) - @action(detail=False, methods=["get"]) + @action(detail=False, methods=["get"], throttle_scope="vulnerable_packages") def all(self, request): """ Return all the vulnerable Package URLs. @@ -318,7 +305,8 @@ def get_queryset(self): serializer_class = VulnerabilitySerializer filter_backends = (filters.DjangoFilterBackend,) filterset_class = VulnerabilityFilterSet - throttle_classes = [VulnerabilitiesAPIThrottle] + throttle_classes = [StaffUserRateThrottle] + throttle_scope = "vulnerabilities" class CPEFilterSet(filters.FilterSet): @@ -335,16 +323,11 @@ class CPEViewSet(viewsets.ReadOnlyModelViewSet): ).distinct() serializer_class = VulnerabilitySerializer filter_backends = (filters.DjangoFilterBackend,) + throttle_classes = [StaffUserRateThrottle] filterset_class = CPEFilterSet + throttle_scope = "cpes" - def get_throttles(self): - if self.action == "bulk_search": - throttle_classes = [BulkSearchCPEAPIThrottle] - else: - throttle_classes = [CPEAPIThrottle] - return [throttle() for throttle in throttle_classes] - - @action(detail=False, methods=["post"]) + @action(detail=False, methods=["post"], throttle_scope="bulk_search_cpes") def bulk_search(self, request): """ This endpoint is used to search for vulnerabilities by more than one CPE. @@ -381,4 +364,5 @@ class AliasViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = VulnerabilitySerializer filter_backends = (filters.DjangoFilterBackend,) filterset_class = AliasFilterSet - throttle_classes = [AliasesAPIThrottle] + throttle_classes = [StaffUserRateThrottle] + throttle_scope = "aliases" diff --git a/vulnerabilities/throttling.py b/vulnerabilities/throttling.py index ff5017711..12fb23426 100644 --- a/vulnerabilities/throttling.py +++ b/vulnerabilities/throttling.py @@ -6,14 +6,10 @@ # See https://github.com/nexB/vulnerablecode for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # +from rest_framework.throttling import ScopedRateThrottle -from django.contrib.auth import get_user_model -from rest_framework.throttling import SimpleRateThrottle -User = get_user_model() - - -class StaffUserRateThrottle(SimpleRateThrottle): +class StaffUserRateThrottle(ScopedRateThrottle): def allow_request(self, request, view): """ Do not apply throttling for superusers and admins. @@ -22,42 +18,3 @@ def allow_request(self, request, view): return True return super().allow_request(request, view) - - def get_cache_key(self, request, view): - """ - Return the cache key to use for this request. - """ - if request.user.is_authenticated: - ident = request.user.pk - else: - ident = self.get_ident(request) - - return self.cache_format % {"scope": self.scope, "ident": ident} - - -class VulnerablePackagesAPIThrottle(StaffUserRateThrottle): - scope = "vulnerable_packages" - - -class BulkSearchPackagesAPIThrottle(StaffUserRateThrottle): - scope = "bulk_search_packages" - - -class PackagesAPIThrottle(StaffUserRateThrottle): - scope = "packages" - - -class VulnerabilitiesAPIThrottle(StaffUserRateThrottle): - scope = "vulnerabilities" - - -class AliasesAPIThrottle(StaffUserRateThrottle): - scope = "aliases" - - -class CPEAPIThrottle(StaffUserRateThrottle): - scope = "cpes" - - -class BulkSearchCPEAPIThrottle(StaffUserRateThrottle): - scope = "bulk_search_cpes" diff --git a/vulnerablecode/settings.py b/vulnerablecode/settings.py index 0652599c7..60d74d707 100644 --- a/vulnerablecode/settings.py +++ b/vulnerablecode/settings.py @@ -150,23 +150,28 @@ LOGIN_REDIRECT_URL = "/" LOGOUT_REDIRECT_URL = "/" -TEST_PACKAGE_THROTTLING_RATE = None -TEST_BULK_SEARCH_PACKAGE_THROTTLING_RATE = None -TEST_ALL_VULNERABLE_PACKAGE_THROTTLING_RATE = None -TEST_VULNERABILITIES_THROTTLING_RATE = None -TEST_CPES_THROTTLING_RATE = None -TEST_BULK_SEARCH_CPES_THROTTLING_RATE = None -TEST_ALIASES_THROTTLING_RATE = None + +REST_FRAMEWORK_DEFAULT_THROTTLE_RATES = { + "vulnerable_packages": "1/hour", + "bulk_search_packages": "5/hour", + "packages": "10/minute", + "vulnerabilities": "10/minute", + "aliases": "5/minute", + "cpes": "5/minute", + "bulk_search_cpes": "5/hour", +} if IS_TESTS: VULNERABLECODEIO_REQUIRE_AUTHENTICATION = True - TEST_PACKAGE_THROTTLING_RATE = "10/day" - TEST_BULK_SEARCH_PACKAGE_THROTTLING_RATE = "6/day" - TEST_ALL_VULNERABLE_PACKAGE_THROTTLING_RATE = "1/day" - TEST_VULNERABILITIES_THROTTLING_RATE = "8/day" - TEST_CPES_THROTTLING_RATE = "4/day" - TEST_BULK_SEARCH_CPES_THROTTLING_RATE = "5/day" - TEST_ALIASES_THROTTLING_RATE = "2/day" + REST_FRAMEWORK_DEFAULT_THROTTLE_RATES = { + "vulnerable_packages": "1/day", + "bulk_search_packages": "6/day", + "packages": "10/day", + "vulnerabilities": "8/day", + "aliases": "2/day", + "cpes": "4/day", + "bulk_search_cpes": "5/day", + } USE_L10N = True @@ -202,15 +207,7 @@ "DEFAULT_THROTTLE_CLASSES": [ "vulnerabilities.throttling.StaffUserRateThrottle", ], - "DEFAULT_THROTTLE_RATES": { - "vulnerable_packages": TEST_ALL_VULNERABLE_PACKAGE_THROTTLING_RATE or "1/hour", - "bulk_search_packages": TEST_BULK_SEARCH_PACKAGE_THROTTLING_RATE or "5/hour", - "packages": TEST_PACKAGE_THROTTLING_RATE or "10/minute", - "vulnerabilities": TEST_VULNERABILITIES_THROTTLING_RATE or "10/minute", - "aliases": TEST_ALIASES_THROTTLING_RATE or "5/minute", - "cpes": TEST_CPES_THROTTLING_RATE or "5/minute", - "bulk_search_cpes": TEST_BULK_SEARCH_CPES_THROTTLING_RATE or "5/hour", - }, + "DEFAULT_THROTTLE_RATES": REST_FRAMEWORK_DEFAULT_THROTTLE_RATES, "DEFAULT_PAGINATION_CLASS": "vulnerabilities.pagination.SmallResultSetPagination", # Limit the load on the Database returning a small number of records by default. https://github.com/nexB/vulnerablecode/issues/819 "PAGE_SIZE": 10,