forked from Rockachopa/Timmy-time-dashboard
101 lines
3.1 KiB
Python
101 lines
3.1 KiB
Python
"""Tests that CSRF rejection does NOT execute the endpoint handler.
|
|
|
|
Regression test for #626: the middleware was calling call_next() before
|
|
checking @csrf_exempt, causing side effects even on CSRF-rejected requests.
|
|
"""
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from dashboard.middleware.csrf import CSRFMiddleware, csrf_exempt
|
|
|
|
|
|
class TestCSRFNoSideEffects:
|
|
"""Verify endpoints are NOT executed when CSRF validation fails."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def enable_csrf(self):
|
|
"""Re-enable CSRF for these tests."""
|
|
from config import settings
|
|
|
|
original = settings.timmy_disable_csrf
|
|
settings.timmy_disable_csrf = False
|
|
yield
|
|
settings.timmy_disable_csrf = original
|
|
|
|
def test_protected_endpoint_not_executed_on_csrf_failure(self):
|
|
"""A protected endpoint must NOT run when CSRF token is missing.
|
|
|
|
Before the fix, the middleware called call_next() to resolve the
|
|
endpoint, executing its side effects before returning 403.
|
|
"""
|
|
app = FastAPI()
|
|
app.add_middleware(CSRFMiddleware)
|
|
|
|
side_effect_log = []
|
|
|
|
@app.post("/transfer")
|
|
def transfer_money():
|
|
side_effect_log.append("money_transferred")
|
|
return {"message": "transferred"}
|
|
|
|
client = TestClient(app)
|
|
response = client.post("/transfer")
|
|
|
|
assert response.status_code == 403
|
|
assert side_effect_log == [], (
|
|
"Endpoint was executed despite CSRF failure — side effects occurred!"
|
|
)
|
|
|
|
def test_csrf_exempt_endpoint_still_executes(self):
|
|
"""A @csrf_exempt endpoint should still execute without a CSRF token."""
|
|
app = FastAPI()
|
|
app.add_middleware(CSRFMiddleware)
|
|
|
|
side_effect_log = []
|
|
|
|
@app.post("/webhook-handler")
|
|
@csrf_exempt
|
|
def webhook_handler():
|
|
side_effect_log.append("webhook_processed")
|
|
return {"message": "processed"}
|
|
|
|
client = TestClient(app)
|
|
response = client.post("/webhook-handler")
|
|
|
|
assert response.status_code == 200
|
|
assert side_effect_log == ["webhook_processed"]
|
|
|
|
def test_exempt_and_protected_no_cross_contamination(self):
|
|
"""Mixed exempt/protected: only exempt endpoints execute without tokens."""
|
|
app = FastAPI()
|
|
app.add_middleware(CSRFMiddleware)
|
|
|
|
execution_log = []
|
|
|
|
@app.post("/safe-webhook")
|
|
@csrf_exempt
|
|
def safe_webhook():
|
|
execution_log.append("safe")
|
|
return {"message": "safe"}
|
|
|
|
@app.post("/dangerous-action")
|
|
def dangerous_action():
|
|
execution_log.append("dangerous")
|
|
return {"message": "danger"}
|
|
|
|
client = TestClient(app)
|
|
|
|
# Exempt endpoint runs
|
|
resp1 = client.post("/safe-webhook")
|
|
assert resp1.status_code == 200
|
|
|
|
# Protected endpoint blocked WITHOUT executing
|
|
resp2 = client.post("/dangerous-action")
|
|
assert resp2.status_code == 403
|
|
|
|
assert execution_log == ["safe"], (
|
|
f"Expected only 'safe' execution, got: {execution_log}"
|
|
)
|