mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(scim): preserve opaque IDs during user provisioning
This commit is contained in:
parent
134d837412
commit
c80cce69b4
3 changed files with 20 additions and 7 deletions
|
|
@ -1294,7 +1294,7 @@ async def _create_user_if_not_exists(user_id: str, created_via: str = "scim_grou
|
|||
if litellm.default_internal_user_params:
|
||||
default_role = litellm.default_internal_user_params.get("user_role")
|
||||
|
||||
new_user_request: Final = NewUserRequest(
|
||||
new_user_request: Final = SCIMNewUserRequest(
|
||||
user_id=user_id,
|
||||
user_email=user_id, # We don't have email from group membership
|
||||
user_alias=None,
|
||||
|
|
@ -1873,7 +1873,7 @@ async def create_user(
|
|||
admin_group: Final = await _get_scim_admin_group()
|
||||
resolved_role: Final = _resolve_scim_user_role(user.groups or [], admin_group, default_role)
|
||||
|
||||
new_user_request: Final = NewUserRequest(
|
||||
new_user_request: Final = SCIMNewUserRequest(
|
||||
user_id=user_id,
|
||||
user_email=user_data["user_email"],
|
||||
user_alias=user_data["user_alias"],
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from pydantic import (
|
|||
from pydantic_core.core_schema import SerializerFunctionWrapHandler
|
||||
|
||||
from litellm.types.llms.base import LiteLLMBaseModel
|
||||
from litellm.types.proxy.management_endpoints.user_requests import NewUserRequest
|
||||
|
||||
SCIM_ENTERPRISE_USER_SCHEMA: Final = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
|
||||
SCIM_ENTERPRISE_METADATA_KEY: Final = "scim_enterprise"
|
||||
|
|
@ -26,6 +27,14 @@ _SCIM_SERIALIZED_FIELDS_ADAPTER: Final = TypeAdapter(
|
|||
)
|
||||
|
||||
|
||||
class SCIMNewUserRequest(NewUserRequest):
|
||||
"""Preserve opaque directory IDs when provisioning SCIM users and group members."""
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
user_id: str | None = None
|
||||
|
||||
|
||||
class LiteLLM_UserScimMetadata(LiteLLMBaseModel):
|
||||
"""
|
||||
Scim metadata stored in LiteLLM_UserTable.metadata
|
||||
|
|
|
|||
|
|
@ -411,10 +411,11 @@ async def test_create_user_without_groups_defers_to_default_team(mocker: MockerF
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user_with_groups_keeps_idp_teams(mocker: MockerFixture, monkeypatch):
|
||||
@pytest.mark.parametrize("user_id", ["new-user", " employee-42 "])
|
||||
async def test_create_user_with_groups_keeps_idp_teams(mocker: MockerFixture, monkeypatch, user_id: str):
|
||||
scim_user = SCIMUser(
|
||||
schemas=["urn:ietf:params:scim:schemas:core:2.0:User"],
|
||||
userName="new-user",
|
||||
userName=user_id,
|
||||
emails=[SCIMUserEmail(value="new@example.com")],
|
||||
groups=[SCIMUserGroup(value="idp-team")],
|
||||
)
|
||||
|
|
@ -428,10 +429,12 @@ async def test_create_user_with_groups_keeps_idp_teams(mocker: MockerFixture, mo
|
|||
await create_user(user=scim_user)
|
||||
|
||||
assert new_user_mock.call_args.kwargs["data"].teams == ["idp-team"]
|
||||
assert new_user_mock.call_args.kwargs["data"].user_id == user_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user_if_not_exists_defers_to_default_team(mocker: MockerFixture, monkeypatch):
|
||||
@pytest.mark.parametrize("user_id", ["group-user", " employee-42 "])
|
||||
async def test_create_user_if_not_exists_defers_to_default_team(mocker: MockerFixture, monkeypatch, user_id: str):
|
||||
monkeypatch.setattr(
|
||||
"litellm.default_internal_user_params",
|
||||
{"teams": [{"team_id": "default-team", "max_budget_in_team": 25}]},
|
||||
|
|
@ -439,14 +442,15 @@ async def test_create_user_if_not_exists_defers_to_default_team(mocker: MockerFi
|
|||
)
|
||||
new_user_mock = mocker.patch( # test-quality-ok: new_user is imported inside the helper, not injectable
|
||||
"litellm.proxy.management_endpoints.internal_user_endpoints.new_user",
|
||||
AsyncMock(return_value=NewUserResponse(user_id="group-user", key="k")),
|
||||
AsyncMock(return_value=NewUserResponse(user_id=user_id, key="k")),
|
||||
)
|
||||
|
||||
created = await _create_user_if_not_exists(user_id="group-user")
|
||||
created = await _create_user_if_not_exists(user_id=user_id)
|
||||
|
||||
assert created is not None
|
||||
assert new_user_mock.call_args.kwargs["data"].teams is None
|
||||
assert new_user_mock.call_args.kwargs["user_api_key_dict"] == UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||
assert new_user_mock.call_args.kwargs["data"].user_id == user_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue