mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
Fix info disclosure via status codes and remove redundant user_id field
Move access control check before DB query for non-admin, non-self requests so unauthorized callers always get 403 (cannot distinguish "not found" from "not authorized"). Remove top-level user_id from UserInfoV2Response since it already exists inside user_info. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f4d29cc268
commit
4ff02a4c65
3 changed files with 21 additions and 20 deletions
|
|
@ -770,22 +770,18 @@ async def user_info_v2(
|
|||
detail="user_id is required",
|
||||
)
|
||||
|
||||
user_info = await prisma_client.get_data(user_id=user_id)
|
||||
# Access control check before DB query to avoid information disclosure
|
||||
# (different error codes for "not found" vs "not authorized")
|
||||
is_proxy_admin = user_api_key_dict.user_role in (
|
||||
LitellmUserRoles.PROXY_ADMIN,
|
||||
LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY,
|
||||
)
|
||||
is_self = user_id == user_api_key_dict.user_id
|
||||
|
||||
if user_info is None:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"User {user_id} not found",
|
||||
)
|
||||
|
||||
# Access control: non-admin users can only query their own info
|
||||
# or info of users in teams they admin
|
||||
if (
|
||||
user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN
|
||||
and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY
|
||||
and user_id != user_api_key_dict.user_id
|
||||
):
|
||||
if not await _is_team_admin_for_user(
|
||||
if not is_proxy_admin and not is_self:
|
||||
# Need to check team admin status — fetch user first
|
||||
user_info = await prisma_client.get_data(user_id=user_id)
|
||||
if user_info is None or not await _is_team_admin_for_user(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
target_user_teams=user_info.teams,
|
||||
prisma_client=prisma_client,
|
||||
|
|
@ -796,9 +792,15 @@ async def user_info_v2(
|
|||
user_api_key_dict.user_id, user_id
|
||||
),
|
||||
)
|
||||
else:
|
||||
user_info = await prisma_client.get_data(user_id=user_id)
|
||||
if user_info is None:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"User {user_id} not found",
|
||||
)
|
||||
|
||||
return UserInfoV2Response(
|
||||
user_id=user_id,
|
||||
user_info=user_info.model_dump(),
|
||||
)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ class UserInfoV2Response(BaseModel):
|
|||
via /key/list and /v2/team/list respectively.
|
||||
"""
|
||||
|
||||
user_id: str
|
||||
user_info: LiteLLM_UserTable
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1336,7 +1336,7 @@ async def test_user_info_v2_returns_user_profile(mocker):
|
|||
request=mock_request,
|
||||
)
|
||||
|
||||
assert response.user_id == "test-user-123"
|
||||
assert response.user_info.user_id == "test-user-123"
|
||||
assert response.user_info.user_email == "test@example.com"
|
||||
assert response.user_info.teams == ["team-1", "team-2"]
|
||||
# Verify no keys or teams fields on the response
|
||||
|
|
@ -1383,7 +1383,7 @@ async def test_user_info_v2_falls_back_to_caller_user_id(mocker):
|
|||
request=mock_request,
|
||||
)
|
||||
|
||||
assert response.user_id == "caller-user-id"
|
||||
assert response.user_info.user_id == "caller-user-id"
|
||||
assert response.user_info.user_email == "caller@example.com"
|
||||
|
||||
|
||||
|
|
@ -1606,7 +1606,7 @@ async def test_user_info_v2_team_admin_can_query_team_member(mocker):
|
|||
request=mock_request,
|
||||
)
|
||||
|
||||
assert result.user_id == "user-B"
|
||||
assert result.user_info.user_id == "user-B"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue