From 89a11500333397191f214bb08ae1bc67f8504635 Mon Sep 17 00:00:00 2001 From: Jugal Bhatt Date: Wed, 13 Aug 2025 10:49:02 -0700 Subject: [PATCH 1/3] Allow routes for admin viewer --- litellm/proxy/_types.py | 13 ++++++++++++- litellm/proxy/auth/route_checks.py | 17 +++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index cf8b3d147f0..de282553950 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..22e6bbffb38 100644 --- a/litellm/proxy/auth/route_checks.py +++ b/litellm/proxy/auth/route_checks.py @@ -159,11 +159,12 @@ class RouteChecks: 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 ): - - # the Admin Viewer is only allowed to call /user/update for their own user_id and can only update + # 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): @@ -174,17 +175,25 @@ class RouteChecks: 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: + elif route in ["/user/new", "/user/delete", "/team/new", "/team/update", "/team/delete", "/model/new", "/model/update", "/model/delete"]: + # 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) + pass + elif RouteChecks.check_route_access( + route=route, allowed_routes=LiteLLMRoutes.admin_viewer_routes.value + ): + # Allow access to admin viewer routes (read-only admin endpoints) + pass 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}", ) - elif ( _user_role == LitellmUserRoles.INTERNAL_USER.value and RouteChecks.check_route_access( From a74056e70745aa85f5d47eaac38692f2d330fd05 Mon Sep 17 00:00:00 2001 From: Jugal Bhatt Date: Wed, 13 Aug 2025 11:00:32 -0700 Subject: [PATCH 2/3] Refactor access checks for PROXY_ADMIN_VIEW_ONLY role in RouteChecks class - Consolidated access control logic into a new static method `_check_proxy_admin_viewer_access`. - Improved readability and maintainability by reducing code duplication in route access checks. - Ensured proper handling of write operations and parameter validation for management routes. --- litellm/proxy/auth/route_checks.py | 96 +++++++++++++++++------------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/litellm/proxy/auth/route_checks.py b/litellm/proxy/auth/route_checks.py index 22e6bbffb38..6976555aee1 100644 --- a/litellm/proxy/auth/route_checks.py +++ b/litellm/proxy/auth/route_checks.py @@ -153,47 +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}", - ) - - # 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"]: - # 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) - pass - elif RouteChecks.check_route_access( - route=route, allowed_routes=LiteLLMRoutes.admin_viewer_routes.value - ): - # Allow access to admin viewer routes (read-only admin endpoints) - pass - 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}", - ) + 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( @@ -410,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"]: + # 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}", + ) From 3b47355449deae9738960b16147a7648faae6b34 Mon Sep 17 00:00:00 2001 From: Jugal Bhatt Date: Wed, 13 Aug 2025 16:04:43 -0700 Subject: [PATCH 3/3] Enhance route access checks for PROXY_ADMIN_VIEW_ONLY role in RouteChecks class - Added additional routes for key management operations to the access control logic. - Improved handling of routes that start with "/key/" and end with "/regenerate" to ensure proper access restrictions. --- litellm/proxy/auth/route_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/auth/route_checks.py b/litellm/proxy/auth/route_checks.py index 6976555aee1..8883f7d5429 100644 --- a/litellm/proxy/auth/route_checks.py +++ b/litellm/proxy/auth/route_checks.py @@ -405,7 +405,7 @@ class RouteChecks: 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"]: + 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,