mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(auth): guard _team_obj_from_token against team-less tokens
The HTTPException arm in _run_centralized_common_checks assumed the exception came from the team-object fetch, but asyncio.gather raises the first exception from any of the five gathered coroutines. If get_user_object / get_project_object / get_end_user_object raises HTTPException on a token with team_id=None, the assert inside _team_obj_from_token fires and the outer auth-exception handler mishandles it. Guard on team_id before calling _team_obj_from_token and default team_object to None otherwise. (Greptile P1.)
This commit is contained in:
parent
fb55e2f1b9
commit
b5c78d7db4
2 changed files with 56 additions and 3 deletions
|
|
@ -1716,9 +1716,14 @@ async def _run_centralized_common_checks(
|
|||
global_proxy_spend,
|
||||
) = await asyncio.gather(*fetch_coros, return_exceptions=False)
|
||||
except HTTPException:
|
||||
# get_team_object raises HTTPException when the team isn't found.
|
||||
# Reconstruct from the token so enforcement can still run.
|
||||
team_object = _team_obj_from_token(user_api_key_auth_obj)
|
||||
# Any of the five gathered fetches can raise HTTPException. Only
|
||||
# reconstruct from the token when a team_id is known — otherwise
|
||||
# the exception came from a different fetch and the assert in
|
||||
# _team_obj_from_token would fire.
|
||||
if user_api_key_auth_obj.team_id is not None:
|
||||
team_object = _team_obj_from_token(user_api_key_auth_obj)
|
||||
else:
|
||||
team_object = None
|
||||
user_object = None
|
||||
project_object = None
|
||||
end_user_object = None
|
||||
|
|
|
|||
|
|
@ -2050,3 +2050,51 @@ async def test_centralized_common_checks_short_circuits_when_master_key_unset():
|
|||
finally:
|
||||
for k, v in originals.items():
|
||||
setattr(_proxy_server_mod, k, v)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_centralized_common_checks_http_exception_without_team_id():
|
||||
"""Regression: an HTTPException raised by any of the five parallel
|
||||
fetches (user/project/end_user/global_spend) must not trigger the
|
||||
_team_obj_from_token reconstruction when the token has no team_id —
|
||||
the helper asserts team_id is not None. This is the Greptile P1
|
||||
finding: the ``except HTTPException`` arm was team-fetch-biased."""
|
||||
import litellm.proxy.proxy_server as _proxy_server_mod
|
||||
from fastapi import HTTPException, Request
|
||||
from starlette.datastructures import URL
|
||||
|
||||
token = UserAPIKeyAuth(api_key="sk-test", user_id="u", team_id=None)
|
||||
request = Request(scope={"type": "http"})
|
||||
request._url = URL(url="/chat/completions")
|
||||
|
||||
attrs = _proxy_attrs_for_centralized_checks(user_custom_auth=None)
|
||||
originals = {a: getattr(_proxy_server_mod, a, None) for a in attrs}
|
||||
try:
|
||||
for k, v in attrs.items():
|
||||
setattr(_proxy_server_mod, k, v)
|
||||
# Make the user fetch raise HTTPException. asyncio.gather with
|
||||
# return_exceptions=False propagates it.
|
||||
with (
|
||||
patch(
|
||||
"litellm.proxy.auth.user_api_key_auth.get_user_object",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=HTTPException(status_code=404, detail="user-not-found"),
|
||||
),
|
||||
patch(
|
||||
"litellm.proxy.auth.user_api_key_auth.common_checks",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_checks,
|
||||
):
|
||||
# Should NOT raise AssertionError from _team_obj_from_token;
|
||||
# should proceed with team_object=None.
|
||||
await _run_centralized_common_checks(
|
||||
user_api_key_auth_obj=token,
|
||||
request=request,
|
||||
request_data={"model": "gpt-4o"},
|
||||
route="/chat/completions",
|
||||
)
|
||||
mock_checks.assert_awaited_once()
|
||||
assert mock_checks.call_args.kwargs["team_object"] is None
|
||||
finally:
|
||||
for k, v in originals.items():
|
||||
setattr(_proxy_server_mod, k, v)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue