from __future__ import annotations import json import re from typing import Final import httpx import pytest from fastapi import FastAPI, Request from fastapi.testclient import TestClient from litellm_enterprise.proxy.liteadmin import AdminSession, NativeAdminContext, native_admin_context, router from pydantic import SecretStr, TypeAdapter from litellm.proxy._types import LiteLLM_UserTable TOKEN: Final = "a" * 43 PATH: Final = "/liteadmin/slack/connect/" + TOKEN ORIGIN: Final = "https://gateway.example.com" class Worker: def __init__(self, *, email: str = "alice@example.com", status: int = 200) -> None: self.email = email self.status = status self.session: object = None self.role = "proxy_admin" def request(self, request: httpx.Request) -> httpx.Response: assert request.headers["X-LiteLLM-Admin-Agent-Token"] == "s" * 32 if request.method == "POST": self.session = json.loads(request.content) return httpx.Response(self.status, json={"status": "connected"}) return httpx.Response( self.status, json={ "workspace_id": "Tworkspace", "slack_user_id": "Ualice", "email": self.email, }, ) def client_for( worker: Worker, *, role: str | None = None, logged_in: bool = True, email: str = "alice@example.com" ) -> TestClient: async def session_user(request: Request) -> str | None: return "alice" if logged_in else None async def load_user(user_id: str) -> LiteLLM_UserTable: return LiteLLM_UserTable(user_id=user_id, user_email=email, user_role=role or worker.role) def mint(user: LiteLLM_UserTable) -> AdminSession: return AdminSession(user_id=user.user_id, credential=SecretStr("personal-session"), expires_at=86400) context: Final = NativeAdminContext( "http://private-worker:10000", SecretStr("s" * 32), httpx.AsyncClient(transport=httpx.MockTransport(worker.request)), session_user, load_user, mint, ) app: Final = FastAPI() app.include_router(router) app.dependency_overrides[native_admin_context] = lambda: context return TestClient(app, base_url=ORIGIN) def csrf_from(client: TestClient) -> str: page: Final = client.get(PATH) assert page.status_code == 200 match: Final = re.search('name="csrf" value="([^"]+)"', page.text) assert match is not None return match[1] def test_connect_uses_existing_login_without_a_hosted_oauth_callback(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PROXY_BASE_URL", ORIGIN) with client_for(Worker(), logged_in=False) as client: response: Final = client.get(PATH, follow_redirects=False) assert response.status_code == 303 assert ( response.headers["location"] == ORIGIN + "/sso/key/generate?return_to=%2Fliteadmin%2Fslack%2Fconnect%2F" + TOKEN ) assert response.headers["cache-control"] == "no-store" def test_connect_hands_off_personal_session_only_over_private_worker_channel(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PROXY_BASE_URL", ORIGIN) worker: Final = Worker() with client_for(worker) as client: csrf: Final = csrf_from(client) response: Final = client.post(PATH, data={"csrf": csrf}, headers={"Origin": ORIGIN}) assert response.status_code == 200 assert "Account connected" in response.text assert worker.session == {"user_id": "alice", "credential": "personal-session", "expires_at": 86400.0} assert "personal-session" not in response.text assert "Max-Age=0" in response.headers["set-cookie"] @pytest.mark.parametrize("role,email", [("internal_user", "alice@example.com"), ("proxy_admin", "bob@example.com")]) def test_connect_rejects_nonadmin_and_another_slack_users_link( monkeypatch: pytest.MonkeyPatch, role: str, email: str, ) -> None: monkeypatch.setenv("PROXY_BASE_URL", ORIGIN) worker: Final = Worker(email=email) with client_for(worker, role=role) as client: response: Final = client.get(PATH) assert response.status_code == 403 assert worker.session is None @pytest.mark.parametrize("origin,csrf", [("https://attacker.example", None), ("null", None), (ORIGIN, "b" * 43)]) def test_connect_requires_same_origin_and_browser_csrf( monkeypatch: pytest.MonkeyPatch, origin: str, csrf: str | None, ) -> None: monkeypatch.setenv("PROXY_BASE_URL", ORIGIN) worker: Final = Worker() with client_for(worker) as client: valid: Final = csrf_from(client) response: Final = client.post(PATH, data={"csrf": csrf or valid}, headers={"Origin": origin}) assert response.status_code == 403 assert worker.session is None @pytest.mark.parametrize("status,expected", [(410, 410), (403, 403), (500, 503), (302, 503)]) def test_worker_denial_expiry_and_failure_never_mint_a_session( monkeypatch: pytest.MonkeyPatch, status: int, expected: int, ) -> None: monkeypatch.setenv("PROXY_BASE_URL", ORIGIN) worker: Final = Worker(status=status) with client_for(worker) as client: response: Final = client.get(PATH) assert response.status_code == expected assert worker.session is None def test_csrf_cookie_cannot_cross_links(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PROXY_BASE_URL", ORIGIN) worker: Final = Worker() with client_for(worker) as client: csrf: Final = csrf_from(client) response: Final = client.post(PATH.replace(TOKEN, "b" * 43), data={"csrf": csrf}, headers={"Origin": ORIGIN}) assert response.status_code == 403 assert worker.session is None @pytest.mark.parametrize( "url,secret,enterprise,database,status", [ ("", "s" * 32, True, True, 404), ("http://worker:10000", "s" * 32, False, True, 403), ("http://worker:10000", "s" * 32, True, False, 503), ("file:///etc/passwd", "s" * 32, True, True, 503), ("https://user:password@worker", "s" * 32, True, True, 503), ("https://worker/path", "s" * 32, True, True, 503), ("https://worker", "short", True, True, 503), ("http://[broken", "s" * 32, True, True, 503), ("http://worker:broken", "s" * 32, True, True, 503), ], ) def test_native_configuration_requires_enterprise_database_and_private_worker_credentials( url: str, secret: str, enterprise: bool, database: bool, status: int, ) -> None: from fastapi import HTTPException from litellm_enterprise.proxy.liteadmin import validate_native_configuration with pytest.raises(HTTPException) as error: validate_native_configuration(url, secret, enterprise, database) assert error.value.status_code == status def test_connect_rechecks_admin_permission_after_consent_page(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PROXY_BASE_URL", ORIGIN) worker: Final = Worker() with client_for(worker) as client: csrf: Final = csrf_from(client) worker.role = "internal_user" response: Final = client.post(PATH, data={"csrf": csrf}, headers={"Origin": ORIGIN}) assert response.status_code == 403 assert worker.session is None def test_consent_escapes_slack_email(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PROXY_BASE_URL", ORIGIN) email: Final = '@example.com' with client_for(Worker(email=email), email=email) as client: page: Final = client.get(PATH) assert page.status_code == 200 assert "