fix(proxy): allow internal users to view log details

This commit is contained in:
Frank Deng 2026-08-07 19:36:59 -07:00
parent e84282b7b3
commit 447f2b0fbd
4 changed files with 112 additions and 1 deletions

View file

@ -592,6 +592,7 @@ class LiteLLMRoutes(enum.Enum):
"/spend/calculate",
"/spend/logs",
"/spend/logs/ui",
"/spend/logs/ui/{request_id}",
"/spend/logs/session/ui",
"/cost/estimate",
]

View file

@ -3577,7 +3577,14 @@ async def _assert_user_can_view_request_id(
include=None,
)
if row is None:
return
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail={
"error": "Not authorized to view spend log for request_id={}".format(
request_id
)
},
)
if row.user is not None and row.user == user_api_key_dict.user_id:
return

View file

@ -1296,6 +1296,38 @@ ADMIN_VIEWER_LOGS_PAGE_ROUTES = [
]
@pytest.mark.parametrize(
"user_role",
[
LitellmUserRoles.INTERNAL_USER,
LitellmUserRoles.INTERNAL_USER_VIEW_ONLY,
],
)
def test_internal_user_can_access_spend_log_detail(
user_role: LitellmUserRoles,
) -> None:
user_obj = LiteLLM_UserTable(
user_id="internal_user",
user_email="user@example.com",
user_role=user_role.value,
)
valid_token = UserAPIKeyAuth(
user_id="internal_user",
user_role=user_role.value,
)
request = MagicMock(spec=Request)
request.query_params = {}
RouteChecks.non_proxy_admin_allowed_routes_check(
user_obj=user_obj,
_user_role=user_role.value,
route="/spend/logs/ui/request-id",
request=request,
valid_token=valid_token,
request_data={},
)
@pytest.mark.parametrize("route", ADMIN_VIEWER_LOGS_PAGE_ROUTES)
def test_proxy_admin_viewer_can_access_logs_page_endpoints(route):
"""

View file

@ -340,6 +340,77 @@ def test_ui_view_request_response_forbids_non_admin_without_db(client, monkeypat
app.dependency_overrides.pop(ps.user_api_key_auth, None)
def test_ui_view_request_response_forbids_missing_ownership_row(
client: TestClient,
monkeypatch: pytest.MonkeyPatch,
) -> None:
mock_prisma = MagicMock()
mock_prisma.db.litellm_spendlogs.find_unique = AsyncMock(return_value=None)
payload_logger = MagicMock()
payload_logger.get_request_response_payload = AsyncMock(
return_value={"response": "private response"}
)
get_custom_loggers = MagicMock(return_value=[payload_logger])
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
monkeypatch.setattr(
litellm.logging_callback_manager,
"get_active_additional_logging_utils_from_custom_logger",
get_custom_loggers,
)
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
user_role=LitellmUserRoles.INTERNAL_USER,
user_id="user_1",
)
try:
response = client.get(
"/spend/logs/ui/missing-request",
headers={"Authorization": "Bearer sk-test"},
)
assert response.status_code == 403
get_custom_loggers.assert_not_called()
payload_logger.get_request_response_payload.assert_not_awaited()
finally:
app.dependency_overrides.pop(ps.user_api_key_auth, None)
def test_ui_view_request_response_forbids_different_user(
client: TestClient,
monkeypatch: pytest.MonkeyPatch,
) -> None:
spend_log_row = MagicMock()
spend_log_row.user = "user_2"
spend_log_row.team_id = None
mock_prisma = MagicMock()
mock_prisma.db.litellm_spendlogs.find_unique = AsyncMock(return_value=spend_log_row)
payload_logger = MagicMock()
payload_logger.get_request_response_payload = AsyncMock(
return_value={"response": "private response"}
)
get_custom_loggers = MagicMock(return_value=[payload_logger])
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
monkeypatch.setattr(
litellm.logging_callback_manager,
"get_active_additional_logging_utils_from_custom_logger",
get_custom_loggers,
)
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
user_role=LitellmUserRoles.INTERNAL_USER,
user_id="user_1",
)
try:
response = client.get(
"/spend/logs/ui/other-user-request",
headers={"Authorization": "Bearer sk-test"},
)
assert response.status_code == 403
get_custom_loggers.assert_not_called()
payload_logger.get_request_response_payload.assert_not_awaited()
finally:
app.dependency_overrides.pop(ps.user_api_key_auth, None)
ignored_keys = [
"request_id",
"session_id",