Apply ratelimitting to search endpoint
This commit is contained in:
parent
b016230500
commit
a8ca688456
4 changed files with 55 additions and 13 deletions
|
|
@ -228,6 +228,10 @@ REST_FRAMEWORK = {
|
||||||
"newsreader.accounts.permissions.IsOwner",
|
"newsreader.accounts.permissions.IsOwner",
|
||||||
),
|
),
|
||||||
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
|
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
|
||||||
|
"DEFAULT_THROTTLE_RATES": {
|
||||||
|
"burst_search": "100/min",
|
||||||
|
"sustained_search": "2000/day",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
SWAGGER_SETTINGS = {
|
SWAGGER_SETTINGS = {
|
||||||
|
|
|
||||||
20
src/newsreader/core/throttling.py
Normal file
20
src/newsreader/core/throttling.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from rest_framework.throttling import UserRateThrottle
|
||||||
|
|
||||||
|
|
||||||
|
class SearchThrottle(UserRateThrottle):
|
||||||
|
"""
|
||||||
|
Only applies throttling to requests with the search param
|
||||||
|
"""
|
||||||
|
|
||||||
|
def allow_request(self, request, view):
|
||||||
|
if not "search" in request.GET.keys():
|
||||||
|
return True
|
||||||
|
return super().allow_request(request, view)
|
||||||
|
|
||||||
|
|
||||||
|
class BurstSearchThrottle(SearchThrottle):
|
||||||
|
scope = "burst_search"
|
||||||
|
|
||||||
|
|
||||||
|
class SustainedSearchThrottle(SearchThrottle):
|
||||||
|
scope = "sustained_search"
|
||||||
|
|
@ -8,6 +8,7 @@ from rest_framework.generics import (
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from newsreader.core.pagination import LargeResultSetPagination, ResultSetPagination
|
from newsreader.core.pagination import LargeResultSetPagination, ResultSetPagination
|
||||||
|
from newsreader.core.throttling import BurstSearchThrottle, SustainedSearchThrottle
|
||||||
from newsreader.news.collection.models import CollectionRule
|
from newsreader.news.collection.models import CollectionRule
|
||||||
from newsreader.news.collection.serializers import RuleSerializer
|
from newsreader.news.collection.serializers import RuleSerializer
|
||||||
from newsreader.news.core.filters import ReadFilter
|
from newsreader.news.core.filters import ReadFilter
|
||||||
|
|
@ -19,9 +20,12 @@ class ListRuleView(ListAPIView):
|
||||||
queryset = CollectionRule.objects.all()
|
queryset = CollectionRule.objects.all()
|
||||||
serializer_class = RuleSerializer
|
serializer_class = RuleSerializer
|
||||||
pagination_class = ResultSetPagination
|
pagination_class = ResultSetPagination
|
||||||
|
|
||||||
filter_backends = [filters.SearchFilter]
|
filter_backends = [filters.SearchFilter]
|
||||||
search_fields = ["name", "screen_name", "url"]
|
search_fields = ["name", "screen_name", "url"]
|
||||||
|
|
||||||
|
throttle_classes = [BurstSearchThrottle, SustainedSearchThrottle]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
return self.queryset.filter(user=user).order_by("name", "screen_name")
|
return self.queryset.filter(user=user).order_by("name", "screen_name")
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
from datetime import date, datetime, time
|
from datetime import datetime
|
||||||
from unittest import skip
|
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
from newsreader.accounts.tests.factories import UserFactory
|
from newsreader.accounts.tests.factories import UserFactory
|
||||||
from newsreader.news.collection.tests.factories import (
|
from newsreader.news.collection.tests.factories import (
|
||||||
FeedFactory,
|
FeedFactory,
|
||||||
|
|
@ -210,9 +213,26 @@ class RuleListViewSearchTestCase(TestCase):
|
||||||
self.assertEqual(response_data["results"][1]["id"], rules["foo"].pk)
|
self.assertEqual(response_data["results"][1]["id"], rules["foo"].pk)
|
||||||
self.assertEqual(response_data["results"][2]["id"], rules["FooBar"].pk)
|
self.assertEqual(response_data["results"][2]["id"], rules["FooBar"].pk)
|
||||||
|
|
||||||
@skip("TODO")
|
@freeze_time("2020-10-30 14:00")
|
||||||
def test_ratelimitting(self):
|
def test_ratelimitting(self):
|
||||||
pass
|
# Trigger ratelimit
|
||||||
|
cache.set(
|
||||||
|
f"throttle_burst_search_{self.user.pk}", [time.time() for i in range(100)]
|
||||||
|
)
|
||||||
|
|
||||||
|
params = urlencode({"search": "foo"})
|
||||||
|
url = reverse("api:news:collection:rules-list")
|
||||||
|
|
||||||
|
response = self.client.get(f"{url}?{params}")
|
||||||
|
response_data = response.json()
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 429)
|
||||||
|
|
||||||
|
message = response_data["detail"]
|
||||||
|
|
||||||
|
self.assertIn("Request was throttled", message)
|
||||||
|
|
||||||
|
cache.delete(f"throttle_burst_search_{self.user.pk}")
|
||||||
|
|
||||||
|
|
||||||
class NestedRuleListViewTestCase(TestCase):
|
class NestedRuleListViewTestCase(TestCase):
|
||||||
|
|
@ -357,23 +377,17 @@ class NestedRuleListViewTestCase(TestCase):
|
||||||
FeedPostFactory(
|
FeedPostFactory(
|
||||||
title="I'm the first post",
|
title="I'm the first post",
|
||||||
rule=rule,
|
rule=rule,
|
||||||
publication_date=datetime.combine(
|
publication_date=datetime(2019, 5, 20, 16, 7, 37, tzinfo=pytz.utc),
|
||||||
date(2019, 5, 20), time(hour=16, minute=7, second=37), pytz.utc
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
FeedPostFactory(
|
FeedPostFactory(
|
||||||
title="I'm the second post",
|
title="I'm the second post",
|
||||||
rule=rule,
|
rule=rule,
|
||||||
publication_date=datetime.combine(
|
publication_date=datetime(2019, 7, 20, 18, 7, 37, tzinfo=pytz.utc),
|
||||||
date(2019, 7, 20), time(hour=18, minute=7, second=37), pytz.utc
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
FeedPostFactory(
|
FeedPostFactory(
|
||||||
title="I'm the third post",
|
title="I'm the third post",
|
||||||
rule=rule,
|
rule=rule,
|
||||||
publication_date=datetime.combine(
|
publication_date=datetime(2019, 7, 20, 16, 7, 37, tzinfo=pytz.utc),
|
||||||
date(2019, 7, 20), time(hour=16, minute=7, second=37), pytz.utc
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue