Skip to content

Commit 8e9607b

Browse files
committed
Throttle API requests based on user permissions
Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent 677ff99 commit 8e9607b

File tree

6 files changed

+47
-40
lines changed

6 files changed

+47
-40
lines changed

vulnerabilities/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from vulnerabilities.models import get_purl_query_lookups
3535
from vulnerabilities.severity_systems import EPSS
3636
from vulnerabilities.severity_systems import SCORING_SYSTEMS
37-
from vulnerabilities.throttling import GroupUserRateThrottle
37+
from vulnerabilities.throttling import PermissionBasedUserRateThrottle
3838
from vulnerabilities.utils import get_severity_range
3939

4040

@@ -471,7 +471,7 @@ class PackageViewSet(viewsets.ReadOnlyModelViewSet):
471471
serializer_class = PackageSerializer
472472
filter_backends = (filters.DjangoFilterBackend,)
473473
filterset_class = PackageFilterSet
474-
throttle_classes = [AnonRateThrottle, GroupUserRateThrottle]
474+
throttle_classes = [AnonRateThrottle, PermissionBasedUserRateThrottle]
475475

476476
def get_queryset(self):
477477
return super().get_queryset().with_is_vulnerable()
@@ -688,7 +688,7 @@ def get_queryset(self):
688688
serializer_class = VulnerabilitySerializer
689689
filter_backends = (filters.DjangoFilterBackend,)
690690
filterset_class = VulnerabilityFilterSet
691-
throttle_classes = [AnonRateThrottle, GroupUserRateThrottle]
691+
throttle_classes = [AnonRateThrottle, PermissionBasedUserRateThrottle]
692692

693693

694694
class CPEFilterSet(filters.FilterSet):

vulnerabilities/api_extension.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from vulnerabilities.models import VulnerabilitySeverity
3434
from vulnerabilities.models import Weakness
3535
from vulnerabilities.models import get_purl_query_lookups
36-
from vulnerabilities.throttling import GroupUserRateThrottle
36+
from vulnerabilities.throttling import PermissionBasedUserRateThrottle
3737

3838

3939
class SerializerExcludeFieldsMixin:
@@ -259,7 +259,7 @@ class V2PackageViewSet(viewsets.ReadOnlyModelViewSet):
259259
lookup_field = "purl"
260260
filter_backends = (filters.DjangoFilterBackend,)
261261
filterset_class = V2PackageFilterSet
262-
throttle_classes = [GroupUserRateThrottle, AnonRateThrottle]
262+
throttle_classes = [PermissionBasedUserRateThrottle, AnonRateThrottle]
263263

264264
def get_queryset(self):
265265
return super().get_queryset().with_is_vulnerable().prefetch_related("vulnerabilities")
@@ -345,7 +345,7 @@ class VulnerabilityViewSet(viewsets.ReadOnlyModelViewSet):
345345
lookup_field = "vulnerability_id"
346346
filter_backends = (filters.DjangoFilterBackend,)
347347
filterset_class = V2VulnerabilityFilterSet
348-
throttle_classes = [GroupUserRateThrottle, AnonRateThrottle]
348+
throttle_classes = [PermissionBasedUserRateThrottle, AnonRateThrottle]
349349

