diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index b38272560d..c785dd05c4 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -4,24 +4,6 @@ model_list: model: openai/fake api_key: fake-key api_base: https://exampleopenaiendpoint-production.up.railway.app/ - - model_name: gpt-5-mini + - model_name: wildcard_models/* litellm_params: - model: azure/gpt-5-mini - api_base: os.environ/AZURE_GPT_5_MINI_API_BASE # runs os.getenv("AZURE_API_BASE") - api_key: os.environ/AZURE_GPT_5_MINI_API_KEY # runs os.getenv("AZURE_API_KEY") - stream_timeout: 60 - merge_reasoning_content_in_choices: true - model_info: - mode: chat - - model_name: ollama-deepseek-r1 - litellm_params: - model: ollama/deepseek-r1:1.5b - model_info: - mode: chat - -router_settings: - model_group_alias: {"my-fake-gpt-4": "fake-openai-endpoint"} - -litellm_settings: - callbacks: ["otel"] - success_callback: ["braintrust"] + model: openai/* diff --git a/litellm/proxy/auth/route_checks.py b/litellm/proxy/auth/route_checks.py index 3774cdfb81..56218b3345 100644 --- a/litellm/proxy/auth/route_checks.py +++ b/litellm/proxy/auth/route_checks.py @@ -300,7 +300,7 @@ class RouteChecks: if re.match(pattern, route): return True return False - + @staticmethod def _is_wildcard_pattern(pattern: str) -> bool: """ @@ -354,16 +354,22 @@ class RouteChecks: ######################################################### if route in allowed_routes: return True - + ######################################################### # wildcard match route is in allowed_routes # e.g calling /anthropic/v1/messages is allowed if allowed_routes has /anthropic/* ######################################################### - wildcard_allowed_routes = [route for route in allowed_routes if RouteChecks._is_wildcard_pattern(pattern=route)] + wildcard_allowed_routes = [ + route + for route in allowed_routes + if RouteChecks._is_wildcard_pattern(pattern=route) + ] for allowed_route in wildcard_allowed_routes: - if RouteChecks._route_matches_wildcard_pattern(route=route, pattern=allowed_route): + if RouteChecks._route_matches_wildcard_pattern( + route=route, pattern=allowed_route + ): return True - + ######################################################### # pattern match route is in allowed_routes # pattern: "/threads/{thread_id}" @@ -375,7 +381,7 @@ class RouteChecks: for allowed_route in allowed_routes ): return True - + return False @staticmethod @@ -420,7 +426,7 @@ 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 @@ -436,7 +442,28 @@ 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", "/key/generate", "/key/delete", "/key/update", "/key/regenerate", "/key/service-account/generate", "/key/block", "/key/unblock"] or route.startswith("/key/") and route.endswith("/regenerate"): + 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, diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 3876230f5a..68e6342d6b 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -835,6 +835,16 @@ async def _update_single_user_helper( existing_user_row = LiteLLM_UserTable( **existing_user_row.model_dump(exclude_none=True) ) + if not can_user_call_user_update( + user_api_key_dict=user_api_key_dict, + user_info=existing_user_row, + ): + raise HTTPException( + status_code=403, + detail={ + "error": "User does not have permission to update this user. Only PROXY_ADMIN can update other users." + }, + ) existing_metadata = ( cast(Dict, getattr(existing_user_row, "metadata", {}) or {}) @@ -929,6 +939,20 @@ async def _update_single_user_helper( return response +def can_user_call_user_update( + user_api_key_dict: UserAPIKeyAuth, + user_info: LiteLLM_UserTable, +) -> bool: + """ + Helper to check if the user has access to the key's info + """ + if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value: + return True + elif user_api_key_dict.user_id == user_info.user_id: + return True + return False + + @router.post( "/user/update", tags=["Internal User management"], @@ -988,6 +1012,7 @@ async def user_update( """ try: verbose_proxy_logger.debug("/user/update: Received data = %s", data) + response = await _update_single_user_helper( user_request=data, user_api_key_dict=user_api_key_dict,