diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 92c5b5e4a79..a606997767b 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -527,9 +527,20 @@ class LiteLLMRoutes(enum.Enum): "/organization/member_update", ] + # Routes accessible by Admin Viewer (read-only admin access) + admin_viewer_routes = [ + "/user/list", + "/user/available_users", + "/user/available_roles", + "/user/daily/activity", + "/team/daily/activity", + "/tag/daily/activity", + "/tag/list", + ] + info_routes + # All routes accesible by an Org Admin org_admin_allowed_routes = ( - org_admin_only_routes + management_routes + self_managed_routes + org_admin_only_routes + management_routes + self_managed_routes + admin_viewer_routes ) diff --git a/litellm/proxy/auth/route_checks.py b/litellm/proxy/auth/route_checks.py index f6b088d15a6..8883f7d5429 100644 --- a/litellm/proxy/auth/route_checks.py +++ b/litellm/proxy/auth/route_checks.py @@ -153,38 +153,11 @@ class RouteChecks: ): pass elif _user_role == LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY.value: - - if RouteChecks.is_llm_api_route(route=route): - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail=f"user not allowed to access this OpenAI routes, role= {_user_role}", - ) - if RouteChecks.check_route_access( - route=route, allowed_routes=LiteLLMRoutes.management_routes.value - ): - - # the Admin Viewer is only allowed to call /user/update for their own user_id and can only update - if route == "/user/update": - # Check the Request params are valid for PROXY_ADMIN_VIEW_ONLY - if request_data is not None and isinstance(request_data, dict): - _params_updated = request_data.keys() - for param in _params_updated: - if param not in ["user_email", "password"]: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail=f"user not allowed to access this route, role= {_user_role}. Trying to access: {route} and updating invalid param: {param}. only user_email and password can be updated", - ) - else: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail=f"user not allowed to access this route, role= {_user_role}. Trying to access: {route}", - ) - else: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail=f"user not allowed to access this route, role= {_user_role}. Trying to access: {route}", - ) - + RouteChecks._check_proxy_admin_viewer_access( + route=route, + _user_role=_user_role, + request_data=request_data, + ) elif ( _user_role == LitellmUserRoles.INTERNAL_USER.value and RouteChecks.check_route_access( @@ -401,3 +374,53 @@ class RouteChecks: if "streamGenerateContent" in route: return True return False + + @staticmethod + def _check_proxy_admin_viewer_access( + route: str, + _user_role: str, + request_data: dict, + ) -> None: + """ + Check access for PROXY_ADMIN_VIEW_ONLY role + """ + if RouteChecks.is_llm_api_route(route=route): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=f"user not allowed to access this OpenAI routes, role= {_user_role}", + ) + + # Check if this is a write operation on management routes + if RouteChecks.check_route_access( + route=route, allowed_routes=LiteLLMRoutes.management_routes.value + ): + # For management routes, only allow read operations or specific allowed updates + if route == "/user/update": + # Check the Request params are valid for PROXY_ADMIN_VIEW_ONLY + if request_data is not None and isinstance(request_data, dict): + _params_updated = request_data.keys() + for param in _params_updated: + if param not in ["user_email", "password"]: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=f"user not allowed to access this route, role= {_user_role}. Trying to access: {route} and updating invalid param: {param}. only user_email and password can be updated", + ) + elif route in ["/user/new", "/user/delete", "/team/new", "/team/update", "/team/delete", "/model/new", "/model/update", "/model/delete", "/key/generate", "/key/delete", "/key/update", "/key/regenerate", "/key/service-account/generate", "/key/block", "/key/unblock"] or route.startswith("/key/") and route.endswith("/regenerate"): + # Block write operations for PROXY_ADMIN_VIEW_ONLY + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=f"user not allowed to access this route, role= {_user_role}. Trying to access: {route}", + ) + # Allow read operations on management routes (like /user/info, /team/info, /model/info) + return + elif RouteChecks.check_route_access( + route=route, allowed_routes=LiteLLMRoutes.admin_viewer_routes.value + ): + # Allow access to admin viewer routes (read-only admin endpoints) + return + else: + # For other routes, block access + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=f"user not allowed to access this route, role= {_user_role}. Trying to access: {route}", + )