fix(scim): evict previous alias cache key even when the rename refresh succeeds

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yassin 2026-08-27 05:19:08 +00:00
parent 87340c5040
commit 21893d045e
2 changed files with 43 additions and 9 deletions

View file

@ -506,8 +506,9 @@ async def _refresh_scim_updated_team_cache(
Best-effort: the DB write has already committed, so a cache backend failure
must not fail the SCIM response and trigger an IdP retry of a successful write.
On failure both the current and previous alias keys are evicted alongside the
id key, so alias-based auth cannot keep serving the pre-write organization policy.
On failure the current keys are evicted so auth re-reads the DB, and after a
rename the previous alias key is always evicted, whether or not the refresh
succeeded, so alias-based auth cannot keep serving the pre-write organization policy.
"""
from litellm.proxy.proxy_server import proxy_logging_obj, user_api_key_cache
@ -531,13 +532,13 @@ async def _refresh_scim_updated_team_cache(
user_api_key_cache=user_api_key_cache,
proxy_logging_obj=proxy_logging_obj,
)
if previous_team_alias is not None and previous_team_alias != updated_team.team_alias:
await delete_cache_team_object(
team_id=updated_team.team_id,
team_alias=previous_team_alias,
user_api_key_cache=user_api_key_cache,
proxy_logging_obj=proxy_logging_obj,
)
if previous_team_alias is not None and previous_team_alias != updated_team.team_alias:
await delete_cache_team_object(
team_id=updated_team.team_id,
team_alias=previous_team_alias,
user_api_key_cache=user_api_key_cache,
proxy_logging_obj=proxy_logging_obj,
)
def _team_with_final_roster(existing_team: LiteLLM_TeamTable, final_member_ids: Iterable[str]) -> LiteLLM_TeamTable:

View file

@ -5996,6 +5996,39 @@ async def test_refresh_cache_failure_evicts_stale_team_entry(mocker: MockerFixtu
assert evicted == [("team-1", "Engineering-Platform"), ("team-1", "Sales")]
@pytest.mark.asyncio
async def test_successful_refresh_after_rename_evicts_previous_alias(mocker: MockerFixture): # test-quality-ok: the observable is the previous-alias eviction the helper must trigger; the eviction's cache delete is covered by auth_checks tests
"""A successful refresh only writes the new keys, so after a rename the
previous alias cache key must still be evicted or alias-based auth keeps
serving the pre-rename organization policy until TTL expiry."""
from litellm.proxy._types import LiteLLM_TeamTable, Member
from litellm.proxy.management_endpoints.scim.scim_v2 import (
_refresh_scim_updated_team_cache,
)
updated_team = LiteLLM_TeamTable(
team_id="team-1",
team_alias="Engineering-Platform",
organization_id="org-eng",
members=["user1"],
members_with_roles=[Member(user_id="user1", role="user")],
metadata={},
)
mocker.patch( # test-quality-ok: pins the refresh success path under test
"litellm.proxy.management_endpoints.scim.scim_v2._refresh_cached_team",
AsyncMock(),
)
evict_mock = mocker.patch( # test-quality-ok: asserts the eviction the helper must trigger
"litellm.proxy.management_endpoints.scim.scim_v2.delete_cache_team_object",
AsyncMock(),
)
await _refresh_scim_updated_team_cache(updated_team, "Sales")
evicted = [(call.kwargs["team_id"], call.kwargs["team_alias"]) for call in evict_mock.await_args_list]
assert evicted == [("team-1", "Sales")]
@pytest.mark.asyncio
async def test_org_validation_sees_final_roster_not_removed_members(mocker: MockerFixture): # test-quality-ok: the observable is the roster snapshot the destination-org validator receives; the validator's auto-add is covered by team_endpoints tests
"""A PUT that moves a team into a mapped organization while removing a member