diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index b4608ac27a0..4234206cd33 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -5326,9 +5326,8 @@ async def _authorize_and_filter_teams( Authorize the /team/list request and return filtered teams. - Proxy admins: all teams (or filtered by user_id if provided). - - Org admins: teams from their orgs (scoped to user_id if provided), plus - the teams they are a member of when querying themselves. - - Own query (user_id matches caller): teams the user is a member of. + - Org admins: teams from their orgs (scoped to user_id if provided). + - Own query (user_id matches caller): teams the user is a member of, across all orgs. - Others: 401. """ is_proxy_admin: Final = _user_has_admin_view(user_api_key_dict) @@ -5364,11 +5363,13 @@ async def _authorize_and_filter_teams( }, ) - if allowed_org_ids is not None and user_id and not is_own_query: + if allowed_org_ids is not None and not is_own_query: org_teams: Final = await _raw_team_db(TeamRepository(prisma_client)).find_many( where={"organization_id": {"in": allowed_org_ids}}, include={"litellm_model_table": True}, ) + if not user_id: + return list(org_teams) return [ team for team in org_teams @@ -5376,17 +5377,15 @@ async def _authorize_and_filter_teams( ] response: Final = await _raw_team_db(TeamRepository(prisma_client)).find_many(include={"litellm_model_table": True}) - if allowed_org_ids is None and not user_id: + if not user_id: # Proxy admin: all teams return list(response) # Prisma can't filter JSON arrays, so membership is filtered in Python - viewer_id: Final = user_id or user_api_key_dict.user_id return [ team for team in response - if (allowed_org_ids is not None and team.organization_id in allowed_org_ids) - or (team.members_with_roles and any(m.get("user_id") == viewer_id for m in team.members_with_roles)) + if team.members_with_roles and any(m.get("user_id") == user_id for m in team.members_with_roles) ] diff --git a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py index 0f891c10d87..7aaf558e2ba 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py @@ -4152,8 +4152,8 @@ async def test_list_team_v2_org_admin_own_query_keeps_memberships_in_other_orgs( async def test_list_team_v1_org_admin_own_query_keeps_memberships_in_other_orgs(): """ /team/list: an org admin of org_A listing their own teams sees every team - in org_A plus the org_B team they are a member of, but a query for another - user stays scoped to org_A. + they belong to, including the org_B one. The bare admin listing stays the + org_A view and a query for another user stays scoped to org_A. Regression test for LIT-3723. """ @@ -4213,8 +4213,8 @@ async def test_list_team_v1_org_admin_own_query_keeps_memberships_in_other_orgs( ) return [t.team_id for t in teams] - assert await list_teams("org_admin_user") == ["team_in_org_A", "other_team_in_org_A", "team_in_org_B"] - assert await list_teams(None) == ["team_in_org_A", "other_team_in_org_A", "team_in_org_B"] + assert await list_teams("org_admin_user") == ["team_in_org_A", "team_in_org_B"] + assert await list_teams(None) == ["team_in_org_A", "other_team_in_org_A"] assert await list_teams("other_user") == ["other_team_in_org_A"]