7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
101 lines
3.9 KiB
Python
101 lines
3.9 KiB
Python
# allow: no-sut-import — black-box HTTP test; drives real routes through the Flask test client
|
|
"""
|
|
End-to-end CSRF flow test for browser-facing API endpoints.
|
|
|
|
Verifies the full flow: login → CSRF token → API call with CSRF enabled,
|
|
ensuring the narrowed CSRF exemptions (only api_v1 exempt) work correctly
|
|
for browser-facing endpoints like /api/start_research.
|
|
"""
|
|
|
|
import re
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
|
|
class TestCSRFEndToEndFlow:
|
|
"""Test the complete CSRF flow from login through API usage."""
|
|
|
|
@pytest.fixture
|
|
def csrf_app(self, app):
|
|
"""Wrap the root conftest app fixture with CSRF enabled."""
|
|
app.config["WTF_CSRF_ENABLED"] = True
|
|
app.config["WTF_CSRF_CHECK_DEFAULT"] = True
|
|
return app
|
|
|
|
def test_full_csrf_flow_login_through_api_call(self, csrf_app):
|
|
"""Test complete browser-like flow: register → get CSRF token → API call."""
|
|
client = csrf_app.test_client()
|
|
|
|
with client:
|
|
# Step 1: GET /auth/login to get a CSRF token for registration
|
|
response = client.get("/auth/login")
|
|
assert response.status_code == 200
|
|
|
|
csrf_match = re.search(
|
|
r'name="csrf_token" value="([^"]+)"', response.data.decode()
|
|
)
|
|
assert csrf_match is not None, (
|
|
"Could not find CSRF token in login page"
|
|
)
|
|
form_csrf_token = csrf_match.group(1)
|
|
|
|
# Step 2: Register (auto-logs in the user)
|
|
username = f"csrf_e2e_{uuid.uuid4().hex[:12]}"
|
|
password = "TestPass123!"
|
|
|
|
register_response = client.post(
|
|
"/auth/register",
|
|
data={
|
|
"username": username,
|
|
"password": password,
|
|
"confirm_password": password,
|
|
"acknowledge": "true",
|
|
"csrf_token": form_csrf_token,
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert register_response.status_code == 302, (
|
|
f"Registration should redirect, got {register_response.status_code}"
|
|
)
|
|
|
|
# Step 3: Get API CSRF token from /auth/csrf-token
|
|
csrf_response = client.get("/auth/csrf-token")
|
|
assert csrf_response.status_code == 200
|
|
csrf_data = csrf_response.get_json()
|
|
assert csrf_data is not None and "csrf_token" in csrf_data
|
|
api_csrf_token = csrf_data["csrf_token"]
|
|
|
|
# Step 4: POST /api/start_research WITH CSRF token — should pass CSRF
|
|
response = client.post(
|
|
"/api/start_research",
|
|
json={"query": "test csrf flow"},
|
|
headers={"X-CSRFToken": api_csrf_token},
|
|
)
|
|
# Expect 200 (success) or 400 from business logic — but not a CSRF rejection
|
|
assert response.status_code in (200, 400), (
|
|
f"Expected 200 or business-logic 400, got {response.status_code}"
|
|
)
|
|
if response.status_code == 400:
|
|
data = response.get_json()
|
|
error_msg = data.get("error", "") if data else ""
|
|
assert "csrf" not in error_msg.lower(), (
|
|
f"CSRF should have passed but got: {error_msg}"
|
|
)
|
|
assert "session cookie" not in error_msg.lower(), (
|
|
f"CSRF should have passed but got: {error_msg}"
|
|
)
|
|
|
|
# Step 5: POST /api/start_research WITHOUT CSRF token — should be rejected
|
|
response = client.post(
|
|
"/api/start_research",
|
|
json={"query": "test csrf flow"},
|
|
)
|
|
assert response.status_code == 400
|
|
data = response.get_json()
|
|
assert data is not None and "error" in data
|
|
error_lower = data["error"].lower()
|
|
assert "csrf" in error_lower or "session cookie" in error_lower, (
|
|
f"Expected CSRF error but got: {data['error']}"
|
|
)
|