From 181b97b31065ad71ff39d10843cb00afc2d60298 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Wed, 29 Apr 2026 11:07:13 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F(backend)=20align=20CSRF=20to?= =?UTF-8?q?ken=20header=20with=20Django=20conventions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the CSRF header naming to follow Django standards, avoiding duplicated client-side logic with inconsistent header names. --- src/backend/core/addons/viewsets.py | 4 +-- .../core/tests/addons/test_api_addons.py | 30 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/backend/core/addons/viewsets.py b/src/backend/core/addons/viewsets.py index ef807341..6cf23771 100644 --- a/src/backend/core/addons/viewsets.py +++ b/src/backend/core/addons/viewsets.py @@ -106,7 +106,7 @@ class SessionViewSet(viewsets.ViewSet): """Poll a session for its current state and, if terminal, consume it. Authenticates the caller using the addonsSid cookie (set by - /init) together with the X-CSRF-Token header, which must match + /init) together with the X-CSRFToken header, which must match the CSRF token issued for that session. The session id alone is not sufficient — both must be presented and must correspond. @@ -127,7 +127,7 @@ class SessionViewSet(viewsets.ViewSet): """ session_id = request.COOKIES.get(settings.ADDONS_SESSION_ID_COOKIE) - submitted_csrf = request.headers.get("X-CSRF-Token") + submitted_csrf = request.headers.get("X-CSRFToken") if not session_id: return drf_response.Response( diff --git a/src/backend/core/tests/addons/test_api_addons.py b/src/backend/core/tests/addons/test_api_addons.py index eaf85703..ab392837 100644 --- a/src/backend/core/tests/addons/test_api_addons.py +++ b/src/backend/core/tests/addons/test_api_addons.py @@ -95,7 +95,7 @@ def test_init_cookie_authorizes_subsequent_poll(): poll_response = api_client.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_token, + HTTP_X_CSRFTOKEN=csrf_token, ) assert poll_response.status_code == 202 @@ -180,7 +180,7 @@ def test_poll_rejects_invalid_csrf_token(): poll_response = api_client.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN="invalid-csrf-token", + HTTP_X_CSRFTOKEN="invalid-csrf-token", ) # SuspiciousOperation translates to 400 via Django's exception middleware. @@ -202,7 +202,7 @@ def test_poll_session_not_found(mock_get_session_data): poll_response = api_client.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_token, + HTTP_X_CSRFTOKEN=csrf_token, ) assert poll_response.status_code == 404 @@ -224,7 +224,7 @@ def test_poll_session_corrupted(mock_get_session_data): poll_response = api_client.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_token, + HTTP_X_CSRFTOKEN=csrf_token, ) assert poll_response.status_code == 400 @@ -249,7 +249,7 @@ def test_poll_session_authenticated(): poll_response = api_client.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_token, + HTTP_X_CSRFTOKEN=csrf_token, ) assert poll_response.status_code == 200 @@ -271,7 +271,7 @@ def test_poll_session_authenticated(): # Server cleared the addonsSid cookie; APIClient drops it → no credentials. poll_response = api_client.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_token, + HTTP_X_CSRFTOKEN=csrf_token, ) assert poll_response.status_code == 401 @@ -279,7 +279,7 @@ def test_poll_session_authenticated(): api_client.cookies["addonsSid"] = session_id_cookie.value poll_response = api_client.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_token, + HTTP_X_CSRFTOKEN=csrf_token, ) assert poll_response.status_code == 404 assert poll_response.json() == {"detail": "Session not found."} @@ -307,11 +307,11 @@ def test_poll_two_clients_do_not_interfere(): # Each client polls its own session. poll_a = client_a.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_a, + HTTP_X_CSRFTOKEN=csrf_a, ) poll_b = client_b.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_b, + HTTP_X_CSRFTOKEN=csrf_b, ) assert poll_a.status_code == 202 assert poll_b.status_code == 202 @@ -319,7 +319,7 @@ def test_poll_two_clients_do_not_interfere(): # Cross-use (A's cookie + B's CSRF) must be rejected. cross_response = client_a.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_b, + HTTP_X_CSRFTOKEN=csrf_b, ) assert cross_response.status_code == 400 @@ -336,13 +336,13 @@ def test_poll_two_clients_do_not_interfere(): ): poll_a = client_a.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_a, + HTTP_X_CSRFTOKEN=csrf_a, ) assert poll_a.status_code == 200 poll_b = client_b.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_b, + HTTP_X_CSRFTOKEN=csrf_b, ) assert poll_b.status_code == 202 @@ -369,14 +369,14 @@ def test_poll_csrf_attack_does_not_disrupt_legitimate_client(): # Fabricated CSRF token attack_bad_csrf = attacker.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN="attacker-guessed-token", + HTTP_X_CSRFTOKEN="attacker-guessed-token", ) assert attack_bad_csrf.status_code == 400 # Legitimate client's session is still usable. legitimate_poll = legitimate.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_token, + HTTP_X_CSRFTOKEN=csrf_token, ) assert legitimate_poll.status_code == 202 assert legitimate_poll.json() == {"state": "pending"} @@ -508,7 +508,7 @@ def test_exchange_success_enables_poll_to_complete(): # 3. Taskpane's next poll transitions from pending → authenticated. poll_response = taskpane.post( "/api/v1.0/addons/sessions/poll/", - HTTP_X_CSRF_TOKEN=csrf_token, + HTTP_X_CSRFTOKEN=csrf_token, ) assert poll_response.status_code == 200 response_data = poll_response.json()