mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge 447f2b0fbd into e6c4580a31
This commit is contained in:
commit
5c0ebcfafd
4 changed files with 112 additions and 1 deletions
|
|
@ -690,6 +690,7 @@ class LiteLLMRoutes(enum.Enum):
|
|||
"/spend/logs",
|
||||
"/spend/logs/v2",
|
||||
"/spend/logs/ui",
|
||||
"/spend/logs/ui/{request_id}",
|
||||
"/spend/logs/session/ui",
|
||||
"/key/spend/report",
|
||||
"/user/spend/report",
|
||||
|
|
|
|||
|
|
@ -4288,7 +4288,14 @@ async def _assert_user_can_view_request_id(
|
|||
"""
|
||||
row: Final = await _find_spend_log_row(prisma_client, request_id)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1977,6 +1977,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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -451,6 +451,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",
|
||||
"metadata.litellm_call_id",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue