mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
Allow /global/spend/report when virtual key allowlists the route
- Replace admin-only guard with _require_global_spend_report_access: proxy admin roles unchanged; others pass if allowed_routes matches via RouteChecks - Tests for internal user with path or global_spend_report_routes bundle Made-with: Cursor
This commit is contained in:
parent
00e45f2cc0
commit
5baf972a96
3 changed files with 77 additions and 3 deletions
|
|
@ -12,6 +12,7 @@ import litellm
|
|||
from litellm._logging import verbose_proxy_logger
|
||||
from litellm.proxy._types import *
|
||||
from litellm.proxy._types import ProviderBudgetResponse, ProviderBudgetResponseObject
|
||||
from litellm.proxy.auth.route_checks import RouteChecks
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
|
||||
# NOTE: Avoid module-level import from common_utils: proxy_server imports this
|
||||
|
|
@ -31,12 +32,28 @@ else:
|
|||
router = APIRouter()
|
||||
|
||||
|
||||
def _require_proxy_admin_or_viewer(user_api_key_dict: UserAPIKeyAuth) -> None:
|
||||
_GLOBAL_SPEND_REPORT_ROUTE = "/global/spend/report"
|
||||
|
||||
|
||||
def _require_global_spend_report_access(user_api_key_dict: UserAPIKeyAuth) -> None:
|
||||
"""
|
||||
Allow proxy admin roles, or any role when the virtual key allowlists this route
|
||||
(same matching rules as RouteChecks.is_virtual_key_allowed_to_call_route).
|
||||
"""
|
||||
if user_api_key_dict.user_role in (
|
||||
LitellmUserRoles.PROXY_ADMIN,
|
||||
LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY,
|
||||
):
|
||||
return
|
||||
if user_api_key_dict.allowed_routes:
|
||||
try:
|
||||
RouteChecks.is_virtual_key_allowed_to_call_route(
|
||||
route=_GLOBAL_SPEND_REPORT_ROUTE,
|
||||
valid_token=user_api_key_dict,
|
||||
)
|
||||
return
|
||||
except HTTPException:
|
||||
pass
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail={"error": CommonProxyErrors.not_allowed_access.value},
|
||||
|
|
@ -1021,7 +1038,7 @@ async def get_global_spend_report(
|
|||
]
|
||||
}
|
||||
"""
|
||||
_require_proxy_admin_or_viewer(user_api_key_dict)
|
||||
_require_global_spend_report_access(user_api_key_dict)
|
||||
|
||||
if start_date is None or end_date is None:
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -1030,7 +1030,7 @@ def test_proxy_admin_viewer_can_access_global_spend_report():
|
|||
|
||||
|
||||
def test_internal_user_cannot_access_global_spend_report_route():
|
||||
"""Internal users must not use /global/spend/report (proxy admin roles only)."""
|
||||
"""Without key allowlisting, internal users cannot use /global/spend/report."""
|
||||
|
||||
user_obj = LiteLLM_UserTable(
|
||||
user_id="internal_user",
|
||||
|
|
@ -1056,6 +1056,32 @@ def test_internal_user_cannot_access_global_spend_report_route():
|
|||
assert "Only proxy admin can be used to generate" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_internal_user_can_access_global_spend_report_when_key_allowlists_route():
|
||||
"""Virtual key with allowed_routes including /global/spend/report passes route check."""
|
||||
|
||||
user_obj = LiteLLM_UserTable(
|
||||
user_id="internal_user",
|
||||
user_email="user@example.com",
|
||||
user_role=LitellmUserRoles.INTERNAL_USER.value,
|
||||
)
|
||||
valid_token = UserAPIKeyAuth(
|
||||
user_id="internal_user",
|
||||
user_role=LitellmUserRoles.INTERNAL_USER.value,
|
||||
allowed_routes=["/global/spend/report"],
|
||||
)
|
||||
request = MagicMock(spec=Request)
|
||||
request.query_params = {}
|
||||
|
||||
RouteChecks.non_proxy_admin_allowed_routes_check(
|
||||
user_obj=user_obj,
|
||||
_user_role=LitellmUserRoles.INTERNAL_USER.value,
|
||||
route="/global/spend/report",
|
||||
request=request,
|
||||
valid_token=valid_token,
|
||||
request_data={},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("route", ["/audit", "/audit/some-log-id"])
|
||||
def test_proxy_admin_viewer_can_access_audit_logs(route):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2761,3 +2761,34 @@ async def test_ui_view_spend_logs_team_member_no_permission_blocked(
|
|||
assert response.status_code == 403
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
def test_require_global_spend_report_access_internal_with_allowed_routes():
|
||||
spend_management_endpoints._require_global_spend_report_access(
|
||||
UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.INTERNAL_USER,
|
||||
user_id="u1",
|
||||
allowed_routes=["/global/spend/report"],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_require_global_spend_report_access_internal_with_route_bundle_name():
|
||||
spend_management_endpoints._require_global_spend_report_access(
|
||||
UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.INTERNAL_USER,
|
||||
user_id="u1",
|
||||
allowed_routes=["global_spend_report_routes"],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_require_global_spend_report_access_internal_without_allowed_routes_fails():
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
spend_management_endpoints._require_global_spend_report_access(
|
||||
UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.INTERNAL_USER,
|
||||
user_id="u1",
|
||||
)
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue