From dac620c00ec6a2673879f975e4a5d38f71ba3531 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Fri, 27 Feb 2026 20:30:24 -0800 Subject: [PATCH] remove demo scripts and assets --- .github/demo-assets/TEST_RESULTS.md | 72 ----- .github/demo-assets/fix1_fix2_results.webp | Bin 42460 -> 0 bytes .github/demo-assets/fix3_results.webp | Bin 42028 -> 0 bytes demo_jwt_fixes.py | 329 --------------------- demo_servers.py | 103 ------- 5 files changed, 504 deletions(-) delete mode 100644 .github/demo-assets/TEST_RESULTS.md delete mode 100644 .github/demo-assets/fix1_fix2_results.webp delete mode 100644 .github/demo-assets/fix3_results.webp delete mode 100644 demo_jwt_fixes.py delete mode 100644 demo_servers.py diff --git a/.github/demo-assets/TEST_RESULTS.md b/.github/demo-assets/TEST_RESULTS.md deleted file mode 100644 index 9e34862f9e4..00000000000 --- a/.github/demo-assets/TEST_RESULTS.md +++ /dev/null @@ -1,72 +0,0 @@ -## JWT Auth Fixes — Integration Test Results - -All three fixes verified end-to-end against a live LiteLLM proxy with mock JWKS/OIDC server, fake LLM backend, and RSA-signed JWTs. - -### Setup - -| Component | Port | Description | -|---|---|---| -| Mock JWKS + OIDC | :19900 | Serves `/.well-known/openid-configuration` → `jwks_uri` | -| Fake LLM | :19901 | Returns canned chat completions | -| LiteLLM Proxy | :19902 | JWT auth enabled, `premium_user` patched, `team_id_upsert: true` | - ---- - -### Fix 1: OIDC Discovery URL Resolution - -`JWT_PUBLIC_KEY_URL` set to `.well-known/openid-configuration` (not a direct JWKS URL). - -| Test | Token | Expected | Got | -|---|---|---|---| -| 1a | Valid JWT (RSA-signed, correct key) | **200** | ✅ **200** — chat completion returned | -| 1b | Tampered JWT (last 5 chars replaced) | **401** | ✅ **401** — "Signature verification failed" | - -The proxy correctly fetches the OIDC discovery doc, resolves `jwks_uri`, and validates the JWT signature. - ---- - -### Fix 2: Roles as Array - -Config: `team_id_jwt_field: "roles"` — JWT `roles` claim is a JSON array. - -| Test | Token Claims | Expected | Got | -|---|---|---|---| -| 2a | `"roles": ["team-beta", "team-gamma"]` | **200** (first element used) | ✅ **200** — `team-beta` extracted, completion returned | -| 2b | `"roles": []` | **401** | ✅ **401** — "No team found in token" | - -![Fix 1+2 results](https://raw.githubusercontent.com/BerriAI/litellm/68c86d22396ea4dce14e646dc9298c7b8e5f18ee/.github/demo-assets/fix1_fix2_results.webp) - ---- - -### Fix 3: Dot-notation Error Hint - -Config: `team_id_jwt_field: "roles.0"` — common Azure AD misconfiguration. - -| Test | Token Claims | Expected | Got | -|---|---|---|---| -| 3 | `"roles": ["team-alpha"]` | **401** with hint | ✅ **401** — hint says: *"Use 'roles' instead — LiteLLM automatically uses the first element when the field value is a list."* | - -![Fix 3 results](https://raw.githubusercontent.com/BerriAI/litellm/68c86d22396ea4dce14e646dc9298c7b8e5f18ee/.github/demo-assets/fix3_results.webp) - ---- - -### Unit Tests - -13/13 new tests pass: - -``` -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_resolve_jwks_url_passthrough_for_direct_jwks_url PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_resolve_jwks_url_resolves_oidc_discovery_document PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_resolve_jwks_url_caches_resolved_jwks_uri PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_resolve_jwks_url_raises_if_no_jwks_uri_in_discovery_doc PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_get_team_id_returns_first_element_when_roles_is_list PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_get_team_id_returns_first_element_from_multi_value_roles_list PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_get_team_id_returns_default_when_roles_list_is_empty PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_get_team_id_still_works_with_string_value PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_get_team_id_list_result_is_hashable PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_find_and_validate_specific_team_id_hints_bracket_notation PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_find_and_validate_specific_team_id_hints_bracket_index_notation PASSED -tests/test_litellm/proxy/auth/test_handle_jwt.py::test_find_and_validate_specific_team_id_no_hint_for_valid_field PASSED -``` - -All 13 passed ✅ diff --git a/.github/demo-assets/fix1_fix2_results.webp b/.github/demo-assets/fix1_fix2_results.webp deleted file mode 100644 index 389d8d5873dbb019bb3347f7829fe672c4c4fd7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42460 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 GVE+duMu1BI diff --git a/demo_jwt_fixes.py b/demo_jwt_fixes.py deleted file mode 100644 index a2edaa8663a..00000000000 --- a/demo_jwt_fixes.py +++ /dev/null @@ -1,329 +0,0 @@ -#!/usr/bin/env python3 -""" -Integration demo for JWT auth fixes on branch fix/jwt-auth-oidc-array-roles. - -Spins up: - - Mock JWKS + OIDC discovery server on port 19900 - - Fake LLM backend on port 19901 - - LiteLLM proxy on port 19902 (premium_user patched to bypass license) - -Tests three scenarios: - Fix 1 OIDC discovery URL resolution (valid JWT → 200, tampered → 401) - Fix 2 roles claim as JSON array (["team-beta","team-gamma"] → 200, [] → 401) - Fix 3 Helpful error for dot-notation (roles.0 → 401 with hint) -""" - -import asyncio, base64, json, os, signal, subprocess, sys, textwrap, time, threading -from http.server import HTTPServer, BaseHTTPRequestHandler - -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives import serialization -import jwt as pyjwt - -# ── 1. RSA key pair ────────────────────────────────────────────────────────── - -RSA_KEY = rsa.generate_private_key(public_exponent=65537, key_size=2048) -RSA_PUB = RSA_KEY.public_key() -PUB_NUMBERS = RSA_PUB.public_numbers() - - -def _b64url(n: int, length: int) -> str: - return base64.urlsafe_b64encode(n.to_bytes(length, "big")).rstrip(b"=").decode() - - -KID = "demo-kid-1" -JWKS = { - "keys": [{ - "kty": "RSA", "kid": KID, "use": "sig", "alg": "RS256", - "n": _b64url(PUB_NUMBERS.n, 256), - "e": _b64url(PUB_NUMBERS.e, 3), - }] -} - -PEM_PRIVATE = RSA_KEY.private_bytes( - serialization.Encoding.PEM, - serialization.PrivateFormat.PKCS8, - serialization.NoEncryption(), -) - -ISSUER = "https://demo-idp.example.com" -AUDIENCE = "litellm-proxy-demo" - - -def sign_token(claims: dict) -> str: - payload = {"iss": ISSUER, "aud": AUDIENCE, "exp": int(time.time()) + 3600, **claims} - return pyjwt.encode(payload, PEM_PRIVATE, algorithm="RS256", headers={"kid": KID}) - - -# ── 2. Mock JWKS + OIDC discovery server (port 19900) ──────────────────────── - -class JWKSHandler(BaseHTTPRequestHandler): - def do_GET(self): - if self.path == "/.well-known/openid-configuration": - body = json.dumps({ - "issuer": ISSUER, - "jwks_uri": "http://127.0.0.1:19900/jwks", - "authorization_endpoint": "https://example.com/authorize", - "token_endpoint": "https://example.com/token", - }).encode() - elif self.path == "/jwks": - body = json.dumps(JWKS).encode() - else: - self.send_response(404); self.end_headers(); return - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(body) - - def log_message(self, *_): - pass - - -# ── 3. Fake LLM backend (port 19901) ───────────────────────────────────────── - -class FakeLLMHandler(BaseHTTPRequestHandler): - def do_POST(self): - body = json.dumps({ - "id": "chatcmpl-fake", "object": "chat.completion", "created": 1700000000, - "model": "fake-model", - "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello from fake LLM!"}, "finish_reason": "stop"}], - "usage": {"prompt_tokens": 5, "completion_tokens": 7, "total_tokens": 12}, - }).encode() - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(body) - - def do_GET(self): - if "/models" in self.path: - body = json.dumps({"data": [{"id": "fake-model", "object": "model"}], "object": "list"}).encode() - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(body) - else: - self.send_response(404); self.end_headers() - - def log_message(self, *_): - pass - - -# ── 4. Proxy startup that patches premium_user ─────────────────────────────── - -PROXY_PORT = 19902 - -CONFIG_FIX12 = textwrap.dedent("""\ - model_list: - - model_name: fake-model - litellm_params: - model: openai/fake-model - api_key: fake-key - api_base: http://127.0.0.1:19901/ - general_settings: - enable_jwt_auth: true - litellm_jwtauth: - team_id_jwt_field: "roles" - team_id_default: "" - litellm_settings: - drop_params: true -""") - -CONFIG_FIX3 = textwrap.dedent("""\ - model_list: - - model_name: fake-model - litellm_params: - model: openai/fake-model - api_key: fake-key - api_base: http://127.0.0.1:19901/ - general_settings: - enable_jwt_auth: true - litellm_jwtauth: - team_id_jwt_field: "roles.0" - team_id_default: "" - litellm_settings: - drop_params: true -""") - - -def _write_config(content: str, path: str): - with open(path, "w") as f: - f.write(content) - - -PROXY_WRAPPER = textwrap.dedent("""\ - import sys, os, unittest.mock - os.environ["JWT_PUBLIC_KEY_URL"] = "http://127.0.0.1:19900/.well-known/openid-configuration" - os.environ["JWT_AUDIENCE"] = "{audience}" - os.environ["DISABLE_SCHEMA_UPDATE"] = "true" - # Patch premium_user BEFORE proxy imports the variable - import litellm.proxy.proxy_server as _ps - _ps.premium_user = True - from litellm.proxy.proxy_server import app, initialize - import asyncio, uvicorn - asyncio.get_event_loop().run_until_complete(initialize(config="{config_path}")) - uvicorn.run(app, host="127.0.0.1", port={port}, log_level="warning") -""") - - -def start_proxy(config_path: str) -> subprocess.Popen: - wrapper_code = PROXY_WRAPPER.format( - audience=AUDIENCE, - config_path=config_path, - port=PROXY_PORT, - ) - wrapper_path = "/tmp/_proxy_wrapper.py" - with open(wrapper_path, "w") as f: - f.write(wrapper_code) - proc = subprocess.Popen( - [sys.executable, wrapper_path], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - ) - return proc - - -def wait_for_proxy(timeout: int = 90) -> bool: - import urllib.request - deadline = time.time() + timeout - while time.time() < deadline: - try: - r = urllib.request.urlopen(f"http://127.0.0.1:{PROXY_PORT}/health/readiness", timeout=2) - if r.status == 200: - return True - except Exception: - pass - time.sleep(1) - return False - - -def stop_proxy(proc: subprocess.Popen): - proc.send_signal(signal.SIGTERM) - try: - proc.wait(timeout=10) - except subprocess.TimeoutExpired: - proc.kill() - - -def proxy_request(token: str, model: str = "fake-model") -> tuple: - import urllib.request, urllib.error - data = json.dumps({"model": model, "messages": [{"role": "user", "content": "Hi"}]}).encode() - req = urllib.request.Request( - f"http://127.0.0.1:{PROXY_PORT}/v1/chat/completions", - data=data, - headers={"Content-Type": "application/json", "Authorization": f"Bearer {token}"}, - ) - try: - r = urllib.request.urlopen(req, timeout=15) - return r.status, r.read().decode() - except urllib.error.HTTPError as e: - return e.code, e.read().decode() - - -# ── 5. Pretty output ───────────────────────────────────────────────────────── - -GREEN = "\033[92m" -RED = "\033[91m" -BOLD = "\033[1m" -RESET = "\033[0m" -results = [] - - -def header(text: str): - print(f"\n{BOLD}{'='*72}{RESET}") - print(f"{BOLD} {text}{RESET}") - print(f"{BOLD}{'='*72}{RESET}") - - -def check(label: str, condition: bool, detail: str = ""): - mark = f"{GREEN}PASS{RESET}" if condition else f"{RED}FAIL{RESET}" - print(f" [{mark}] {label}") - if detail: - for line in detail.strip().split("\n"): - print(f" {line}") - results.append((label, condition)) - - -# ── 6. Main ────────────────────────────────────────────────────────────────── - -def main(): - _write_config(CONFIG_FIX12, "/tmp/config_fix12.yaml") - _write_config(CONFIG_FIX3, "/tmp/config_fix3.yaml") - - print(f"{BOLD}Starting mock JWKS + OIDC discovery server on :19900 ...{RESET}") - jwks_srv = HTTPServer(("127.0.0.1", 19900), JWKSHandler) - threading.Thread(target=jwks_srv.serve_forever, daemon=True).start() - - print(f"{BOLD}Starting fake LLM backend on :19901 ...{RESET}") - llm_srv = HTTPServer(("127.0.0.1", 19901), FakeLLMHandler) - threading.Thread(target=llm_srv.serve_forever, daemon=True).start() - - # ── Fix 1 + 2 ──────────────────────────────────────────────────────── - header("Fix 1 + Fix 2: OIDC Discovery & Array Roles") - proxy = start_proxy("/tmp/config_fix12.yaml") - try: - print(f" Waiting for proxy on :{PROXY_PORT} (may take ~30s for Prisma) ...") - if not wait_for_proxy(): - print(f"{RED}Proxy failed to start!{RESET}") - out = proxy.stdout.read().decode() if proxy.stdout else "" - print(out[-3000:]) - raise SystemExit(1) - print(f" {GREEN}Proxy is up.{RESET}\n") - - # Fix 1a - token_ok = sign_token({"sub": "user-1", "roles": ["team-beta", "team-gamma"]}) - status, body = proxy_request(token_ok) - check("Fix 1 — OIDC discovery: valid JWT → HTTP 200", status == 200, f"status={status}") - - # Fix 1b - tampered = token_ok[:-5] + "XXXXX" - status, body = proxy_request(tampered) - check("Fix 1 — OIDC discovery: tampered JWT → HTTP 401", status == 401, f"status={status}") - - # Fix 2a - token_arr = sign_token({"sub": "user-2", "roles": ["team-beta", "team-gamma"]}) - status, body = proxy_request(token_arr) - check("Fix 2 — roles array ['team-beta','team-gamma'] → HTTP 200", status == 200, f"status={status}") - - # Fix 2b - token_empty = sign_token({"sub": "user-3", "roles": []}) - status, body = proxy_request(token_empty) - check("Fix 2 — empty roles [] → HTTP 401", status == 401, f"status={status}") - - finally: - stop_proxy(proxy) - time.sleep(2) - - # ── Fix 3 ───────────────────────────────────────────────────────────── - header("Fix 3: Dot-notation hint (roles.0)") - proxy = start_proxy("/tmp/config_fix3.yaml") - try: - print(f" Waiting for proxy on :{PROXY_PORT} ...") - if not wait_for_proxy(): - print(f"{RED}Proxy failed to start!{RESET}") - out = proxy.stdout.read().decode() if proxy.stdout else "" - print(out[-3000:]) - raise SystemExit(1) - print(f" {GREEN}Proxy is up.{RESET}\n") - - token_dot = sign_token({"sub": "user-4", "roles": ["team-alpha"]}) - status, body = proxy_request(token_dot) - hint_text = "Use 'roles' instead" - has_hint = hint_text in body - check( - "Fix 3 — roles.0 config → HTTP 401 with helpful hint", - status == 401 and has_hint, - f"status={status}, hint_present={has_hint}\n" - f"body excerpt: {body[:400]}", - ) - - finally: - stop_proxy(proxy) - - # ── Summary ─────────────────────────────────────────────────────────── - all_pass = all(ok for _, ok in results) - header("ALL TESTS PASSED" if all_pass else "SOME TESTS FAILED") - print() - raise SystemExit(0 if all_pass else 1) - - -if __name__ == "__main__": - main() diff --git a/demo_servers.py b/demo_servers.py deleted file mode 100644 index 3e941d3b690..00000000000 --- a/demo_servers.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -""" -Start mock JWKS/OIDC server (:19900) and fake LLM backend (:19901). -Also writes signed JWTs to /tmp/tokens.env for use in shell demos. -Runs forever — Ctrl-C to stop. -""" - -import base64, json, os, time, threading -from http.server import HTTPServer, BaseHTTPRequestHandler -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives import serialization -import jwt as pyjwt - -# ── RSA key pair ────────────────────────────────────────────────────────────── -RSA_KEY = rsa.generate_private_key(public_exponent=65537, key_size=2048) -RSA_PUB = RSA_KEY.public_key() -PUB_NUMBERS = RSA_PUB.public_numbers() - -def _b64url(n, length): - return base64.urlsafe_b64encode(n.to_bytes(length, "big")).rstrip(b"=").decode() - -KID = "demo-kid-1" -JWKS = {"keys": [{"kty": "RSA", "kid": KID, "use": "sig", "alg": "RS256", - "n": _b64url(PUB_NUMBERS.n, 256), "e": _b64url(PUB_NUMBERS.e, 3)}]} - -PEM_PRIVATE = RSA_KEY.private_bytes( - serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()) - -ISSUER = "https://demo-idp.example.com" -AUDIENCE = "litellm-proxy-demo" - -def sign_token(claims): - payload = {"iss": ISSUER, "aud": AUDIENCE, "exp": int(time.time()) + 3600, **claims} - return pyjwt.encode(payload, PEM_PRIVATE, algorithm="RS256", headers={"kid": KID}) - -# ── Write tokens to file ───────────────────────────────────────────────────── -valid_token = sign_token({"sub": "user-1", "roles": ["team-beta", "team-gamma"]}) -tampered_token = valid_token[:-5] + "XXXXX" -empty_roles_token = sign_token({"sub": "user-2", "roles": []}) -single_role_token = sign_token({"sub": "user-3", "roles": ["team-alpha"]}) - -with open("/tmp/tokens.env", "w") as f: - f.write(f'VALID_TOKEN="{valid_token}"\n') - f.write(f'TAMPERED_TOKEN="{tampered_token}"\n') - f.write(f'EMPTY_ROLES_TOKEN="{empty_roles_token}"\n') - f.write(f'SINGLE_ROLE_TOKEN="{single_role_token}"\n') - -print("[tokens] Written to /tmp/tokens.env") - -# ── Mock JWKS + OIDC discovery server ──────────────────────────────────────── -class JWKSHandler(BaseHTTPRequestHandler): - def do_GET(self): - if self.path == "/.well-known/openid-configuration": - body = json.dumps({"issuer": ISSUER, - "jwks_uri": "http://127.0.0.1:19900/jwks", - "authorization_endpoint": "https://example.com/authorize", - "token_endpoint": "https://example.com/token"}).encode() - elif self.path == "/jwks": - body = json.dumps(JWKS).encode() - else: - self.send_response(404); self.end_headers(); return - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(body) - def log_message(self, *_): pass - -# ── Fake LLM backend ───────────────────────────────────────────────────────── -class FakeLLMHandler(BaseHTTPRequestHandler): - def do_POST(self): - body = json.dumps({"id": "chatcmpl-fake", "object": "chat.completion", - "created": 1700000000, "model": "fake-model", - "choices": [{"index": 0, "message": {"role": "assistant", - "content": "Hello from fake LLM!"}, "finish_reason": "stop"}], - "usage": {"prompt_tokens": 5, "completion_tokens": 7, "total_tokens": 12}}).encode() - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(body) - def do_GET(self): - if "/models" in self.path: - body = json.dumps({"data": [{"id": "fake-model", "object": "model"}], "object": "list"}).encode() - self.send_response(200); self.send_header("Content-Type", "application/json") - self.end_headers(); self.wfile.write(body) - else: - self.send_response(404); self.end_headers() - def log_message(self, *_): pass - -# ── Start servers ───────────────────────────────────────────────────────────── -print("[JWKS] Starting on :19900 (OIDC discovery + JWKS)") -jwks_srv = HTTPServer(("127.0.0.1", 19900), JWKSHandler) -threading.Thread(target=jwks_srv.serve_forever, daemon=True).start() - -print("[LLM] Starting fake LLM backend on :19901") -llm_srv = HTTPServer(("127.0.0.1", 19901), FakeLLMHandler) -threading.Thread(target=llm_srv.serve_forever, daemon=True).start() - -print("[READY] Mock servers running. Press Ctrl-C to stop.") -try: - while True: - time.sleep(60) -except KeyboardInterrupt: - print("\nShutting down.")