diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 7f5a0da1066..845fb3cf2ef 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -596,6 +596,7 @@ class LiteLLMRoutes(enum.Enum): "/spend/calculate", "/spend/logs", "/spend/logs/ui", + "/spend/logs/ui/{request_id}", "/spend/logs/session/ui", "/cost/estimate", ] @@ -772,7 +773,6 @@ class LiteLLMRoutes(enum.Enum): "/customer/info", # UI Logs page detail drawer (single + session). The list endpoint # `/spend/logs/ui` is covered via spend_tracking_routes below. - "/spend/logs/ui/{logId}", "/spend/logs/session/ui", # Settings / observability read endpoints exposed in admin-only # sidebar groups (Logging & Alerts, Admin Settings, Budgets, diff --git a/tests/test_litellm/proxy/auth/test_route_checks.py b/tests/test_litellm/proxy/auth/test_route_checks.py index 3e0b1b739ec..53a957d9b5f 100644 --- a/tests/test_litellm/proxy/auth/test_route_checks.py +++ b/tests/test_litellm/proxy/auth/test_route_checks.py @@ -2102,3 +2102,51 @@ async def test_initialize_pass_through_registers_wildcard_for_auth_subpath(): ) for k in registered: InitPassThroughEndpointHelpers.remove_endpoint_routes(k.split(":")[0]) + + +# ── Regression: internal-user roles can view their own spend-log detail ────── +# +# `/spend/logs/ui/` (the detail-drawer endpoint behind row clicks +# on the dashboard Logs page) was previously missing from +# `spend_tracking_routes`, which feeds both `internal_user_routes` and +# `internal_user_view_only_routes`. That meant every non-admin click on a log +# row produced a 401, wrapped by `_raise_admin_only_route_exception` into the +# misleading "Only proxy admin can be used to..." message. +@pytest.mark.parametrize( + "route", + [ + "/spend/logs/ui", + "/spend/logs/ui/2fb2085b-6703-4ed4-81aa-0a586d0affeb", + "/spend/logs/session/ui", + ], +) +@pytest.mark.parametrize( + "role", + [ + LitellmUserRoles.INTERNAL_USER, + LitellmUserRoles.INTERNAL_USER_VIEW_ONLY, + ], +) +def test_internal_user_roles_can_view_own_log_detail(route, role): + """Internal users (read-write and view-only) must reach the per-user log + handlers; the handlers themselves enforce per-user filtering.""" + user_obj = LiteLLM_UserTable( + user_id="some-user-id", + user_email="user@example.com", + user_role=role.value, + ) + valid_token = UserAPIKeyAuth(user_id="some-user-id", user_role=role.value) + request = MagicMock(spec=Request) + request.query_params = {} + + try: + RouteChecks.non_proxy_admin_allowed_routes_check( + user_obj=user_obj, + _user_role=role.value, + route=route, + request=request, + valid_token=valid_token, + request_data={}, + ) + except Exception as e: + pytest.fail(f"{role.value} should access {route}; got: {e}")