diff --git a/litellm/proxy/common_utils/http_parsing_utils.py b/litellm/proxy/common_utils/http_parsing_utils.py index 259755f5ef9..1d94b10f6a4 100644 --- a/litellm/proxy/common_utils/http_parsing_utils.py +++ b/litellm/proxy/common_utils/http_parsing_utils.py @@ -329,8 +329,8 @@ def populate_request_with_path_params( request_data: dict, request: Request ) -> dict: """ - Copy FastAPI path params into the request payload so downstream checks - (e.g. vector store RBAC) see them the same way as body params. + Copy FastAPI path params and query params into the request payload so downstream checks + (e.g. vector store RBAC, organization RBAC) see them the same way as body params. Since path_params may not be available during dependency injection, we parse the URL path directly for known patterns. @@ -340,8 +340,15 @@ def populate_request_with_path_params( request: The FastAPI Request object Returns: - dict: Updated request_data with path parameters added + dict: Updated request_data with path parameters and query parameters added """ + # Add query parameters to request_data (for GET requests, etc.) + query_params = _safe_get_request_query_params(request) + if query_params: + for key, value in query_params.items(): + # Don't overwrite existing values from request body + request_data.setdefault(key, value) + # Try to get path_params if available (sometimes populated by FastAPI) path_params = getattr(request, "path_params", None) if isinstance(path_params, dict) and path_params: diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 9137b29ce27..1850ffa2560 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -1467,7 +1467,7 @@ async def get_users( ), ): """ - Get a paginated list of users with filtering and sorting options + Get a paginated list of users with filtering and sorting options. Parameters: role: Optional[str] diff --git a/tests/test_litellm/proxy/common_utils/test_http_parsing_utils.py b/tests/test_litellm/proxy/common_utils/test_http_parsing_utils.py index 2361decc5af..324a58acfa9 100644 --- a/tests/test_litellm/proxy/common_utils/test_http_parsing_utils.py +++ b/tests/test_litellm/proxy/common_utils/test_http_parsing_utils.py @@ -24,6 +24,7 @@ from litellm.proxy.common_utils.http_parsing_utils import ( get_form_data, get_request_body, get_tags_from_request_body, + populate_request_with_path_params, ) @@ -630,3 +631,69 @@ def test_get_tags_from_request_body_with_null_metadata(): assert result == [] assert isinstance(result, list) + + +def test_populate_request_with_path_params_adds_query_params(): + """ + Test that populate_request_with_path_params correctly adds query parameters + like organization_id to the request data. + """ + # Create a mock request with query parameters + mock_request = MagicMock() + # Mock query_params as a dict-like object that can be converted to dict + mock_request.query_params = { + "organization_id": "org-123", + "user_id": "user-456" + } + mock_request.path_params = {} + # Mock url.path to avoid errors in _add_vector_store_id_from_path + mock_request.url.path = "/v1/chat/completions" + + # Initial request data without query params + request_data = { + "model": "gpt-4", + "messages": [{"role": "user", "content": "Hello"}] + } + + # Call the function + result = populate_request_with_path_params(request_data, mock_request) + + # Verify query params were added + assert result["organization_id"] == "org-123" + assert result["user_id"] == "user-456" + # Verify original data is preserved + assert result["model"] == "gpt-4" + assert result["messages"] == [{"role": "user", "content": "Hello"}] + + +def test_populate_request_with_path_params_does_not_overwrite_existing_values(): + """ + Test that populate_request_with_path_params does not overwrite existing values + in request_data when query params contain the same keys. + """ + # Create a mock request with query parameters + mock_request = MagicMock() + # Mock query_params as a dict-like object that can be converted to dict + mock_request.query_params = { + "organization_id": "org-query-param", + "model": "gpt-3.5-turbo" + } + mock_request.path_params = {} + # Mock url.path to avoid errors in _add_vector_store_id_from_path + mock_request.url.path = "/v1/chat/completions" + + # Initial request data with existing values + request_data = { + "model": "gpt-4", # This should NOT be overwritten + "organization_id": "org-existing", # This should NOT be overwritten + "messages": [{"role": "user", "content": "Hello"}] + } + + # Call the function + result = populate_request_with_path_params(request_data, mock_request) + + # Verify existing values were NOT overwritten + assert result["model"] == "gpt-4" # Should keep original, not "gpt-3.5-turbo" + assert result["organization_id"] == "org-existing" # Should keep original, not "org-query-param" + # Verify other data is preserved + assert result["messages"] == [{"role": "user", "content": "Hello"}]