350350
def get_queryset(self):
351351
"""
@@ -381,7 +381,7 @@ class CPEViewSet(viewsets.ReadOnlyModelViewSet):
381381
).distinct()
382382
serializer_class = V2VulnerabilitySerializer
383383
filter_backends = (filters.DjangoFilterBackend,)
384-
throttle_classes = [GroupUserRateThrottle, AnonRateThrottle]
384+
throttle_classes = [PermissionBasedUserRateThrottle, AnonRateThrottle]
385385
filterset_class = CPEFilterSet
386386

387387
@action(detail=False, methods=["post"])
@@ -420,4 +420,4 @@ class AliasViewSet(viewsets.ReadOnlyModelViewSet):
420420
serializer_class = V2VulnerabilitySerializer
421421
filter_backends = (filters.DjangoFilterBackend,)
422422
filterset_class = AliasFilterSet
423-
throttle_classes = [GroupUserRateThrottle, AnonRateThrottle]
423+
throttle_classes = [PermissionBasedUserRateThrottle, AnonRateThrottle]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 4.2.22 on 2025-06-13 08:07
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("vulnerabilities", "0092_pipelineschedule_pipelinerun"),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name="apiuser",
15+
options={
16+
"permissions": [
17+
("throttle_unrestricted", "Exempt from API throttling limits"),
18+
("throttle_18000_hour", "Can make 18000 API requests per hour"),
19+
("throttle_14400_hour", "Can make 14400 API requests per hour"),
20+
]
21+
},
22+
),
23+
]

vulnerabilities/models.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,10 +1473,6 @@ def create_api_user(self, username, first_name="", last_name="", **extra_fields)
14731473
user.set_unusable_password()
14741474
user.save()
14751475

1476-
# Assign the default basic group
1477-
default_group, _ = Group.objects.get_or_create(name="silver")
1478-
user.groups.add(default_group)
1479-
14801476
Token._default_manager.get_or_create(user=user)
14811477

14821478
return user
@@ -1494,14 +1490,17 @@ def _validate_username(self, email):
14941490

14951491

14961492
class ApiUser(UserModel):
1497-
"""
1498-
A User proxy model to facilitate simplified admin API user creation.
1499-
"""
1493+
"""A User proxy model to facilitate simplified admin API user creation."""
15001494

15011495
objects = ApiUserManager()
15021496

15031497
class Meta:
15041498
proxy = True
1499+
permissions = [
1500+
("throttle_unrestricted", "Exempt from API throttling limits"),
1501+
("throttle_18000_hour", "Can make 18000 API requests per hour"),
1502+
("throttle_14400_hour", "Can make 14400 API requests per hour"),
1503+
]
15051504

15061505

15071506
class ChangeLog(models.Model):

vulnerabilities/throttling.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,19 @@
1212
from rest_framework.views import exception_handler
1313

1414

15-
class GroupUserRateThrottle(UserRateThrottle):
16-
scope = "bronze"
17-
15+
class PermissionBasedUserRateThrottle(UserRateThrottle):
1816
def allow_request(self, request, view):
1917
user = request.user
2018

2119
if user and user.is_authenticated:
22-
if user.is_superuser or user.is_staff:
23-
return True
24-
25-
user_groups = user.groups.all()
26-
if any([group.name == "gold" for group in user_groups]):
20+
if user.has_perm("vulnerabilities.throttle_unrestricted"):
2721
return True
22+
elif user.has_perm("vulnerabilities.throttle_18000_hour"):
23+
self.rate = "18000/hour"
24+
elif user.has_perm("vulnerabilities.throttle_14400_hour"):
25+
self.rate = "14400/hour"
2826

29-
if any([group.name == "silver" for group in user_groups]):
30-
self.scope = "silver"
31-
32-
self.rate = self.THROTTLE_RATES.get(self.scope)
33-
self.num_requests, self.duration = self.parse_rate(self.rate)
27+
self.num_requests, self.duration = self.parse_rate(self.rate)
3428

3529
return super().allow_request(request, view)
3630

vulnerablecode/settings.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -190,20 +190,11 @@
190190
LOGIN_REDIRECT_URL = "/"
191191
LOGOUT_REDIRECT_URL = "/"
192192

193-
REST_FRAMEWORK_DEFAULT_THROTTLE_RATES = {
194-
# No throttling for users in gold group.
195-
"silver": "10800/hour",
196-
"bronze": "7200/hour",
197-
"anon": "3600/hour",
198-
}
193+
REST_FRAMEWORK_DEFAULT_THROTTLE_RATES = {"anon": "3600/hour", "user": "10800/hour"}
194+
199195

200196
if IS_TESTS:
201197
VULNERABLECODEIO_REQUIRE_AUTHENTICATION = False
202-
REST_FRAMEWORK_DEFAULT_THROTTLE_RATES = {
203-
"silver": "20/day",
204-
"bronze": "15/day",
205-
"anon": "10/day",
206-
}
207198

208199
USE_L10N = True
209200

@@ -243,7 +234,7 @@
243234
"rest_framework.filters.SearchFilter",
244235
),
245236
"DEFAULT_THROTTLE_CLASSES": [
246-
"vulnerabilities.throttling.GroupUserRateThrottle",
237+
"vulnerabilities.throttling.PermissionBasedUserRateThrottle",
247238
"rest_framework.throttling.AnonRateThrottle",
248239
],
249240
"DEFAULT_THROTTLE_RATES": REST_FRAMEWORK_DEFAULT_THROTTLE_RATES,

0 commit comments

Comments
 (0)