mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-12 03:48:56 +00:00
* feat(lens): consume the independent Lens service and shared UI * fix(lens): keep embedded setup stable and vendor UI before builds * chore(lens): pin embedded UI provenance to published Lens source * fix(lens): complete embedded UI extraction across CI builds * docs(lens): record latest main and removal-boundary validation * fix(lens): wire external chart credentials and ingestion modes * test(lens): cover adapter failures and fix extraction CI * docs(lens): refresh bundled chart setup instructions * test(lens): restore gateway adapter test package marker * build(lens): pin clean-cut UI and supported storage chart * test(ui): await guardrail scope menu before checking options * perf(lens): omit unused trace-team lookup from product requests * docs(lens): record final-image adapter permission checks * test(lens): assert delegated investigation authorization * refactor(lens): remove copied trace runtime and preserve spend logging * test(lens): verify independent gateway image upgrades and outages * test(lens): pin the chart qualification database image * fix(lens): update embedded UI metadata filters * test: wait for Lens chart pod identity convergence * fix(e2e): select Lens chart pods by deployment ownership * fix(e2e): select Helm migration ownership for Lens upgrades * test(lens): retain migration Jobs through rollout assertions * docs(helm): require existing database for migration hooks * test(lens): qualify release boundaries with embedded navigation update * docs(lens): record browser qualification for embedded tabs * fix(lens): remove unrelated CI and guardrail test changes * test(lens): give qualification tenants unique key aliases * feat(lens): embed the latest canonical Lens interface * fix(lens): qualify split inference through the gateway * fix(lens): preserve scoped auth and isolate delegated credentials * chore(lens): remove unrelated documentation and lint changes * fix(lens): bound concurrent forwarding buffers through response delivery * fix(lens): preserve OAuth2 dispatch without inference policies * chore(lens): adopt current shared onboarding UI * fix(lens): refresh shared setup UI and review context * fix(lens): bound uploads before gateway authentication * docs(lens): remove extraction evidence from the gateway repo * docs(lens): refresh shared setup prompt for paired releases
125 lines
4.8 KiB
Python
125 lines
4.8 KiB
Python
from collections.abc import Mapping
|
|
from typing import Final
|
|
|
|
import httpx
|
|
import jwt
|
|
import pytest
|
|
from fastapi import FastAPI, Header, HTTPException
|
|
|
|
from litellm.integrations.clickhouse.context import is_lens_analysis
|
|
from litellm.litellm_core_utils.initialize_dynamic_callback_params import initialize_standard_callback_dynamic_params
|
|
from litellm.proxy.lens.internal import LensInternalMiddleware, verified
|
|
|
|
SECRET: Final = "gateway-internal-identity-fixture-key-32"
|
|
NOW: Final = 1_800_000_000
|
|
|
|
|
|
def claims(**changes: object) -> Mapping[str, object]:
|
|
return {
|
|
"iss": "litellm-lens",
|
|
"aud": "litellm",
|
|
"sub": "lens-internal",
|
|
"purpose": "analysis",
|
|
"iat": NOW,
|
|
"exp": NOW + 30,
|
|
**changes,
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"changes,valid",
|
|
(
|
|
({}, True),
|
|
({"purpose": "signals"}, True),
|
|
({"purpose": "billing"}, False),
|
|
({"iss": "unknown"}, False),
|
|
({"aud": "unknown"}, False),
|
|
({"sub": "admin"}, False),
|
|
({"exp": NOW}, False),
|
|
({"iat": NOW + 1, "exp": NOW + 31}, True),
|
|
({"iat": NOW + 5, "exp": NOW + 35}, True),
|
|
({"iat": NOW + 6, "exp": NOW + 36}, False),
|
|
({"iat": NOW + 5, "exp": NOW + 5}, False),
|
|
({"iat": NOW + 5, "exp": NOW + 4}, False),
|
|
({"exp": NOW + 61}, False),
|
|
({"iat": True}, False),
|
|
({"exp": str(NOW + 30)}, False),
|
|
({"unknown": "value"}, False),
|
|
({"iat": NOW - 59, "exp": NOW + 1}, True),
|
|
),
|
|
ids=(
|
|
"analysis",
|
|
"signals",
|
|
"purpose",
|
|
"issuer",
|
|
"audience",
|
|
"subject",
|
|
"expired",
|
|
"small_clock_skew",
|
|
"clock_skew_boundary",
|
|
"excessive_clock_skew",
|
|
"zero_lifetime",
|
|
"negative_lifetime",
|
|
"ttl",
|
|
"bool_timestamp",
|
|
"string_timestamp",
|
|
"unknown_claim",
|
|
"maximum_ttl",
|
|
),
|
|
)
|
|
def test_only_bounded_internal_identity_is_accepted(changes: Mapping[str, object], valid: bool) -> None:
|
|
token: Final = jwt.encode(dict(claims(**changes)), SECRET, algorithm="HS256")
|
|
assert verified(token, SECRET, NOW) is valid
|
|
|
|
|
|
@pytest.mark.parametrize("secret", ("", "short", SECRET + "wrong"), ids=("unset", "short", "wrong"))
|
|
def test_marker_does_not_authorize_itself(secret: str) -> None:
|
|
token: Final = jwt.encode(dict(claims()), SECRET, algorithm="HS256")
|
|
assert not verified(token, secret, NOW)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("authenticated", (True, False), ids=("authenticated", "unauthenticated"))
|
|
async def test_internal_marker_preserves_authentication_and_is_private(authenticated: bool) -> None:
|
|
app: Final = FastAPI()
|
|
|
|
@app.post("/chat/completions")
|
|
async def completion(
|
|
authorization: str | None = Header(default=None), x_lens_internal: str | None = Header(default=None)
|
|
) -> Mapping[str, object]:
|
|
if authorization != "Bearer gateway-key":
|
|
raise HTTPException(401, "Key required")
|
|
return {
|
|
"internal": is_lens_analysis(),
|
|
"marker": x_lens_internal,
|
|
"privacy": initialize_standard_callback_dynamic_params({}).get("turn_off_message_logging"),
|
|
}
|
|
|
|
app.add_middleware(LensInternalMiddleware, environ={"LENS_GATEWAY_SECRET": SECRET}, now=lambda: NOW)
|
|
token: Final = jwt.encode(dict(claims()), SECRET, algorithm="HS256")
|
|
headers: Final = {"X-Lens-Internal": token, **({"Authorization": "Bearer gateway-key"} if authenticated else {})}
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://gateway.test") as client:
|
|
response: Final = await client.post("/chat/completions", headers=headers)
|
|
ordinary: Final = await client.post("/chat/completions", headers={"Authorization": "Bearer gateway-key"})
|
|
assert response.status_code == (200 if authenticated else 401)
|
|
if authenticated:
|
|
assert response.json() == {"internal": True, "marker": None, "privacy": True}
|
|
assert ordinary.json() == {"internal": False, "marker": None, "privacy": None}
|
|
assert not is_lens_analysis()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_duplicate_markers_are_rejected_before_inference() -> None:
|
|
app: Final = FastAPI()
|
|
|
|
@app.post("/chat/completions")
|
|
async def completion() -> Mapping[str, object]:
|
|
pytest.fail("Invalid duplicate marker reached inference")
|
|
|
|
app.add_middleware(LensInternalMiddleware, environ={"LENS_GATEWAY_SECRET": SECRET}, now=lambda: NOW)
|
|
token: Final = jwt.encode(dict(claims()), SECRET, algorithm="HS256")
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://gateway.test") as client:
|
|
response: Final = await client.post(
|
|
"/chat/completions", headers=[("X-Lens-Internal", token), ("X-Lens-Internal", "forged")]
|
|
)
|
|
assert response.status_code == 401
|