Skip to content

Commit 5acb25f

Browse files
authored
Merge pull request BerriAI#23095 from BerriAI/litellm_org_admin_add_user_e2e
[Feature] Org Admin Access to Team Management Endpoints
2 parents 1c37872 + 3a1ac96 commit 5acb25f

File tree

25 files changed

+1496
-223
lines changed

25 files changed

+1496
-223
lines changed

litellm/proxy/_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,8 @@ class LiteLLMRoutes(enum.Enum):
653653
"/model/update",
654654
"/model/delete",
655655
"/user/daily/activity",
656+
"/user/available_roles", # read-only role metadata; any authenticated user may read
657+
"/user/list", # org admins checked in endpoint; non-admins get 403
656658
"/model/{model_id}/update",
657659
"/prompt/list",
658660
"/prompt/info",

litellm/proxy/agent_endpoints/endpoints.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ async def get_agents(
106106
health_check: bool = Query(
107107
False,
108108
description="When true, performs a GET request to each agent's URL. Agents with reachable URLs (HTTP status < 500) and agents without a URL are returned; unreachable agents are filtered out.",
109+
),
109110
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), # Used for auth
110111
):
111112
"""

litellm/proxy/auth/auth_checks_organization.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,19 +144,32 @@ def _user_is_org_admin(
144144
user_object: Optional[LiteLLM_UserTable] = None,
145145
) -> bool:
146146
"""
147-
Helper function to check if user is an org admin for the passed organization_id
148-
"""
149-
if request_data.get("organization_id", None) is None:
150-
return False
147+
Helper function to check if user is an org admin for any of the passed organizations.
151148
149+
Checks both:
150+
- `organization_id` (singular string) — legacy callers
151+
- `organizations` (list of strings) — used by /user/new
152+
"""
152153
if user_object is None:
153154
return False
154155

155156
if user_object.organization_memberships is None:
156157
return False
157158

159+
# Collect candidate org IDs from both fields
160+
candidate_org_ids: List[str] = []
161+
singular = request_data.get("organization_id", None)
162+
if singular is not None:
163+
candidate_org_ids.append(singular)
164+
orgs_list = request_data.get("organizations", None)
165+
if isinstance(orgs_list, list):
166+
candidate_org_ids.extend(orgs_list)
167+
168+
if not candidate_org_ids:
169+
return False
170+
158171
for _membership in user_object.organization_memberships:
159-
if _membership.organization_id == request_data.get("organization_id", None):
172+
if _membership.organization_id in candidate_org_ids:
160173
if _membership.user_role == LitellmUserRoles.ORG_ADMIN.value:
161174
return True
162175

litellm/proxy/management_endpoints/common_utils.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,46 @@ def _is_user_team_admin(
4141
return False
4242

4343

44+
async def _is_user_org_admin_for_team(
45+
user_api_key_dict: UserAPIKeyAuth, team_obj: LiteLLM_TeamTable
46+
) -> bool:
47+
"""
48+
Check if user is an org admin for the team's organization.
49+
50+
Returns True if:
51+
- The team belongs to an organization, AND
52+
- The user has org_admin role in that organization
53+
"""
54+
if not team_obj.organization_id or not user_api_key_dict.user_id:
55+
return False
56+
57+
from litellm.proxy.auth.auth_checks import get_user_object
58+
from litellm.proxy.proxy_server import (
59+
prisma_client,
60+
proxy_logging_obj,
61+
user_api_key_cache,
62+
)
63+
64+
caller_user = await get_user_object(
65+
user_id=user_api_key_dict.user_id,
66+
prisma_client=prisma_client,
67+
user_api_key_cache=user_api_key_cache,
68+
user_id_upsert=False,
69+
proxy_logging_obj=proxy_logging_obj,
70+
)
71+
if caller_user is None:
72+
return False
73+
74+
for m in caller_user.organization_memberships or []:
75+
if (
76+
m.organization_id == team_obj.organization_id
77+
and m.user_role == LitellmUserRoles.ORG_ADMIN.value
78+
):
79+
return True
80+
81+
return False
82+
83+
4484
def _team_member_has_permission(
4585
user_api_key_dict: UserAPIKeyAuth,
4686
team_obj: LiteLLM_TeamTable,

0 commit comments

Comments
 (0)