From ff4d48cfe878f97433bdf8553fcb7a99aebf4779 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:25:56 -0700 Subject: [PATCH] Reject searches with too many params --- src/documents/tests/test_api_search.py | 8 ++++++++ src/documents/views.py | 27 ++++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/documents/tests/test_api_search.py b/src/documents/tests/test_api_search.py index ef377aa55..9e0879e89 100644 --- a/src/documents/tests/test_api_search.py +++ b/src/documents/tests/test_api_search.py @@ -212,6 +212,14 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): self.assertEqual(response.data["count"], 1) self.assertEqual(response.data["results"][0]["id"], title_match.id) + def test_search_rejects_multiple_search_modes(self) -> None: + response = self.client.get("/api/documents/?text=bank&query=bank") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual( + response.data["detail"], + "Specify only one of text, title_search, query, or more_like_id.", + ) + def test_search_returns_all_for_api_version_9(self) -> None: d1 = Document.objects.create( title="invoice", diff --git a/src/documents/views.py b/src/documents/views.py index 8f7752234..f90226ca5 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -2037,19 +2037,22 @@ class ChatStreamingView(GenericAPIView): ), ) class UnifiedSearchViewSet(DocumentViewSet): + SEARCH_PARAM_NAMES = ("text", "title_search", "query", "more_like_id") + def get_serializer_class(self): if self._is_search_request(): return SearchResultSerializer else: return DocumentSerializer + def _get_active_search_params(self, request: Request | None = None) -> list[str]: + request = request or self.request + return [ + param for param in self.SEARCH_PARAM_NAMES if param in request.query_params + ] + def _is_search_request(self): - return ( - "text" in self.request.query_params - or "title_search" in self.request.query_params - or "query" in self.request.query_params - or "more_like_id" in self.request.query_params - ) + return bool(self._get_active_search_params()) def list(self, request, *args, **kwargs): if not self._is_search_request(): @@ -2065,6 +2068,16 @@ class UnifiedSearchViewSet(DocumentViewSet): filtered_qs = self.filter_queryset(self.get_queryset()) user = None if request.user.is_superuser else request.user + active_search_params = self._get_active_search_params(request) + + if len(active_search_params) > 1: + raise ValidationError( + { + "detail": _( + "Specify only one of text, title_search, query, or more_like_id.", + ), + }, + ) if ( "text" in request.query_params @@ -2160,6 +2173,8 @@ class UnifiedSearchViewSet(DocumentViewSet): if str(e.detail) == str(invalid_more_like_id_message): return HttpResponseForbidden(invalid_more_like_id_message) return HttpResponseForbidden(_("Insufficient permissions.")) + except ValidationError: + raise except Exception as e: logger.warning(f"An error occurred listing search results: {e!s}") return HttpResponseBadRequest(