mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
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:
parent
e2c106101c
commit
9247826cdd
20 changed files with 100 additions and 71 deletions
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
55
litellm/proxy/management/teams/authz.py
Normal file
55
litellm/proxy/management/teams/authz.py
Normal 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")
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue