mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix: order CLI SSO team choices by alias then id for a stable consent default
This commit is contained in:
parent
487356733c
commit
77fabdf5e9
2 changed files with 63 additions and 1 deletions
|
|
@ -2203,6 +2203,18 @@ def _cli_sso_team_detail(team_row: Mapping[str, object]) -> CliSsoTeamDetail:
|
|||
)
|
||||
|
||||
|
||||
def _cli_sso_team_sort_key(detail: CliSsoTeamDetail) -> tuple[str, str]:
|
||||
"""Stable, predictable order for a user's team choices.
|
||||
|
||||
The DB returns rows in an arbitrary order, so the consent page (and the
|
||||
classic login team choice, which shares this lookup) would preselect
|
||||
whichever team happened to come back first -- silently binding a user who
|
||||
takes the default to a team they did not mean to pick. Order by alias
|
||||
case-insensitively, with team_id as a deterministic tiebreaker.
|
||||
"""
|
||||
return ((detail.team_alias or "").casefold(), detail.team_id or "")
|
||||
|
||||
|
||||
async def fetch_cli_sso_team_details(
|
||||
prisma_client: PrismaClient,
|
||||
teams: Sequence[str],
|
||||
|
|
@ -2218,7 +2230,10 @@ async def fetch_cli_sso_team_details(
|
|||
except Exception as e:
|
||||
verbose_proxy_logger.error("Error fetching team details for CLI SSO session: %s", e)
|
||||
return None
|
||||
return tuple(_cli_sso_team_detail(team_row.model_dump()) for team_row in prisma_teams)
|
||||
details: Final = (
|
||||
_cli_sso_team_detail(team_row.model_dump()) for team_row in prisma_teams
|
||||
)
|
||||
return tuple(sorted(details, key=_cli_sso_team_sort_key))
|
||||
|
||||
|
||||
def _cli_sso_session_teams(team_details: Sequence[CliSsoTeamDetail]) -> list[str]:
|
||||
|
|
|
|||
|
|
@ -8344,3 +8344,50 @@ class TestPersistReturnToCookieSharedHelper:
|
|||
resp = Response()
|
||||
_persist_return_to_cookie(resp, "https://cp.example.com/ui?page=models")
|
||||
assert "litellm_cp_return_to=" in self._cookie(resp)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_cli_sso_team_details_orders_by_alias_then_id():
|
||||
"""CLI SSO team choices must come back in a stable order.
|
||||
|
||||
The consent page preselects the first team, so an arbitrary DB order can
|
||||
silently bind a user who takes the default to a team they did not mean to
|
||||
pick. Teams must be ordered by alias case-insensitively, then team_id as a
|
||||
deterministic tiebreaker. Regression for the CLI login consent page.
|
||||
"""
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from litellm.proxy.management_endpoints import ui_sso as ui_sso_mod
|
||||
|
||||
def _row(team_id, alias):
|
||||
row = MagicMock()
|
||||
row.model_dump.return_value = {
|
||||
"team_id": team_id,
|
||||
"team_alias": alias,
|
||||
"models": [],
|
||||
}
|
||||
return row
|
||||
|
||||
# Deliberately scrambled DB order, mixed case, plus an alias-less team.
|
||||
rows = [
|
||||
_row("35551", "forge-team-b-35551"),
|
||||
_row("10000", "Forge-Team-A-35551"),
|
||||
_row("99999", None),
|
||||
]
|
||||
repo = MagicMock()
|
||||
repo.find_many = AsyncMock(return_value=rows)
|
||||
|
||||
with patch.object(ui_sso_mod, "TeamRepository", return_value=MagicMock()), patch.object(
|
||||
ui_sso_mod, "_team_detail_db", return_value=repo
|
||||
):
|
||||
result = await ui_sso_mod.fetch_cli_sso_team_details(
|
||||
prisma_client=MagicMock(), teams=["35551", "10000", "99999"]
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
# None alias sorts first (""), then case-insensitive "Forge-Team-A" < "forge-team-b".
|
||||
assert [d.team_alias for d in result] == [
|
||||
None,
|
||||
"Forge-Team-A-35551",
|
||||
"forge-team-b-35551",
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue