refactor(proxy): rename management/teams/access.py to authz.py (#44624)

access already means access groups in this codebase; authz names what the
module decides (who may act on a team). Pure rename: every importer now uses
authz, and access.py stays as a re-export because the published
litellm-enterprise 0.1.73 wheel imports is_team_admin from it.
This commit is contained in:
ryan-crabbe-berri 2026-10-05 12:11:20 -07:00 • committed by GitHub
parent e2c106101c
commit 9247826cdd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 100 additions and 71 deletions

View file

@ -22,7 +22,7 @@ from litellm._uuid import uuid
from litellm.proxy._types import *
from litellm.proxy.auth.auth_checks import delete_cached_project_object
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.management.teams.access import is_team_admin
from litellm.proxy.management.teams.authz import is_team_admin
from litellm.proxy.management_endpoints.common_utils import _set_object_metadata_field
from litellm.proxy.management_endpoints.team_admin_field_permissions import team_admin_may_manage_projects
from litellm.proxy.management_helpers.utils import (

View file

@ -36,7 +36,7 @@ async def resolve_owned_read_scope(
def can_read_team_logs(auth: UserAPIKeyAuth, team: LiteLLM_TeamTable) -> bool:
from litellm.proxy.management.teams.access import is_team_admin
from litellm.proxy.management.teams.authz import is_team_admin
from litellm.proxy.management_endpoints.common_utils import (
_team_member_has_permission, # pyright: ignore[reportPrivateUsage] # reuse existing team permission policy
)

View file

@ -1,55 +1,22 @@
"""Who may act on a team: every management route asks ``TeamAccess.allows`` with the roles it accepts."""
"""Moved to ``authz``. Kept because the published litellm-enterprise 0.1.73 wheel imports ``is_team_admin`` from here;
delete once the enterprise pin moves to a release that imports from ``authz``."""
from __future__ import annotations
from litellm.proxy.management.teams.authz import (
TEAM_ADMIN_ONLY,
TEAM_OR_ORG_ADMIN,
OrgRoles,
TeamAccess,
TeamRole,
is_team_admin,
team_access_denied,
)
from dataclasses import dataclass
from typing import Final, Literal, NoReturn, Protocol, TypeAlias
from fastapi import HTTPException, status
from litellm.proxy._types import LiteLLM_TeamTable, LitellmUserRoles, UserAPIKeyAuth
TeamRole: TypeAlias = Literal["proxy_admin", "org_admin", "team_admin"]
TEAM_ADMIN_ONLY: Final[frozenset[TeamRole]] = frozenset({"proxy_admin", "team_admin"})
TEAM_OR_ORG_ADMIN: Final[frozenset[TeamRole]] = frozenset({"proxy_admin", "team_admin", "org_admin"})
class OrgRoles(Protocol):
async def is_org_admin(self, user_id: str, organization_id: str) -> bool: ...
@dataclass(frozen=True, slots=True)
class TeamAccess:
org_roles: OrgRoles
async def allows(self, caller: UserAPIKeyAuth, team: LiteLLM_TeamTable, allow: frozenset[TeamRole]) -> bool:
"""Team admin is checked before org admin, so only callers off the roster pay for the org lookup."""
if "proxy_admin" in allow and caller.user_role == LitellmUserRoles.PROXY_ADMIN:
return True
if "team_admin" in allow and is_team_admin(caller, team):
return True
return "org_admin" in allow and await self._is_org_admin(caller, team)
async def strongest_role(self, caller: UserAPIKeyAuth, team: LiteLLM_TeamTable) -> TeamRole | None:
"""Org admin outranks team admin so a caller holding both keeps unrestricted edits."""
if caller.user_role == LitellmUserRoles.PROXY_ADMIN:
return "proxy_admin"
if await self._is_org_admin(caller, team):
return "org_admin"
return "team_admin" if is_team_admin(caller, team) else None
async def _is_org_admin(self, caller: UserAPIKeyAuth, team: LiteLLM_TeamTable) -> bool:
if not caller.user_id or not team.organization_id:
return False
return await self.org_roles.is_org_admin(caller.user_id, team.organization_id)
def is_team_admin(user_api_key_dict: UserAPIKeyAuth, team_obj: LiteLLM_TeamTable) -> bool:
return any(
member.user_id is not None and member.user_id == user_api_key_dict.user_id and member.role == "admin"
for member in team_obj.members_with_roles
)
def team_access_denied() -> NoReturn:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You do not have access to this team")
__all__ = [
"TEAM_ADMIN_ONLY",
"TEAM_OR_ORG_ADMIN",
"OrgRoles",
"TeamAccess",
"TeamRole",
"is_team_admin",
"team_access_denied",
]

View file

@ -0,0 +1,55 @@
"""Who may act on a team: every management route asks ``TeamAccess.allows`` with the roles it accepts."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Final, Literal, NoReturn, Protocol, TypeAlias
from fastapi import HTTPException, status
from litellm.proxy._types import LiteLLM_TeamTable, LitellmUserRoles, UserAPIKeyAuth
TeamRole: TypeAlias = Literal["proxy_admin", "org_admin", "team_admin"]
TEAM_ADMIN_ONLY: Final[frozenset[TeamRole]] = frozenset({"proxy_admin", "team_admin"})
TEAM_OR_ORG_ADMIN: Final[frozenset[TeamRole]] = frozenset({"proxy_admin", "team_admin", "org_admin"})
class OrgRoles(Protocol):
async def is_org_admin(self, user_id: str, organization_id: str) -> bool: ...
@dataclass(frozen=True, slots=True)
class TeamAccess:
org_roles: OrgRoles
async def allows(self, caller: UserAPIKeyAuth, team: LiteLLM_TeamTable, allow: frozenset[TeamRole]) -> bool:
"""Team admin is checked before org admin, so only callers off the roster pay for the org lookup."""
if "proxy_admin" in allow and caller.user_role == LitellmUserRoles.PROXY_ADMIN:
return True
if "team_admin" in allow and is_team_admin(caller, team):
return True
return "org_admin" in allow and await self._is_org_admin(caller, team)
async def strongest_role(self, caller: UserAPIKeyAuth, team: LiteLLM_TeamTable) -> TeamRole | None:
"""Org admin outranks team admin so a caller holding both keeps unrestricted edits."""
if caller.user_role == LitellmUserRoles.PROXY_ADMIN:
return "proxy_admin"
if await self._is_org_admin(caller, team):
return "org_admin"
return "team_admin" if is_team_admin(caller, team) else None
async def _is_org_admin(self, caller: UserAPIKeyAuth, team: LiteLLM_TeamTable) -> bool:
if not caller.user_id or not team.organization_id:
return False
return await self.org_roles.is_org_admin(caller.user_id, team.organization_id)
def is_team_admin(user_api_key_dict: UserAPIKeyAuth, team_obj: LiteLLM_TeamTable) -> bool:
return any(
member.user_id is not None and member.user_id == user_api_key_dict.user_id and member.role == "admin"
for member in team_obj.members_with_roles
)
def team_access_denied() -> NoReturn:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You do not have access to this team")

View file

@ -1,6 +1,6 @@
from __future__ import annotations
from litellm.proxy.management.teams.access import TeamAccess
from litellm.proxy.management.teams.authz import TeamAccess
from litellm.proxy.management.users.service import PrismaOrgRoles

View file

@ -43,7 +43,7 @@ from litellm.proxy.litellm_pre_call_utils import (
LiteLLMProxyRequestSetup,
refresh_proxy_server_request_body_snapshot,
)
from litellm.proxy.management.teams.access import is_team_admin
from litellm.proxy.management.teams.authz import is_team_admin
from litellm.proxy.management_endpoints.common_daily_activity import daily_activity_scope
from litellm.proxy.management_helpers.auto_router_permissions import (
authorize_member_auto_router_dependencies,

View file

@ -61,7 +61,7 @@ from litellm.proxy._types import ( # noqa: F401 re-exported
user_api_key_has_admin_view as _user_has_admin_view,
)
from litellm.proxy.common_utils.timezone_utils import get_budget_reset_time
from litellm.proxy.management.teams.access import is_team_admin
from litellm.proxy.management.teams.authz import is_team_admin
from litellm.proxy.utils import _premium_user_check
from litellm.repositories.team_repository import TeamRepository
from litellm.types.utils import BudgetConfig

View file

@ -53,7 +53,7 @@ from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler
from litellm.proxy.hooks.key_management_event_hooks import KeyManagementEventHooks
from litellm.proxy.hooks.model_max_budget_limiter import build_model_max_budget_usage
from litellm.proxy.hooks.user_management_event_hooks import UserManagementEventHooks
from litellm.proxy.management.teams.access import is_team_admin
from litellm.proxy.management.teams.authz import is_team_admin
from litellm.proxy.management_endpoints.common_daily_activity import (
DailySpendRecord,
ScopeDenied,

View file

@ -86,7 +86,7 @@ from litellm.proxy.common_utils.timezone_utils import get_budget_reset_time
from litellm.proxy.common_utils.user_api_key_cache import AUTH_OBJECTS_TARGET, UserApiKeyCache
from litellm.proxy.hooks.key_management_event_hooks import KeyManagementEventHooks
from litellm.proxy.hooks.model_max_budget_limiter import build_model_max_budget_usage
from litellm.proxy.management.teams.access import TEAM_ADMIN_ONLY, TEAM_OR_ORG_ADMIN, is_team_admin
from litellm.proxy.management.teams.authz import TEAM_ADMIN_ONLY, TEAM_OR_ORG_ADMIN, is_team_admin
from litellm.proxy.management.teams.dependencies import get_team_access
from litellm.proxy.management_endpoints.common_utils import (
_check_disable_global_guardrails_caller_permission,

View file

@ -75,7 +75,7 @@ from litellm.proxy.common_utils.encrypt_decrypt_utils import (
)
from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache
from litellm.proxy.db.routing_prisma_wrapper import WriterPinnedClient
from litellm.proxy.management.teams.access import TEAM_ADMIN_ONLY, is_team_admin
from litellm.proxy.management.teams.authz import TEAM_ADMIN_ONLY, is_team_admin
from litellm.proxy.management.teams.dependencies import get_team_access
from litellm.proxy.management_endpoints.team_endpoints import (
_refresh_cached_team,

View file

@ -44,7 +44,7 @@ from litellm.proxy.litellm_pre_call_utils import (
_get_validated_callback_metadata,
convert_key_logging_metadata_to_callback,
)
from litellm.proxy.management.teams.access import TEAM_OR_ORG_ADMIN, team_access_denied
from litellm.proxy.management.teams.authz import TEAM_OR_ORG_ADMIN, team_access_denied
from litellm.proxy.management.teams.dependencies import get_team_access
from litellm.proxy.management_endpoints.team_endpoints import _refresh_cached_team
from litellm.proxy.management_helpers.utils import management_endpoint_wrapper

View file

@ -123,7 +123,7 @@ from litellm.proxy.hooks.model_max_budget_limiter import (
build_model_max_budget_usage,
resolve_model_budget,
)
from litellm.proxy.management.teams.access import TEAM_OR_ORG_ADMIN, TeamRole, is_team_admin, team_access_denied
from litellm.proxy.management.teams.authz import TEAM_OR_ORG_ADMIN, TeamRole, is_team_admin, team_access_denied
from litellm.proxy.management.teams.dependencies import get_team_access
from litellm.proxy.management_endpoints.common_daily_activity import (
InvalidDateRange,

View file

@ -23,7 +23,7 @@ from litellm.proxy._types import (
from litellm.proxy.auth.auth_checks import invalidate_team_member_spend_state
from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache
from litellm.proxy.db.routing_prisma_wrapper import WriterPinnedClient
from litellm.proxy.management.teams.access import TEAM_OR_ORG_ADMIN
from litellm.proxy.management.teams.authz import TEAM_OR_ORG_ADMIN
from litellm.proxy.management.teams.dependencies import get_team_access
from litellm.proxy.management_endpoints.common_utils import (
_upsert_budget_and_membership, # pyright: ignore[reportPrivateUsage] # the single-member write, shared so the two surfaces cannot drift

View file

@ -34,7 +34,7 @@ from litellm.proxy.common_utils.timezone_utils import get_budget_reset_time
from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler
from litellm.proxy.hooks.user_management_event_hooks import UserManagementEventHooks
from litellm.proxy.list_api.common import PROBLEM_TYPE_BASE, ManagementProblem
from litellm.proxy.management.teams.access import TEAM_OR_ORG_ADMIN
from litellm.proxy.management.teams.authz import TEAM_OR_ORG_ADMIN
from litellm.proxy.management.teams.dependencies import get_team_access
from litellm.proxy.management_endpoints.common_utils import validate_budget_duration
from litellm.proxy.management_endpoints.internal_user_endpoints import (

View file

@ -34,7 +34,7 @@ from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache
from litellm.proxy.hooks.key_management_event_hooks import KeyManagementEventHooks
from litellm.proxy.hooks.user_management_event_hooks import UserManagementEventHooks
from litellm.proxy.list_api.common import PROBLEM_TYPE_BASE, ManagementProblem
from litellm.proxy.management.teams.access import TEAM_OR_ORG_ADMIN
from litellm.proxy.management.teams.authz import TEAM_OR_ORG_ADMIN
from litellm.proxy.management.teams.dependencies import get_team_access
from litellm.proxy.management_endpoints.key_management_endpoints import (
_persist_deleted_verification_tokens, # pyright: ignore[reportPrivateUsage] # same audit path /key/delete uses

View file

@ -32,7 +32,7 @@ from litellm.proxy._types import (
user_api_key_has_admin_view,
)
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.management.teams.access import TEAM_OR_ORG_ADMIN
from litellm.proxy.management.teams.authz import TEAM_OR_ORG_ADMIN
from litellm.proxy.management.teams.dependencies import get_team_access
from litellm.repositories.prisma_protocols import TableActions
from litellm.repositories.table_repositories import MemoryRepository

View file

@ -7,7 +7,7 @@ import pytest
from fastapi import HTTPException
from litellm.proxy._types import LiteLLM_TeamTable, LitellmUserRoles, Member, UserAPIKeyAuth
from litellm.proxy.management.teams.access import (
from litellm.proxy.management.teams.authz import (
TEAM_ADMIN_ONLY,
TEAM_OR_ORG_ADMIN,
TeamAccess,
@ -134,3 +134,10 @@ def test_team_access_denied_is_the_403_management_routes_have_always_raised() ->
team_access_denied()
assert denied.value.status_code == 403
assert denied.value.detail == "You do not have access to this team"
def test_the_old_access_module_still_serves_the_published_enterprise_wheel() -> None:
from litellm.proxy.management.teams import access, authz
assert access.is_team_admin is authz.is_team_admin
assert set(access.__all__) <= set(dir(authz))

View file

@ -21,7 +21,7 @@ from litellm.proxy._types import (
UserAPIKeyAuth,
)
from litellm.proxy.common_utils.callback_config_validation import cross_entry_family_error
from litellm.proxy.management.teams.access import TeamAccess
from litellm.proxy.management.teams.authz import TeamAccess
from litellm.proxy.management_endpoints.team_callback_endpoints import (
add_team_callbacks,
delete_team_callback,

View file

@ -40,7 +40,7 @@ from litellm.proxy._types import (
UpdateTeamRequest,
UserAPIKeyAuth, # Import UserAPIKeyAuth
)
from litellm.proxy.management.teams.access import TeamAccess
from litellm.proxy.management.teams.authz import TeamAccess
from litellm.proxy.management_endpoints.team_endpoints import (
_STRIP_DELETED_TEAM_FROM_USERS_SQL,
GetTeamMemberPermissionsResponse,

View file

@ -274,7 +274,7 @@ from litellm.proxy._types import (
UserAPIKeyAuth,
)
from litellm.proxy.hooks.proxy_track_cost_callback import _ProxyDBLogger
from litellm.proxy.management.teams import access as team_access
from litellm.proxy.management.teams import authz as team_access
from litellm.proxy.proxy_server import app
from litellm.proxy.spend_tracking import spend_management_endpoints
from litellm.router import Router