mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-26 06:03:19 +00:00
Merge d95d79093c into 783cf06f31
This commit is contained in:
commit
d65dff2633
5 changed files with 80 additions and 7 deletions
|
|
@ -184,7 +184,7 @@ from litellm.router_utils.fallback_event_handlers import (
|
|||
fallbacks_disabled_for_request,
|
||||
get_fallback_model_group_for_lookup_groups,
|
||||
)
|
||||
from litellm.secret_managers.main import get_secret_bool
|
||||
from litellm.secret_managers.main import get_secret_bool, get_secret_str, normalize_nonempty_secret_str
|
||||
from litellm.types.services import ServiceTypes
|
||||
|
||||
try:
|
||||
|
|
@ -2737,6 +2737,18 @@ async def user_api_key_auth_builder(
|
|||
if (
|
||||
prisma_client is None
|
||||
): # if both master key + user key submitted, and user key != master key, and no db connected, raise an error
|
||||
if normalize_nonempty_secret_str(get_secret_str("DATABASE_URL")) is None:
|
||||
# No DB configured: an auth error, so allow_requests_on_db_unavailable can't fail open
|
||||
raise ProxyException(
|
||||
message=(
|
||||
"Authentication Error, Invalid proxy server token passed. Token does not match "
|
||||
"the configured master key, and no database is configured to look up virtual keys. "
|
||||
"Set DATABASE_URL to enable virtual keys."
|
||||
),
|
||||
type=ProxyErrorTypes.auth_error,
|
||||
code=status.HTTP_401_UNAUTHORIZED,
|
||||
param=None,
|
||||
)
|
||||
raise ProxyException(
|
||||
message="No connected db.",
|
||||
type=ProxyErrorTypes.no_db_connection,
|
||||
|
|
|
|||
|
|
@ -12073,7 +12073,7 @@ class ProxyStartupEvent:
|
|||
"""
|
||||
connected_client: PrismaClient | None = None
|
||||
try:
|
||||
if database_url is None:
|
||||
if database_url is None or not database_url.strip():
|
||||
return None
|
||||
|
||||
prisma_client = PrismaClient(database_url=database_url, proxy_logging_obj=proxy_logging_obj)
|
||||
|
|
|
|||
|
|
@ -476,9 +476,9 @@ def test_is_api_route_allowed(route, user_role, expected_result):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auth_not_connected_to_db():
|
||||
async def test_auth_not_connected_to_db(monkeypatch: pytest.MonkeyPatch):
|
||||
"""
|
||||
ensure requests don't fail when `prisma_client` = None
|
||||
ensure requests don't fail when `prisma_client` = None but a database is configured
|
||||
"""
|
||||
from fastapi import Request
|
||||
from starlette.datastructures import URL
|
||||
|
|
@ -486,6 +486,7 @@ async def test_auth_not_connected_to_db():
|
|||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.proxy_server import hash_token, user_api_key_cache
|
||||
|
||||
monkeypatch.setenv("DATABASE_URL", "postgresql://unreachable.invalid:5432/litellm")
|
||||
user_key = "sk-98765678"
|
||||
|
||||
setattr(litellm.proxy.proxy_server, "user_api_key_cache", user_api_key_cache)
|
||||
|
|
@ -506,6 +507,46 @@ async def test_auth_not_connected_to_db():
|
|||
assert valid_token.token == "failed-to-connect-to-db"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("allow_requests_on_db_unavailable", [True, False])
|
||||
@pytest.mark.parametrize("database_url", [None, "", " "])
|
||||
async def test_auth_no_db_configured_rejects_non_master_key(
|
||||
allow_requests_on_db_unavailable: bool, database_url: str | None, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""
|
||||
With no database configured (DATABASE_URL unset or empty), a key that isn't the master key is an
|
||||
auth error (401), and allow_requests_on_db_unavailable must not fail open: there is no outage to ride out.
|
||||
"""
|
||||
from fastapi import Request
|
||||
from starlette.datastructures import URL
|
||||
|
||||
from litellm.proxy._types import ProxyErrorTypes, ProxyException
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.proxy_server import user_api_key_cache
|
||||
|
||||
if database_url is None:
|
||||
monkeypatch.delenv("DATABASE_URL", raising=False)
|
||||
else:
|
||||
monkeypatch.setenv("DATABASE_URL", database_url)
|
||||
setattr(litellm.proxy.proxy_server, "user_api_key_cache", user_api_key_cache)
|
||||
setattr(litellm.proxy.proxy_server, "master_key", MASTER_KEY)
|
||||
setattr(litellm.proxy.proxy_server, "prisma_client", None)
|
||||
setattr(
|
||||
litellm.proxy.proxy_server,
|
||||
"general_settings",
|
||||
{"allow_requests_on_db_unavailable": allow_requests_on_db_unavailable},
|
||||
)
|
||||
|
||||
request = Request(scope={"type": "http", "method": "POST", "path": "/chat/completions", "headers": []})
|
||||
request._url = URL(url="/chat/completions")
|
||||
|
||||
with pytest.raises(ProxyException) as exc_info:
|
||||
await user_api_key_auth(request=request, api_key="Bearer sk-not-the-master-key")
|
||||
|
||||
assert exc_info.value.type == ProxyErrorTypes.auth_error
|
||||
assert exc_info.value.code == "401"
|
||||
|
||||
|
||||
def _assert_api_key_from_custom_header(headers, custom_header_name, expected_api_key):
|
||||
verbose_proxy_logger.setLevel(logging.DEBUG)
|
||||
request = MagicMock(spec=Request)
|
||||
|
|
|
|||
|
|
@ -12502,10 +12502,11 @@ async def test_lens_oauth2_validates_identity_without_inference_user_field(
|
|||
[("/lens", False, True), ("/lens/traces", True, False), ("/lens-other", True, True)],
|
||||
)
|
||||
async def test_lens_oauth2_dispatch_preserves_opt_in_premium_and_route_boundary(
|
||||
route: str, enabled: bool, premium: bool
|
||||
route: str, enabled: bool, premium: bool, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
from starlette.requests import Request
|
||||
|
||||
monkeypatch.delenv("DATABASE_URL", raising=False)
|
||||
request: Final = Request({"type": "http", "method": "GET", "path": route, "headers": [], "query_string": b""})
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.general_settings", {"enable_oauth2_auth": enabled}),
|
||||
|
|
@ -12522,8 +12523,8 @@ async def test_lens_oauth2_dispatch_preserves_opt_in_premium_and_route_boundary(
|
|||
assert error.value.code == "403"
|
||||
assert "premium" in error.value.message.lower()
|
||||
else:
|
||||
assert error.value.code == "400"
|
||||
assert "No connected db" in error.value.message
|
||||
assert error.value.code == "401"
|
||||
assert "no database is configured" in error.value.message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -14105,6 +14105,25 @@ async def test_setup_prisma_client_retains_connected_client_when_startup_health_
|
|||
assert result is mock_client
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("database_url", ["", " "])
|
||||
async def test_setup_prisma_client_treats_an_empty_database_url_as_unset(database_url):
|
||||
"""An empty DATABASE_URL (e.g. from ``${DATABASE_URL:-}``) can never connect, so no client is
|
||||
created. Otherwise auth sees a configured-but-unreachable DB and allow_requests_on_db_unavailable
|
||||
fails open for any key."""
|
||||
from litellm.proxy.proxy_server import ProxyStartupEvent
|
||||
|
||||
mock_client = _mock_startup_prisma_client()
|
||||
with patch.object(proxy_server_module, "PrismaClient", return_value=mock_client):
|
||||
result = await ProxyStartupEvent._setup_prisma_client(
|
||||
database_url=database_url,
|
||||
proxy_logging_obj=MagicMock(),
|
||||
user_api_key_cache=DualCache(),
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setup_prisma_client_arms_health_watchdog_before_startup_health_check(
|
||||
monkeypatch,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue