diff --git a/buildhub/api/views.py b/buildhub/api/views.py index 9871a5a1..f84a7abf 100644 --- a/buildhub/api/views.py +++ b/buildhub/api/views.py @@ -7,6 +7,7 @@ import markus from django import http +from django.conf import settings from elasticsearch.exceptions import RequestError from buildhub.main.models import Build @@ -25,6 +26,11 @@ def search(request): if request.method in ("POST",): arguments = json.loads(request.body.decode("utf-8")) if arguments: + if arguments.get("size") and arguments["size"] > settings.MAX_SEARCH_SIZE: + return http.JsonResponse( + {"error": f"Search size too large ({arguments['size']})"}, + status=400, + ) try: search.update_from_dict(arguments) except ValueError as exception: diff --git a/buildhub/settings.py b/buildhub/settings.py index e5723bd7..b9d1c681 100644 --- a/buildhub/settings.py +++ b/buildhub/settings.py @@ -181,6 +181,9 @@ def ES_BUILD_INDEX_SETTINGS(self): def ES_CONNECTIONS(self): return {"default": {"hosts": self.ES_URLS}} + # To prevent the ES search query from being too big. + MAX_SEARCH_SIZE = values.IntegerValue(1000) + class OptionalDatabaseURLValue(values.DatabaseURLValue): def caster(self, url, **options): diff --git a/tests/test_api_views.py b/tests/test_api_views.py index 52eef394..93ceb65c 100644 --- a/tests/test_api_views.py +++ b/tests/test_api_views.py @@ -122,6 +122,15 @@ def test_search_empty_filter(valid_build, json_poster, elasticsearch): ) +@pytest.mark.django_db +def test_search_unbound_size(valid_build, json_poster, elasticsearch, settings): + search = {"query": {"match_all": {}}, "size": settings.MAX_SEARCH_SIZE + 1} + url = reverse("api:search") + response = json_poster(url, search) + assert response.status_code == 400 + assert response.json()["error"] == "Search size too large (1001)" + + @pytest.mark.django_db def test_happy_path_records(valid_build, client, elasticsearch): url = reverse("api:records")