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 "