From 334e4e35e47293e2ed433a25bb3523bc152f8f15 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Sat, 10 Oct 2026 19:33:58 -0700 Subject: [PATCH] fix(sso): preserve admin organization defaults during concurrent login --- litellm/proxy/management_endpoints/ui_sso.py | 4 +++- .../proxy/management_endpoints/test_ui_sso.py | 17 +++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py index f8e0d3ab98e..cee7f928a8e 100644 --- a/litellm/proxy/management_endpoints/ui_sso.py +++ b/litellm/proxy/management_endpoints/ui_sso.py @@ -2703,7 +2703,9 @@ async def _concurrently_inserted_sso_user( getattr(litellm, "default_internal_user_params", None) or {} ) required_organizations: Final = TypeAdapter(tuple[str, ...]).validate_python( - default_parameters.get("organizations") or () + () + if user_defined_values["user_role"] == LitellmUserRoles.PROXY_ADMIN + else default_parameters.get("organizations") or () ) existing: Final = await UserRepository(prisma_client, use_writer=True).find_by_id( user_id, include_organization_memberships=bool(required_organizations) diff --git a/tests/unit/proxy/management_endpoints/test_ui_sso.py b/tests/unit/proxy/management_endpoints/test_ui_sso.py index 1b69b511543..b1899e44887 100644 --- a/tests/unit/proxy/management_endpoints/test_ui_sso.py +++ b/tests/unit/proxy/management_endpoints/test_ui_sso.py @@ -1348,17 +1348,22 @@ async def test_concurrent_sso_creation_refuses_partial_membership_across_multipl @pytest.mark.asyncio -async def test_public_sso_upsert_recovers_an_actual_duplicate_user_refusal(monkeypatch: pytest.MonkeyPatch) -> None: +@pytest.mark.parametrize( + "defaults,user_role", ((None, "internal_user"), ({"organizations": ["not-for-admins"]}, "proxy_admin")) +) +async def test_public_sso_upsert_recovers_an_actual_duplicate_user_refusal( + defaults: Mapping[str, JsonValue] | None, user_role: str, monkeypatch: pytest.MonkeyPatch +) -> None: from litellm.proxy import proxy_server - row: Final = {"user_id": RACE_SUBJECT, "sso_user_id": RACE_SUBJECT, "user_role": "internal_user"} + row: Final = {"user_id": RACE_SUBJECT, "sso_user_id": RACE_SUBJECT, "user_role": user_role} prisma: Final = _writer_with_row(row) prisma.db.litellm_usertable.find_first = AsyncMock(return_value=LiteLLM_UserTable.model_validate(row)) monkeypatch.setattr(proxy_server, "prisma_client", prisma) - monkeypatch.setattr(litellm, "default_internal_user_params", None) - values: Final[SSOUserDefinedValues] = {**RACE_VALUES} + monkeypatch.setattr(litellm, "default_internal_user_params", defaults) + values: Final[SSOUserDefinedValues] = {**RACE_VALUES, "user_role": user_role} recovered: Final = await SSOAuthenticationHandler.upsert_sso_user( - result=CustomOpenID(id=RACE_SUBJECT, email=RACE_VALUES["user_email"], user_role="internal_user", team_ids=[]), + result=CustomOpenID(id=RACE_SUBJECT, email=RACE_VALUES["user_email"], user_role=user_role, team_ids=[]), user_info=None, user_email=RACE_VALUES["user_email"], user_defined_values=values, @@ -1368,7 +1373,7 @@ async def test_public_sso_upsert_recovers_an_actual_duplicate_user_refusal(monke assert (recovered.user_id, recovered.sso_user_id, recovered.user_role) == ( RACE_SUBJECT, RACE_SUBJECT, - "internal_user", + user_role, )