Merge branch 'main' into litellm_add_scim_patch_user

This commit is contained in:
Ishaan Jaff 2025-06-18 09:51:28 -07:00
commit f8b2e07cce
4 changed files with 80 additions and 2 deletions

View file

@ -0,0 +1,13 @@
from fastapi import HTTPException
class ScimUserAlreadyExists(HTTPException):
"""
Exception raised when a user already exists in the database.
"""
def __init__(self, message: str, scim_type: str = "uniqueness"):
super().__init__(status_code=409, detail=message)
self.message = message
self.scim_type = scim_type
self.schemas = ["urn:ietf:params:scim:api:messages:2.0:Error"]

View file

@ -17,6 +17,7 @@ from fastapi import (
Request,
Response,
)
from prisma.types import HttpConfig
from litellm._logging import verbose_proxy_logger
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
@ -32,9 +33,11 @@ from litellm.proxy._types import (
)
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.management_endpoints.internal_user_endpoints import new_user
from litellm.proxy.management_endpoints.scim.scim_errors import ScimUserAlreadyExists
from litellm.proxy.management_endpoints.scim.scim_transformations import (
ScimTransformations,
)
from litellm.proxy.management_endpoints.scim.utils import _extract_error_message
from litellm.proxy.management_endpoints.team_endpoints import (
new_team,
team_member_add,
@ -285,7 +288,6 @@ async def get_user(
except Exception as e:
raise handle_exception_on_proxy(e)
@scim_router.post(
"/Users",
response_model=SCIMUser,
@ -335,7 +337,8 @@ async def create_user(
user=created_user
)
return scim_user
except HTTPException as e: # allow exceptions like SCIMUserAlreadyExists to be raised
raise e
except Exception as e:
raise handle_exception_on_proxy(e)

View file

@ -0,0 +1,20 @@
from fastapi import HTTPException
async def _check_user_exists(prisma_client, user_name: str) -> bool:
"""Check if user already exists by username"""
if not user_name:
return False
existing_user = await prisma_client.db.litellm_usertable.find_unique(
where={"user_id": user_name}
)
return existing_user is not None
def _extract_error_message(http_exception: HTTPException) -> str:
"""Extract error message from HTTPException detail"""
if isinstance(http_exception.detail, dict):
return http_exception.detail.get("error", "User already exists")
return str(http_exception.detail)

View file

@ -0,0 +1,42 @@
from unittest.mock import AsyncMock
import pytest
from litellm.proxy.management_endpoints.scim.scim_errors import ScimUserAlreadyExists
from litellm.proxy.management_endpoints.scim.scim_v2 import create_user
from litellm.types.proxy.management_endpoints.scim_v2 import (
SCIMUser,
SCIMUserEmail,
SCIMUserName,
)
@pytest.mark.asyncio
async def test_create_user_existing_user_conflict(mocker):
"""If a user already exists, create_user should raise ScimUserAlreadyExists"""
scim_user = SCIMUser(
schemas=["urn:ietf:params:scim:schemas:core:2.0:User"],
userName="existing-user",
name=SCIMUserName(familyName="User", givenName="Existing"),
emails=[SCIMUserEmail(value="existing@example.com")],
)
mock_prisma = mocker.MagicMock()
mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma)
mocker.patch(
"litellm.proxy.management_endpoints.scim.scim_v2._check_user_exists",
AsyncMock(return_value=True),
)
mocked_new_user = mocker.patch(
"litellm.proxy.management_endpoints.scim.scim_v2.new_user",
AsyncMock(),
)
with pytest.raises(ScimUserAlreadyExists) as exc_info:
await create_user(user=scim_user)
assert exc_info.value.status_code == 409
assert "existing-user" in exc_info.value.message
mocked_new_user.assert_not_called()