fix(scim): keep deletion writes typed and verify cache eviction
Some checks are pending
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run

This commit is contained in:
Joshua Valluru 2026-09-28 19:44:25 -07:00
parent f9eaae589e
commit 585f671bad
2 changed files with 26 additions and 3 deletions

View file

@ -1944,11 +1944,13 @@ async def write_scim_group_deletion(tx: "Prisma", group_id: str, admin_group: st
if team is None:
return ()
member_ids: Final = tuple(await _get_team_member_user_ids_from_team(team))
for member_id in member_ids:
user: Final = await UserRepository(database).find_by_id(member_id, id_field="user_id")
users: Final = tuple(
[await UserRepository(database).find_by_id(member_id, id_field="user_id") for member_id in member_ids]
)
for user in users:
if user is not None and group_id in (user.teams or ()):
await _table(UserRepository(database)).update(
where={"user_id": member_id}, data={"teams": [value for value in user.teams if value != group_id]}
where={"user_id": user.user_id}, data={"teams": [value for value in user.teams if value != group_id]}
)
await _table(TeamRepository(database)).delete(where={"team_id": group_id})
await write_scim_member_roles(database, member_ids, admin_group)

View file

@ -641,3 +641,24 @@ async def test_scim_delete_user_evicts_cached_user_row(failure: str | None) -> N
else:
assert cached is None
broadcast.assert_awaited_once_with(cache_key=user_id)
@pytest.mark.asyncio
async def test_committed_scim_deletion_evicts_the_removed_team_and_user(monkeypatch):
from litellm.proxy import proxy_server
from litellm.proxy._types import LiteLLM_TeamTable
from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache
from litellm.proxy.management_endpoints.scim.scim_v2 import SCIMUserDeletion, finish_scim_user_deletion
from litellm.proxy.management_endpoints.team_endpoints import TeamMemberRemoval
cache = UserApiKeyCache()
team = LiteLLM_TeamTable(team_id="deleted-membership-team")
user = LiteLLM_UserTable(user_id="deleted-member")
await cache.async_set_cache(key="team_id:" + team.team_id, value=team, model_type=LiteLLM_TeamTable)
await cache.async_set_cache(key=user.user_id, value=user, model_type=LiteLLM_UserTable)
monkeypatch.setattr(proxy_server, "user_api_key_cache", cache)
monkeypatch.setattr(proxy_server, "proxy_logging_obj", None)
removal = TeamMemberRemoval(team=team, before=(), after=(), keys=(), jwt_mapping_cache_keys=(), user_ids=frozenset((user.user_id,)))
await finish_scim_user_deletion(SCIMUserDeletion(user_id=user.user_id, tokens=(), removals=(removal,)))
assert await cache.async_get_cache(key="team_id:" + team.team_id, model_type=LiteLLM_TeamTable) is None
assert await cache.async_get_cache(key=user.user_id, model_type=LiteLLM_UserTable) is None