Added throttling to v2 api routes

This commit is contained in:
Spitap
2023-01-27 16:27:52 +01:00
parent 955b5016fb
commit 0d2f3d05a3
9 changed files with 61 additions and 8 deletions
+9 -1
View File
@@ -9,13 +9,14 @@ from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import (
filters, mixins, parsers, pagination, permissions, response, status, viewsets
)
from rest_framework.decorators import action
from rest_framework.decorators import action, throttle_classes
from rest_framework.exceptions import PermissionDenied
from modoboa.admin.api.v1 import viewsets as v1_viewsets
from modoboa.core import models as core_models
from modoboa.lib import renderers as lib_renderers
from modoboa.lib import viewsets as lib_viewsets
from modoboa.lib.throttle import UserDosThrottle
from ... import lib
from ... import models
@@ -51,6 +52,7 @@ class DomainViewSet(lib_viewsets.RevisionModelMixin,
permission_classes = (
permissions.IsAuthenticated, permissions.DjangoModelPermissions,
)
throttle_classes = [UserDosThrottle]
def get_queryset(self):
"""Filter queryset based on current user."""
@@ -159,6 +161,7 @@ class AccountViewSet(v1_viewsets.AccountViewSet):
filter_backends = (filters.SearchFilter, dj_filters.DjangoFilterBackend)
filterset_class = AccountFilterSet
throttle_classes = [UserDosThrottle]
def get_serializer_class(self):
if self.action in ["create", "validate", "update", "partial_update"]:
@@ -213,6 +216,7 @@ class IdentityViewSet(viewsets.ViewSet):
permission_classes = (permissions.IsAuthenticated, )
serializer_class = None
throttle_classes = [UserDosThrottle]
def list(self, request, **kwargs):
"""Return all identities."""
@@ -252,6 +256,7 @@ class AliasViewSet(v1_viewsets.AliasViewSet):
"""Viewset for Alias."""
serializer_class = serializers.AliasSerializer
throttle_classes = [UserDosThrottle]
@action(methods=["post"], detail=False)
def validate(self, request, **kwargs):
@@ -273,6 +278,8 @@ class AliasViewSet(v1_viewsets.AliasViewSet):
class UserAccountViewSet(viewsets.ViewSet):
"""Viewset for current user operations."""
throttle_classes = [UserDosThrottle]
@action(methods=["get", "post"], detail=False)
def forward(self, request, **kwargs):
"""Get or define user forward."""
@@ -334,6 +341,7 @@ class AlarmViewSet(viewsets.ReadOnlyModelViewSet):
)
search_fields = ["domain__name", "title"]
serializer_class = serializers.AlarmSerializer
throttle_classes = [UserDosThrottle]
def get_queryset(self):
return models.Alarm.objects.select_related("domain").filter(
+5 -2
View File
@@ -14,6 +14,7 @@ from rest_framework_simplejwt import views as jwt_views
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from rest_framework.views import APIView
from modoboa.lib.throttle import UserDosThrottle
from modoboa.core.password_hashers import get_password_hasher
from modoboa.core.utils import check_for_updates
from modoboa.parameters import tools as param_tools
@@ -28,7 +29,7 @@ logger = logging.getLogger("modoboa.auth")
class TokenObtainPairView(jwt_views.TokenObtainPairView):
"""We overwrite this view to deal with password scheme update."""
throttle_classes = ["AnonRateThrottle"]
throttle_scope = "login"
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
@@ -86,7 +87,7 @@ class EmailPasswordResetView(APIView):
"""
An Api View which provides a method to request a password reset token based on an e-mail address.
"""
throttle_scope = 'password_recovery_request'
def post(self, request, *args, **kwargs):
@@ -173,6 +174,8 @@ class PasswordResetConfirmView(APIView):
class ComponentsInformationAPIView(APIView):
"""Retrieve information about installed components."""
throttle_classes = [UserDosThrottle]
@extend_schema(responses=serializers.ModoboaComponentSerializer(many=True))
def get(self, request, *args, **kwargs):
status, extensions = check_for_updates()
@@ -194,12 +194,16 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.ScopedRateThrottle'
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.ScopedRateThrottle',
'modoboa.lib.throttle.UserDosThrottle',
'modoboa.lib.throttle.AnonDosThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/hour',
'login': '5/hour',
'login': '3/minute',
'ddos': '100/minute',
'anon_ddos': '10/minute',
'password_recovery_request': '6/hour',
'password_recovery_totp_check': '20/hour',
'password_recovery_apply': '20/hour'
+2
View File
@@ -4,6 +4,7 @@ from rest_framework import permissions, response, viewsets
from rest_framework.decorators import action
from modoboa.admin import models as admin_models
from modoboa.lib.throttle import UserDosThrottle
from . import serializers
@@ -12,6 +13,7 @@ class DNSViewSet(viewsets.GenericViewSet):
"""A viewset to provide extra routes related to DNS information."""
permission_classes = (permissions.IsAuthenticated, )
throttle_classes = [UserDosThrottle]
def get_queryset(self):
"""Filter queryset based on current user."""
+23
View File
@@ -0,0 +1,23 @@
from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
class UserDosThrottle(UserRateThrottle):
"""Custom Throttle class for rest_framework. The throttling is applied on a per view basis for authentificated users."""
scope = "ddos"
def get_cache_key(self, request, view):
return "throttle_{viewid}_{indent}".format(
viewid=id(view),
indent=self.get_indent(request)
)
class AnonDosThrottle(AnonRateThrottle):
"""Custom Throttle class for rest_framework. The throttling is applied on a per view basis for visitors."""
scope = "anon_ddos"
def get_cache_key(self, request, view):
return "throttle_{viewid}_{indent}".format(
viewid=id(view),
indent=self.get_indent(request)
)
+3
View File
@@ -9,6 +9,7 @@ from rest_framework import filters, permissions, response, viewsets
from modoboa.admin import models as admin_models
from modoboa.lib import pagination
from modoboa.lib.throttle import UserDosThrottle
from ... import models
from ... import signals
@@ -19,6 +20,7 @@ class StatisticsViewSet(viewsets.ViewSet):
"""A viewset to provide extra route related to mail statistics."""
permission_classes = (permissions.IsAuthenticated, )
throttle_classes = [UserDosThrottle]
@extend_schema(
parameters=[serializers.StatisticsInputSerializer],
@@ -61,6 +63,7 @@ class MaillogViewSet(viewsets.ReadOnlyModelViewSet):
permissions = (permissions.IsAuthenticated, )
search_fields = ["queue_id", "sender", "rcpt", "original_rcpt", "status"]
serializer_class = serializers.MaillogSerializer
throttle_classes = [UserDosThrottle]
def get_queryset(self):
"""Filter queryset based on current user."""
+3
View File
@@ -4,6 +4,8 @@ from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework import response, viewsets
from rest_framework.decorators import action
from modoboa.lib.throttle import UserDosThrottle
from . import serializers
from ... import tools
@@ -13,6 +15,7 @@ class ParametersViewSet(viewsets.ViewSet):
lookup_value_regex = r"\w+"
serializer_class = None
throttle_classes = [UserDosThrottle]
@extend_schema(responses=serializers.ApplicationSerializer(many=True))
@action(methods=["get"], detail=False)
+3
View File
@@ -3,6 +3,8 @@
from drf_spectacular.utils import extend_schema
from rest_framework import permissions, response, viewsets
from modoboa.lib.throttle import UserDosThrottle
from . import serializers
from ... import backends
@@ -11,6 +13,7 @@ class TransportViewSet(viewsets.ViewSet):
"""Viewset for Transport."""
permissions = (permissions.IsAuthenticated, )
throttle_classes = [UserDosThrottle]
@extend_schema(
responses={200: serializers.TransportBackendSerializer}
+6 -2
View File
@@ -188,11 +188,15 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.ScopedRateThrottle'
'rest_framework.throttling.ScopedRateThrottle',
'modoboa.lib.throttle.UserDosThrottle',
'modoboa.lib.throttle.AnonDosThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/hour',
'login': '5/hour',
'login': '3/minute',
'ddos': '100/minute',
'anon_ddos': '10/minute',
'password_recovery_request': '6/hour',
'password_recovery_totp_check': '20/hour',
'password_recovery_apply': '20/hour'