From c16a5887bd6eaf2f8a8b87b3a5beb6eff5a3668b Mon Sep 17 00:00:00 2001 From: BOUKERFA Mohamed El Amine Date: Sat, 20 Jun 2026 10:18:22 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=EF=B8=8F(backend)=20throttle=20the?= =?UTF-8?q?=20document=20mention=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a dedicated "mention" throttle scope limiting calls to the document mention endpoint to 30 requests per minute. Signed-off-by: BOUKERFA Mohamed El Amine --- src/backend/core/api/viewsets.py | 7 ++- .../documents/test_api_documents_mention.py | 60 +++++++++++++++++++ src/backend/impress/settings.py | 5 ++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 75efbe151..4160b7176 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -1877,7 +1877,12 @@ class DocumentViewSet( status=drf.status.HTTP_200_OK, ) - @drf.decorators.action(detail=True, methods=["post"], url_path="mention") + @drf.decorators.action( + detail=True, + methods=["post"], + url_path="mention", + throttle_scope="mention", + ) def mention(self, request, *args, **kwargs): """Mention a user on the document and notify them by email. diff --git a/src/backend/core/tests/documents/test_api_documents_mention.py b/src/backend/core/tests/documents/test_api_documents_mention.py index d066f7a83..a139c878e 100644 --- a/src/backend/core/tests/documents/test_api_documents_mention.py +++ b/src/backend/core/tests/documents/test_api_documents_mention.py @@ -509,3 +509,63 @@ def test_api_documents_mention_soft_deleted_document(): assert response.status_code == 404 assert models.Mention.objects.exists() is False + + +def test_api_documents_mention_throttling(settings): + """Mention requests should be throttled once the mention rate is exceeded.""" + current_rate = settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["mention"] + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["mention"] = "3/minute" + + user = factories.UserFactory() + document = factories.DocumentFactory(link_reach="restricted") + factories.UserDocumentAccessFactory(document=document, user=user, role="commenter") + mentioned_user = factories.UserFactory() + factories.UserDocumentAccessFactory(document=document, user=mentioned_user) + + client = APIClient() + client.force_login(user) + payload = {"anchor_id": "block-1", "mentioned_user_id": str(mentioned_user.id)} + + # The first three requests within the minute are allowed. + for _i in range(3): + response = client.post(f"/api/v1.0/documents/{document.id!s}/mention/", payload) + assert response.status_code == 201 + + # The fourth request is throttled. + response = client.post(f"/api/v1.0/documents/{document.id!s}/mention/", payload) + assert response.status_code == 429 + + # Restore original rate + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["mention"] = current_rate + + +def test_api_documents_mention_throttling_y_provider_exempted(settings): + """ + Collaboration-server requests bypass the mention throttle, just like other + document endpoints relying on the document throttle exemption. + """ + current_rate = settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["mention"] + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["mention"] = "3/minute" + settings.Y_PROVIDER_API_KEY = "test-y-provider-key" + + user = factories.UserFactory() + document = factories.DocumentFactory(link_reach="restricted") + factories.UserDocumentAccessFactory(document=document, user=user, role="commenter") + mentioned_user = factories.UserFactory() + factories.UserDocumentAccessFactory(document=document, user=mentioned_user) + + client = APIClient() + client.force_login(user) + payload = {"anchor_id": "block-1", "mentioned_user_id": str(mentioned_user.id)} + + # More requests than the rate allows all succeed with the y-provider key. + for _i in range(5): + response = client.post( + f"/api/v1.0/documents/{document.id!s}/mention/", + payload, + HTTP_X_Y_PROVIDER_KEY="test-y-provider-key", + ) + assert response.status_code == 201 + + # Restore original rate + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["mention"] = current_rate diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py index 5d93fa548..e478d6f10 100755 --- a/src/backend/impress/settings.py +++ b/src/backend/impress/settings.py @@ -486,6 +486,11 @@ class Base(Configuration): environ_name="API_DOCUMENT_ASK_FOR_ACCESS_THROTTLE_RATE", environ_prefix=None, ), + "mention": values.Value( + default="30/minute", + environ_name="API_MENTION_THROTTLE_RATE", + environ_prefix=None, + ), "config": values.Value( default="30/minute", environ_name="API_CONFIG_THROTTLE_RATE",