From 034e3a6d446d5d0d241f6dda37c73e3fbb007ee9 Mon Sep 17 00:00:00 2001 From: YutaSaito <36355491+uc4w6c@users.noreply.github.com> Date: Sat, 17 Jan 2026 06:46:41 +0900 Subject: [PATCH] Revert "[Feature] Deleted Keys and Deleted Teams Table" --- ...tellm_proxy_extras-0.4.15-py3-none-any.whl | Bin 45399 -> 0 bytes .../dist/litellm_proxy_extras-0.4.15.tar.gz | Bin 21228 -> 0 bytes ...tellm_proxy_extras-0.4.22-py3-none-any.whl | Bin 48859 -> 0 bytes .../dist/litellm_proxy_extras-0.4.22.tar.gz | Bin 22506 -> 0 bytes .../migration.sql | 117 ----- .../litellm_proxy_extras/schema.prisma | 99 ----- litellm/proxy/_types.py | 30 -- .../internal_user_endpoints.py | 13 - .../key_management_endpoints.py | 141 ++---- .../management_endpoints/team_endpoints.py | 114 ----- litellm/proxy/schema.prisma | 99 ----- schema.prisma | 99 ----- .../test_key_management.py | 2 - .../test_key_generate_prisma.py | 20 +- .../test_key_management_endpoints.py | 416 +++--------------- .../test_team_endpoints.py | 349 --------------- 16 files changed, 89 insertions(+), 1410 deletions(-) delete mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.15-py3-none-any.whl delete mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.15.tar.gz delete mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.22-py3-none-any.whl delete mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.22.tar.gz delete mode 100644 litellm-proxy-extras/litellm_proxy_extras/migrations/20251219110931_add_deleted_keys_and_deleted_teams_tables/migration.sql diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.15-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.15-py3-none-any.whl deleted file mode 100644 index ba2e5e5fce56aa770485ef0b8229c356521f4fd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45399 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 S2#7e~s{%07n-KH+U;hu7c8lEr diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.15.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.15.tar.gz deleted file mode 100644 index 7d01b3de6fffb59a627a538bc98fbff0a4ec3704..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21228 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 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.22-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.22-py3-none-any.whl deleted file mode 100644 index 1e2f6967dc757ea0bfcd274529a855107502b09c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48859 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ð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 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.22.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.22.tar.gz deleted file mode 100644 index 1864c77ddda45e4f7605bdc7283e60aa80d7e65d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22506 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 D71Id9 diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20251219110931_add_deleted_keys_and_deleted_teams_tables/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251219110931_add_deleted_keys_and_deleted_teams_tables/migration.sql deleted file mode 100644 index 6ca66ddaad..0000000000 --- a/litellm-proxy-extras/litellm_proxy_extras/migrations/20251219110931_add_deleted_keys_and_deleted_teams_tables/migration.sql +++ /dev/null @@ -1,117 +0,0 @@ --- CreateTable -CREATE TABLE "LiteLLM_DeletedTeamTable" ( - "id" TEXT NOT NULL, - "team_id" TEXT NOT NULL, - "team_alias" TEXT, - "organization_id" TEXT, - "object_permission_id" TEXT, - "admins" TEXT[], - "members" TEXT[], - "members_with_roles" JSONB NOT NULL DEFAULT '{}', - "metadata" JSONB NOT NULL DEFAULT '{}', - "max_budget" DOUBLE PRECISION, - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "models" TEXT[], - "max_parallel_requests" INTEGER, - "tpm_limit" BIGINT, - "rpm_limit" BIGINT, - "budget_duration" TEXT, - "budget_reset_at" TIMESTAMP(3), - "blocked" BOOLEAN NOT NULL DEFAULT false, - "model_spend" JSONB NOT NULL DEFAULT '{}', - "model_max_budget" JSONB NOT NULL DEFAULT '{}', - "team_member_permissions" TEXT[] DEFAULT ARRAY[]::TEXT[], - "model_id" INTEGER, - "created_at" TIMESTAMP(3), - "updated_at" TIMESTAMP(3), - "deleted_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted_by" TEXT, - "deleted_by_api_key" TEXT, - "litellm_changed_by" TEXT, - - CONSTRAINT "LiteLLM_DeletedTeamTable_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "LiteLLM_DeletedVerificationToken" ( - "id" TEXT NOT NULL, - "token" TEXT NOT NULL, - "key_name" TEXT, - "key_alias" TEXT, - "soft_budget_cooldown" BOOLEAN NOT NULL DEFAULT false, - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "expires" TIMESTAMP(3), - "models" TEXT[], - "aliases" JSONB NOT NULL DEFAULT '{}', - "config" JSONB NOT NULL DEFAULT '{}', - "user_id" TEXT, - "team_id" TEXT, - "permissions" JSONB NOT NULL DEFAULT '{}', - "max_parallel_requests" INTEGER, - "metadata" JSONB NOT NULL DEFAULT '{}', - "blocked" BOOLEAN, - "tpm_limit" BIGINT, - "rpm_limit" BIGINT, - "max_budget" DOUBLE PRECISION, - "budget_duration" TEXT, - "budget_reset_at" TIMESTAMP(3), - "allowed_cache_controls" TEXT[] DEFAULT ARRAY[]::TEXT[], - "allowed_routes" TEXT[] DEFAULT ARRAY[]::TEXT[], - "model_spend" JSONB NOT NULL DEFAULT '{}', - "model_max_budget" JSONB NOT NULL DEFAULT '{}', - "budget_id" TEXT, - "organization_id" TEXT, - "object_permission_id" TEXT, - "created_at" TIMESTAMP(3), - "created_by" TEXT, - "updated_at" TIMESTAMP(3), - "updated_by" TEXT, - "rotation_count" INTEGER DEFAULT 0, - "auto_rotate" BOOLEAN DEFAULT false, - "rotation_interval" TEXT, - "last_rotation_at" TIMESTAMP(3), - "key_rotation_at" TIMESTAMP(3), - "deleted_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted_by" TEXT, - "deleted_by_api_key" TEXT, - "litellm_changed_by" TEXT, - - CONSTRAINT "LiteLLM_DeletedVerificationToken_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedTeamTable_team_id_idx" ON "LiteLLM_DeletedTeamTable"("team_id"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedTeamTable_deleted_at_idx" ON "LiteLLM_DeletedTeamTable"("deleted_at"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedTeamTable_organization_id_idx" ON "LiteLLM_DeletedTeamTable"("organization_id"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedTeamTable_team_alias_idx" ON "LiteLLM_DeletedTeamTable"("team_alias"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedTeamTable_created_at_idx" ON "LiteLLM_DeletedTeamTable"("created_at"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedVerificationToken_token_idx" ON "LiteLLM_DeletedVerificationToken"("token"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedVerificationToken_deleted_at_idx" ON "LiteLLM_DeletedVerificationToken"("deleted_at"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedVerificationToken_user_id_idx" ON "LiteLLM_DeletedVerificationToken"("user_id"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedVerificationToken_team_id_idx" ON "LiteLLM_DeletedVerificationToken"("team_id"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedVerificationToken_organization_id_idx" ON "LiteLLM_DeletedVerificationToken"("organization_id"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedVerificationToken_key_alias_idx" ON "LiteLLM_DeletedVerificationToken"("key_alias"); - --- CreateIndex -CREATE INDEX "LiteLLM_DeletedVerificationToken_created_at_idx" ON "LiteLLM_DeletedVerificationToken"("created_at"); - diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index 71b398c59a..56fe093a8b 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -132,49 +132,6 @@ model LiteLLM_TeamTable { object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id]) } -// Audit table for deleted teams - preserves spend and team information for historical tracking -model LiteLLM_DeletedTeamTable { - id String @id @default(uuid()) - team_id String // Original team_id - team_alias String? - organization_id String? - object_permission_id String? - admins String[] - members String[] - members_with_roles Json @default("{}") - metadata Json @default("{}") - max_budget Float? - spend Float @default(0.0) - models String[] - max_parallel_requests Int? - tpm_limit BigInt? - rpm_limit BigInt? - budget_duration String? - budget_reset_at DateTime? - blocked Boolean @default(false) - model_spend Json @default("{}") - model_max_budget Json @default("{}") - router_settings Json? @default("{}") - team_member_permissions String[] @default([]) - model_id Int? // id for LiteLLM_ModelTable -> stores team-level model aliases - - // Original timestamps from team creation/updates - created_at DateTime? @map("created_at") - updated_at DateTime? @map("updated_at") - - // Deletion metadata - deleted_at DateTime @default(now()) @map("deleted_at") - deleted_by String? @map("deleted_by") // User who deleted the team - deleted_by_api_key String? @map("deleted_by_api_key") // API key hash that performed the deletion - litellm_changed_by String? @map("litellm_changed_by") // From litellm-changed-by header if provided - - @@index([team_id]) - @@index([deleted_at]) - @@index([organization_id]) - @@index([team_alias]) - @@index([created_at]) -} - // Track spend, rate limit, budget Users model LiteLLM_UserTable { user_id String @id @@ -302,62 +259,6 @@ model LiteLLM_VerificationToken { object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id]) } -// Audit table for deleted keys - preserves spend and key information for historical tracking -model LiteLLM_DeletedVerificationToken { - id String @id @default(uuid()) - token String // Original token (hashed) - key_name String? - key_alias String? - soft_budget_cooldown Boolean @default(false) - spend Float @default(0.0) - expires DateTime? - models String[] - aliases Json @default("{}") - config Json @default("{}") - user_id String? - team_id String? - permissions Json @default("{}") - max_parallel_requests Int? - metadata Json @default("{}") - blocked Boolean? - tpm_limit BigInt? - rpm_limit BigInt? - max_budget Float? - budget_duration String? - budget_reset_at DateTime? - allowed_cache_controls String[] @default([]) - allowed_routes String[] @default([]) - model_spend Json @default("{}") - model_max_budget Json @default("{}") - router_settings Json? @default("{}") - budget_id String? - organization_id String? - object_permission_id String? - created_at DateTime? // Original creation timestamp - created_by String? // Original creator - updated_at DateTime? // Last update timestamp before deletion - updated_by String? // Last user who updated before deletion - rotation_count Int? @default(0) - auto_rotate Boolean? @default(false) - rotation_interval String? - last_rotation_at DateTime? - key_rotation_at DateTime? - - // Deletion metadata - deleted_at DateTime @default(now()) @map("deleted_at") - deleted_by String? @map("deleted_by") // User who deleted the key - deleted_by_api_key String? @map("deleted_by_api_key") // API key hash that performed the deletion - litellm_changed_by String? @map("litellm_changed_by") // From litellm-changed-by header if provided - - @@index([token]) - @@index([deleted_at]) - @@index([user_id]) - @@index([team_id]) - @@index([organization_id]) - @@index([key_alias]) - @@index([created_at]) -} - model LiteLLM_EndUserTable { user_id String @id alias String? // admin-facing alias diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 0f2cd4b3a7..3c6e210526 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1723,21 +1723,6 @@ class LiteLLM_TeamTableCachedObj(LiteLLM_TeamTable): last_refreshed_at: Optional[float] = None -class LiteLLM_DeletedTeamTable(LiteLLM_TeamTable): - """ - Recording of deleted teams for audit purposes. Mirrors LiteLLM_TeamTable - plus metadata captured at deletion time. - """ - - id: Optional[str] = None - deleted_at: Optional[datetime] = None - deleted_by: Optional[str] = None - deleted_by_api_key: Optional[str] = None - litellm_changed_by: Optional[str] = None - - model_config = ConfigDict(protected_namespaces=()) - - class TeamRequest(LiteLLMPydanticObjectBase): teams: List[str] @@ -2132,21 +2117,6 @@ class LiteLLM_VerificationToken(LiteLLMPydanticObjectBase): model_config = ConfigDict(protected_namespaces=()) -class LiteLLM_DeletedVerificationToken(LiteLLM_VerificationToken): - """ - Recording of deleted keys for audit purposes. Mirrors LiteLLM_VerificationToken - plus metadata captured at deletion time. - """ - - id: Optional[str] = None - deleted_at: Optional[datetime] = None - deleted_by: Optional[str] = None - deleted_by_api_key: Optional[str] = None - litellm_changed_by: Optional[str] = None - - model_config = ConfigDict(protected_namespaces=()) - - class LiteLLM_VerificationTokenView(LiteLLM_VerificationToken): """ Combined view of litellm verification token + litellm team table (select values) diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 2672c41893..1850ffa256 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -412,19 +412,6 @@ async def new_user( status_code=403, detail="License is over limit. Please contact support@berri.ai to upgrade your license.", ) - - # Only proxy admins can create administrative users - # Check if user_api_key_dict is actually a UserAPIKeyAuth instance (not a Depends object) - # This can happen when the function is called directly in tests - if ( - data.user_role in [LitellmUserRoles.PROXY_ADMIN, LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY] - and isinstance(user_api_key_dict, UserAPIKeyAuth) - and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN - ): - raise HTTPException( - status_code=403, - detail=f"Only proxy admins can create administrative users (proxy_admin, proxy_admin_viewer). Attempted to create user with role: {data.user_role}. Your role: {user_api_key_dict.user_role}" - ) data_json = data.json() # type: ignore data_json = _update_internal_new_user_params(data_json, data) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 3c1053c7b0..39b6774a61 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -16,7 +16,7 @@ import secrets import traceback import yaml from datetime import datetime, timedelta, timezone -from typing import Any, Dict, List, Literal, Optional, Tuple, cast +from typing import List, Literal, Optional, Tuple, cast from litellm.litellm_core_utils.safe_json_dumps import safe_dumps import fastapi from fastapi import APIRouter, Depends, Header, HTTPException, Query, Request, status @@ -1791,10 +1791,6 @@ async def delete_key_fn( if prisma_client is None: raise Exception("Not connected to DB!") - # Normalize litellm_changed_by: if it's a Header object or not a string, convert to None - if litellm_changed_by is not None and not isinstance(litellm_changed_by, str): - litellm_changed_by = None - ## only allow user to delete keys they own verbose_proxy_logger.debug( f"user_api_key_dict.user_role: {user_api_key_dict.user_role}" @@ -1807,7 +1803,6 @@ async def delete_key_fn( tokens=data.keys, user_api_key_cache=user_api_key_cache, user_api_key_dict=user_api_key_dict, - litellm_changed_by=litellm_changed_by, ) num_keys_to_be_deleted = len(data.keys) deleted_keys = data.keys @@ -1817,7 +1812,6 @@ async def delete_key_fn( prisma_client=prisma_client, user_api_key_cache=user_api_key_cache, user_api_key_dict=user_api_key_dict, - litellm_changed_by=litellm_changed_by, ) num_keys_to_be_deleted = len(data.key_aliases) deleted_keys = data.key_aliases @@ -2439,7 +2433,6 @@ async def delete_verification_tokens( tokens: List, user_api_key_cache: DualCache, user_api_key_dict: UserAPIKeyAuth, - litellm_changed_by: Optional[str] = None, ) -> Tuple[Optional[Dict], List[LiteLLM_VerificationToken]]: """ Helper that deletes the list of tokens from the database @@ -2476,43 +2469,38 @@ async def delete_verification_tokens( detail={"error": "No keys found"}, ) - if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value: - authorized_keys = _keys_being_deleted - else: - authorized_keys = [] - for key in _keys_being_deleted: - if await can_modify_verification_token( - key_info=key, - user_api_key_cache=user_api_key_cache, - user_api_key_dict=user_api_key_dict, - prisma_client=prisma_client, - ): - authorized_keys.append(key) - else: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail={ - "error": "You are not authorized to delete this key" - }, - ) - await _persist_deleted_verification_tokens( - keys=authorized_keys, - prisma_client=prisma_client, - user_api_key_dict=user_api_key_dict, - litellm_changed_by=litellm_changed_by, - ) - + # Assuming 'db' is your Prisma Client instance + # check if admin making request - don't filter by user-id if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value: deleted_tokens = await prisma_client.delete_data(tokens=tokens) + # else else: - deletion_tasks = [ - prisma_client.delete_data(tokens=[key.token]) - for key in authorized_keys - ] - await asyncio.gather(*deletion_tasks) + tasks = [] + deleted_tokens = [] + for key in _keys_being_deleted: - deleted_tokens = [key.token for key in authorized_keys] - if len(deleted_tokens) != len(tokens): + async def _delete_key(key: LiteLLM_VerificationToken): + if await can_modify_verification_token( + key_info=key, + user_api_key_cache=user_api_key_cache, + user_api_key_dict=user_api_key_dict, + prisma_client=prisma_client, + ): + await prisma_client.delete_data(tokens=[key.token]) + deleted_tokens.append(key.token) + else: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail={ + "error": "You are not authorized to delete this key" + }, + ) + + tasks.append(_delete_key(key)) + await asyncio.gather(*tasks) + + _num_deleted_tokens = len(deleted_tokens) + if _num_deleted_tokens != len(tokens): failed_tokens = [ token for token in tokens if token not in deleted_tokens ] @@ -2540,81 +2528,11 @@ async def delete_verification_tokens( return {"deleted_keys": deleted_tokens}, _keys_being_deleted -def _transform_verification_tokens_to_deleted_records( - keys: List[LiteLLM_VerificationToken], - user_api_key_dict: UserAPIKeyAuth, - litellm_changed_by: Optional[str] = None, -) -> List[Dict[str, Any]]: - """Transform verification tokens into deleted token records ready for persistence.""" - if not keys: - return [] - - deleted_at = datetime.now(timezone.utc) - records = [] - for key in keys: - key_payload = key.model_dump() - deleted_record = LiteLLM_DeletedVerificationToken( - **key_payload, - deleted_at=deleted_at, - deleted_by=user_api_key_dict.user_id, - deleted_by_api_key=user_api_key_dict.api_key, - litellm_changed_by=litellm_changed_by, - ) - record = deleted_record.model_dump() - - # Map org_id to organization_id (model uses org_id, but schema expects organization_id) - org_id_value = record.pop("org_id", None) - if org_id_value is not None: - record["organization_id"] = org_id_value - - for json_field in ["aliases", "config", "permissions", "metadata", "model_spend", "model_max_budget", "router_settings"]: - if json_field in record and record[json_field] is not None: - record[json_field] = json.dumps(record[json_field]) - - for rel_key in ("litellm_budget_table", "litellm_organization_table", "object_permission", "id"): - record.pop(rel_key, None) - - records.append(record) - - return records - - -async def _save_deleted_verification_token_records( - records: List[Dict[str, Any]], - prisma_client: PrismaClient, -) -> None: - """Save deleted verification token records to the database.""" - if not records: - return - await prisma_client.db.litellm_deletedverificationtoken.create_many( - data=records - ) - - -async def _persist_deleted_verification_tokens( - keys: List[LiteLLM_VerificationToken], - prisma_client: PrismaClient, - user_api_key_dict: UserAPIKeyAuth, - litellm_changed_by: Optional[str] = None, -) -> None: - """Persist deleted verification token records by transforming and saving them.""" - records = _transform_verification_tokens_to_deleted_records( - keys=keys, - user_api_key_dict=user_api_key_dict, - litellm_changed_by=litellm_changed_by, - ) - await _save_deleted_verification_token_records( - records=records, - prisma_client=prisma_client, - ) - - async def delete_key_aliases( key_aliases: List[str], user_api_key_cache: DualCache, prisma_client: PrismaClient, user_api_key_dict: UserAPIKeyAuth, - litellm_changed_by: Optional[str] = None, ) -> Tuple[Optional[Dict], List[LiteLLM_VerificationToken]]: _keys_being_deleted = await prisma_client.db.litellm_verificationtoken.find_many( where={"key_alias": {"in": key_aliases}} @@ -2625,7 +2543,6 @@ async def delete_key_aliases( tokens=tokens, user_api_key_cache=user_api_key_cache, user_api_key_dict=user_api_key_dict, - litellm_changed_by=litellm_changed_by, ) diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index c606420cc0..d1549b5116 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -34,10 +34,8 @@ from litellm.proxy._types import ( LiteLLM_OrganizationTableWithMembers, LiteLLM_TeamMembership, LiteLLM_TeamTable, - LiteLLM_DeletedTeamTable, LiteLLM_TeamTableCachedObj, LiteLLM_UserTable, - LiteLLM_VerificationToken, LitellmTableNames, LitellmUserRoles, Member, @@ -2020,28 +2018,6 @@ async def team_member_delete( ## DELETE KEYS CREATED BY USER FOR THIS TEAM if user_ids_to_delete: - from litellm.proxy.management_endpoints.key_management_endpoints import ( - _persist_deleted_verification_tokens, - ) - - # Fetch keys before deletion to persist them - keys_to_delete: List[LiteLLM_VerificationToken] = ( - await prisma_client.db.litellm_verificationtoken.find_many( - where={ - "user_id": {"in": list(user_ids_to_delete)}, - "team_id": data.team_id, - } - ) - ) - - if keys_to_delete: - await _persist_deleted_verification_tokens( - keys=keys_to_delete, - prisma_client=prisma_client, - user_api_key_dict=user_api_key_dict, - litellm_changed_by=None, - ) - await prisma_client.db.litellm_verificationtoken.delete_many( where={ "user_id": {"in": list(user_ids_to_delete)}, @@ -2427,13 +2403,6 @@ async def delete_team( team_row_pydantic = LiteLLM_TeamTable(**team_row_base.model_dump()) team_rows.append(team_row_pydantic) - await _persist_deleted_team_records( - teams=team_rows, - prisma_client=prisma_client, - user_api_key_dict=user_api_key_dict, - litellm_changed_by=litellm_changed_by, - ) - # Enterprise Feature - Audit Logging. Enable with litellm.store_audit_logs = True # we do this after the first for loop, since first for loop is for validation. we only want this inserted after validation passes if litellm.store_audit_logs is True: @@ -2469,25 +2438,6 @@ async def delete_team( # End of Audit logging ## DELETE ASSOCIATED KEYS - # Fetch keys before deletion to persist them - from litellm.proxy.management_endpoints.key_management_endpoints import ( - _persist_deleted_verification_tokens, - ) - - keys_to_delete: List[LiteLLM_VerificationToken] = ( - await prisma_client.db.litellm_verificationtoken.find_many( - where={"team_id": {"in": data.team_ids}} - ) - ) - - if keys_to_delete: - await _persist_deleted_verification_tokens( - keys=keys_to_delete, - prisma_client=prisma_client, - user_api_key_dict=user_api_key_dict, - litellm_changed_by=litellm_changed_by, - ) - await prisma_client.delete_data(team_id_list=data.team_ids, table_name="key") # ## DELETE TEAM MEMBERSHIPS @@ -2516,70 +2466,6 @@ async def delete_team( return deleted_teams - -def _transform_teams_to_deleted_records( - teams: List[LiteLLM_TeamTable], - user_api_key_dict: UserAPIKeyAuth, - litellm_changed_by: Optional[str] = None, -) -> List[Dict[str, Any]]: - """Transform teams into deleted team records ready for persistence.""" - if not teams: - return [] - - deleted_at = datetime.now(timezone.utc) - records = [] - for team in teams: - team_payload = team.model_dump() - deleted_record = LiteLLM_DeletedTeamTable( - **team_payload, - deleted_at=deleted_at, - deleted_by=user_api_key_dict.user_id, - deleted_by_api_key=user_api_key_dict.api_key, - litellm_changed_by=litellm_changed_by, - ) - record = deleted_record.model_dump() - - for json_field in ["members_with_roles", "metadata", "model_spend", "model_max_budget", "router_settings"]: - if json_field in record and record[json_field] is not None: - record[json_field] = json.dumps(record[json_field]) - - for rel_key in ("litellm_model_table", "object_permission", "id"): - record.pop(rel_key, None) - - records.append(record) - - return records - - -async def _save_deleted_team_records( - records: List[Dict[str, Any]], - prisma_client: PrismaClient, -) -> None: - """Save deleted team records to the database.""" - if not records: - return - await prisma_client.db.litellm_deletedteamtable.create_many( - data=records - ) - - -async def _persist_deleted_team_records( - teams: List[LiteLLM_TeamTable], - prisma_client: PrismaClient, - user_api_key_dict: UserAPIKeyAuth, - litellm_changed_by: Optional[str] = None, -) -> None: - """Persist deleted team records by transforming and saving them.""" - records = _transform_teams_to_deleted_records( - teams=teams, - user_api_key_dict=user_api_key_dict, - litellm_changed_by=litellm_changed_by, - ) - await _save_deleted_team_records( - records=records, - prisma_client=prisma_client, - ) - def validate_membership( user_api_key_dict: UserAPIKeyAuth, team_table: LiteLLM_TeamTable ): diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 71b398c59a..56fe093a8b 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -132,49 +132,6 @@ model LiteLLM_TeamTable { object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id]) } -// Audit table for deleted teams - preserves spend and team information for historical tracking -model LiteLLM_DeletedTeamTable { - id String @id @default(uuid()) - team_id String // Original team_id - team_alias String? - organization_id String? - object_permission_id String? - admins String[] - members String[] - members_with_roles Json @default("{}") - metadata Json @default("{}") - max_budget Float? - spend Float @default(0.0) - models String[] - max_parallel_requests Int? - tpm_limit BigInt? - rpm_limit BigInt? - budget_duration String? - budget_reset_at DateTime? - blocked Boolean @default(false) - model_spend Json @default("{}") - model_max_budget Json @default("{}") - router_settings Json? @default("{}") - team_member_permissions String[] @default([]) - model_id Int? // id for LiteLLM_ModelTable -> stores team-level model aliases - - // Original timestamps from team creation/updates - created_at DateTime? @map("created_at") - updated_at DateTime? @map("updated_at") - - // Deletion metadata - deleted_at DateTime @default(now()) @map("deleted_at") - deleted_by String? @map("deleted_by") // User who deleted the team - deleted_by_api_key String? @map("deleted_by_api_key") // API key hash that performed the deletion - litellm_changed_by String? @map("litellm_changed_by") // From litellm-changed-by header if provided - - @@index([team_id]) - @@index([deleted_at]) - @@index([organization_id]) - @@index([team_alias]) - @@index([created_at]) -} - // Track spend, rate limit, budget Users model LiteLLM_UserTable { user_id String @id @@ -302,62 +259,6 @@ model LiteLLM_VerificationToken { object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id]) } -// Audit table for deleted keys - preserves spend and key information for historical tracking -model LiteLLM_DeletedVerificationToken { - id String @id @default(uuid()) - token String // Original token (hashed) - key_name String? - key_alias String? - soft_budget_cooldown Boolean @default(false) - spend Float @default(0.0) - expires DateTime? - models String[] - aliases Json @default("{}") - config Json @default("{}") - user_id String? - team_id String? - permissions Json @default("{}") - max_parallel_requests Int? - metadata Json @default("{}") - blocked Boolean? - tpm_limit BigInt? - rpm_limit BigInt? - max_budget Float? - budget_duration String? - budget_reset_at DateTime? - allowed_cache_controls String[] @default([]) - allowed_routes String[] @default([]) - model_spend Json @default("{}") - model_max_budget Json @default("{}") - router_settings Json? @default("{}") - budget_id String? - organization_id String? - object_permission_id String? - created_at DateTime? // Original creation timestamp - created_by String? // Original creator - updated_at DateTime? // Last update timestamp before deletion - updated_by String? // Last user who updated before deletion - rotation_count Int? @default(0) - auto_rotate Boolean? @default(false) - rotation_interval String? - last_rotation_at DateTime? - key_rotation_at DateTime? - - // Deletion metadata - deleted_at DateTime @default(now()) @map("deleted_at") - deleted_by String? @map("deleted_by") // User who deleted the key - deleted_by_api_key String? @map("deleted_by_api_key") // API key hash that performed the deletion - litellm_changed_by String? @map("litellm_changed_by") // From litellm-changed-by header if provided - - @@index([token]) - @@index([deleted_at]) - @@index([user_id]) - @@index([team_id]) - @@index([organization_id]) - @@index([key_alias]) - @@index([created_at]) -} - model LiteLLM_EndUserTable { user_id String @id alias String? // admin-facing alias diff --git a/schema.prisma b/schema.prisma index 52170f2f3e..a16380fb5f 100644 --- a/schema.prisma +++ b/schema.prisma @@ -132,49 +132,6 @@ model LiteLLM_TeamTable { object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id]) } -// Audit table for deleted teams - preserves spend and team information for historical tracking -model LiteLLM_DeletedTeamTable { - id String @id @default(uuid()) - team_id String // Original team_id - team_alias String? - organization_id String? - object_permission_id String? - admins String[] - members String[] - members_with_roles Json @default("{}") - metadata Json @default("{}") - max_budget Float? - spend Float @default(0.0) - models String[] - max_parallel_requests Int? - tpm_limit BigInt? - rpm_limit BigInt? - budget_duration String? - budget_reset_at DateTime? - blocked Boolean @default(false) - model_spend Json @default("{}") - model_max_budget Json @default("{}") - router_settings Json? @default("{}") - team_member_permissions String[] @default([]) - model_id Int? // id for LiteLLM_ModelTable -> stores team-level model aliases - - // Original timestamps from team creation/updates - created_at DateTime? @map("created_at") - updated_at DateTime? @map("updated_at") - - // Deletion metadata - deleted_at DateTime @default(now()) @map("deleted_at") - deleted_by String? @map("deleted_by") // User who deleted the team - deleted_by_api_key String? @map("deleted_by_api_key") // API key hash that performed the deletion - litellm_changed_by String? @map("litellm_changed_by") // From litellm-changed-by header if provided - - @@index([team_id]) - @@index([deleted_at]) - @@index([organization_id]) - @@index([team_alias]) - @@index([created_at]) -} - // Track spend, rate limit, budget Users model LiteLLM_UserTable { user_id String @id @@ -302,62 +259,6 @@ model LiteLLM_VerificationToken { object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id]) } -// Audit table for deleted keys - preserves spend and key information for historical tracking -model LiteLLM_DeletedVerificationToken { - id String @id @default(uuid()) - token String // Original token (hashed) - key_name String? - key_alias String? - soft_budget_cooldown Boolean @default(false) - spend Float @default(0.0) - expires DateTime? - models String[] - aliases Json @default("{}") - config Json @default("{}") - user_id String? - team_id String? - permissions Json @default("{}") - max_parallel_requests Int? - metadata Json @default("{}") - blocked Boolean? - tpm_limit BigInt? - rpm_limit BigInt? - max_budget Float? - budget_duration String? - budget_reset_at DateTime? - allowed_cache_controls String[] @default([]) - allowed_routes String[] @default([]) - model_spend Json @default("{}") - model_max_budget Json @default("{}") - router_settings Json? @default("{}") - budget_id String? - organization_id String? - object_permission_id String? - created_at DateTime? // Original creation timestamp - created_by String? // Original creator - updated_at DateTime? // Last update timestamp before deletion - updated_by String? // Last user who updated before deletion - rotation_count Int? @default(0) - auto_rotate Boolean? @default(false) - rotation_interval String? - last_rotation_at DateTime? - key_rotation_at DateTime? - - // Deletion metadata - deleted_at DateTime @default(now()) @map("deleted_at") - deleted_by String? @map("deleted_by") // User who deleted the key - deleted_by_api_key String? @map("deleted_by_api_key") // API key hash that performed the deletion - litellm_changed_by String? @map("litellm_changed_by") // From litellm-changed-by header if provided - - @@index([token]) - @@index([deleted_at]) - @@index([user_id]) - @@index([team_id]) - @@index([organization_id]) - @@index([key_alias]) - @@index([created_at]) -} - model LiteLLM_EndUserTable { user_id String @id alias String? // admin-facing alias diff --git a/tests/proxy_admin_ui_tests/test_key_management.py b/tests/proxy_admin_ui_tests/test_key_management.py index a196080ead..126718af84 100644 --- a/tests/proxy_admin_ui_tests/test_key_management.py +++ b/tests/proxy_admin_ui_tests/test_key_management.py @@ -1061,7 +1061,6 @@ async def test_list_key_helper(prisma_client): api_key="sk-1234", user_id="admin", ), - litellm_changed_by=None, ) @@ -1182,7 +1181,6 @@ async def test_list_key_helper_team_filtering(prisma_client): api_key="sk-1234", user_id="admin", ), - litellm_changed_by=None, ) diff --git a/tests/proxy_unit_tests/test_key_generate_prisma.py b/tests/proxy_unit_tests/test_key_generate_prisma.py index 1a613a3db5..e0d6b7e81b 100644 --- a/tests/proxy_unit_tests/test_key_generate_prisma.py +++ b/tests/proxy_unit_tests/test_key_generate_prisma.py @@ -1166,10 +1166,8 @@ def test_delete_key_auth(prisma_client): asyncio.run(test()) except Exception as e: print("Got Exception", e) - # Handle different exception types - ProxyException has .message, others might have .detail or str(e) - error_message = getattr(e, "message", None) or getattr(e, "detail", None) or str(e) - print(f"Error message: {error_message}") - assert "Authentication Error" in error_message or "Invalid proxy server token" in error_message or "not found in db" in error_message + print(e.message) + assert "Authentication Error" in e.message pass @@ -2710,12 +2708,7 @@ async def test_reset_spend_authentication(prisma_client): _response = await new_user( data=NewUserRequest( tpm_limit=20, - ), - user_api_key_dict=UserAPIKeyAuth( - user_role=LitellmUserRoles.PROXY_ADMIN, - api_key=master_key, - user_id="1234", - ), + ) ) generate_key = "Bearer " + _response.key @@ -2735,12 +2728,7 @@ async def test_reset_spend_authentication(prisma_client): data=NewUserRequest( user_role=LitellmUserRoles.PROXY_ADMIN, tpm_limit=20, - ), - user_api_key_dict=UserAPIKeyAuth( - user_role=LitellmUserRoles.PROXY_ADMIN, - api_key=master_key, - user_id="1234", - ), + ) ) generate_key = "Bearer " + _response.key diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index 47395a1f32..c9a10e3c4d 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -31,13 +31,9 @@ from litellm.proxy.management_endpoints.key_management_endpoints import ( _check_team_key_limits, _common_key_generation_helper, _list_key_helper, - _persist_deleted_verification_tokens, - _save_deleted_verification_token_records, - _transform_verification_tokens_to_deleted_records, can_modify_verification_token, check_org_key_model_specific_limits, check_team_key_model_specific_limits, - delete_verification_tokens, generate_key_helper_fn, prepare_key_update_data, validate_key_team_change, @@ -2732,364 +2728,64 @@ def test_check_org_key_model_specific_limits_org_model_tpm_overallocation(): ) -def test_transform_verification_tokens_to_deleted_records(): - from datetime import datetime, timezone - - user_api_key_dict = UserAPIKeyAuth( - user_id="user-123", - api_key="sk-test", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - key1 = LiteLLM_VerificationToken( - token="hashed-token-1", - user_id="user-123", - team_id="team-456", - key_alias="test-key-1", - spend=100.0, - max_budget=1000.0, - models=["gpt-4"], - aliases={}, - config={}, - permissions={}, - metadata={"test": "value"}, - model_max_budget={}, - model_spend={}, - soft_budget_cooldown=False, - allowed_routes=[], - ) - - key2 = LiteLLM_VerificationToken( - token="hashed-token-2", - user_id="user-789", - team_id=None, - key_alias="test-key-2", - spend=50.0, - max_budget=500.0, - models=["gpt-3.5-turbo"], - aliases={"alias": "model"}, - config={"config": "value"}, - permissions={"permission": True}, - metadata={}, - model_max_budget={"gpt-4": {"budget_limit": 100.0}}, - model_spend={}, - soft_budget_cooldown=False, - allowed_routes=[], - ) - - records = _transform_verification_tokens_to_deleted_records( - keys=[key1, key2], - user_api_key_dict=user_api_key_dict, - litellm_changed_by="admin-user", - ) - - assert len(records) == 2 - assert all("deleted_at" in record for record in records) - assert all("deleted_by" in record for record in records) - assert all("deleted_by_api_key" in record for record in records) - assert all("litellm_changed_by" in record for record in records) - assert all(record["deleted_by"] == "user-123" for record in records) - assert all(record["deleted_by_api_key"] == user_api_key_dict.api_key for record in records) - assert all(record["litellm_changed_by"] == "admin-user" for record in records) - - record1 = records[0] - assert record1["token"] == "hashed-token-1" - assert record1["user_id"] == "user-123" - assert record1["team_id"] == "team-456" - assert isinstance(record1["aliases"], str) - assert isinstance(record1["config"], str) - assert isinstance(record1["permissions"], str) - assert isinstance(record1["metadata"], str) - assert "litellm_budget_table" not in record1 - assert "litellm_organization_table" not in record1 - assert "object_permission" not in record1 - assert "id" not in record1 - - record2 = records[1] - assert record2["token"] == "hashed-token-2" - assert isinstance(record2["model_max_budget"], str) - - -def test_transform_verification_tokens_to_deleted_records_empty_list(): - user_api_key_dict = UserAPIKeyAuth( - user_id="user-123", - api_key="sk-test", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - records = _transform_verification_tokens_to_deleted_records( - keys=[], - user_api_key_dict=user_api_key_dict, - ) - - assert records == [] - - -@pytest.mark.asyncio -async def test_save_deleted_verification_token_records(): - mock_prisma_client = AsyncMock() - mock_create_many = AsyncMock() - mock_prisma_client.db.litellm_deletedverificationtoken.create_many = ( - mock_create_many - ) - - records = [ - { - "token": "hashed-token-1", - "user_id": "user-123", - "deleted_at": "2024-01-01T00:00:00Z", - "deleted_by": "admin", - }, - { - "token": "hashed-token-2", - "user_id": "user-456", - "deleted_at": "2024-01-01T00:00:00Z", - "deleted_by": "admin", - }, - ] - - await _save_deleted_verification_token_records( - records=records, prisma_client=mock_prisma_client - ) - - mock_create_many.assert_called_once_with(data=records) - - -@pytest.mark.asyncio -async def test_save_deleted_verification_token_records_empty_list(): - mock_prisma_client = AsyncMock() - mock_create_many = AsyncMock() - mock_prisma_client.db.litellm_deletedverificationtoken.create_many = ( - mock_create_many - ) - - await _save_deleted_verification_token_records( - records=[], prisma_client=mock_prisma_client - ) - - mock_create_many.assert_not_called() - - -@pytest.mark.asyncio -async def test_persist_deleted_verification_tokens(): - mock_prisma_client = AsyncMock() - mock_create_many = AsyncMock() - mock_prisma_client.db.litellm_deletedverificationtoken.create_many = ( - mock_create_many - ) - - user_api_key_dict = UserAPIKeyAuth( - user_id="user-123", - api_key="sk-test", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - key = LiteLLM_VerificationToken( - token="hashed-token-1", - user_id="user-123", - team_id="team-456", - key_alias="test-key", - spend=100.0, - max_budget=1000.0, - models=["gpt-4"], - aliases={}, - config={}, - permissions={}, - metadata={}, - model_max_budget={}, - model_spend={}, - soft_budget_cooldown=False, - allowed_routes=[], - ) - - await _persist_deleted_verification_tokens( - keys=[key], - prisma_client=mock_prisma_client, - user_api_key_dict=user_api_key_dict, - litellm_changed_by="admin-user", - ) - - mock_create_many.assert_called_once() - call_args = mock_create_many.call_args - assert "data" in call_args.kwargs - records = call_args.kwargs["data"] - assert len(records) == 1 - assert records[0]["token"] == "hashed-token-1" - assert records[0]["deleted_by"] == "user-123" - assert records[0]["litellm_changed_by"] == "admin-user" - - -@pytest.mark.asyncio -async def test_delete_verification_tokens_persists_deleted_keys(monkeypatch): - mock_prisma_client = AsyncMock() - mock_user_api_key_cache = MagicMock() - - user_api_key_dict = UserAPIKeyAuth( - user_id="admin-user", - api_key="sk-admin", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - key1 = LiteLLM_VerificationToken( - token="hashed-token-1", - user_id="user-123", - team_id="team-456", - key_alias="test-key-1", - spend=100.0, - max_budget=1000.0, - models=["gpt-4"], - aliases={}, - config={}, - permissions={}, - metadata={}, - model_max_budget={}, - model_spend={}, - soft_budget_cooldown=False, - allowed_routes=[], - ) - - key2 = LiteLLM_VerificationToken( - token="hashed-token-2", - user_id="user-789", - team_id=None, - key_alias="test-key-2", - spend=50.0, - max_budget=500.0, - models=["gpt-3.5-turbo"], - aliases={}, - config={}, - permissions={}, - metadata={}, - model_max_budget={}, - model_spend={}, - soft_budget_cooldown=False, - allowed_routes=[], - ) - - mock_find_many = AsyncMock(return_value=[key1, key2]) - mock_prisma_client.db.litellm_verificationtoken.find_many = mock_find_many - - # delete_data returns {"deleted_keys": ...} from utils.py line 3049 - # The function at line 2410 assigns it to deleted_tokens - # Then at line 2444 returns {"deleted_keys": deleted_tokens} - # So if delete_data returns {"deleted_keys": list}, then result would be nested - # But looking at the error, it seems like delete_data might return just the list - # Or the code extracts it. Let's return the list directly since that's what the test expects - mock_delete_data = AsyncMock(return_value=["hashed-token-1", "hashed-token-2"]) - mock_prisma_client.delete_data = mock_delete_data - - # Mock cache delete_cache method - mock_user_api_key_cache.delete_cache = MagicMock() - - mock_create_many = AsyncMock() - mock_prisma_client.db.litellm_deletedverificationtoken.create_many = ( - mock_create_many - ) - - def mock_hash_token(token): - return token if not token.startswith("sk-") else f"hashed-{token}" - - monkeypatch.setattr( - "litellm.proxy.management_endpoints.key_management_endpoints._hash_token_if_needed", - mock_hash_token, - ) - monkeypatch.setattr( - "litellm.proxy.management_endpoints.key_management_endpoints.hash_token", - mock_hash_token, - ) - monkeypatch.setattr( - "litellm.proxy.proxy_server.prisma_client", - mock_prisma_client, - ) - - result, deleted_keys = await delete_verification_tokens( - tokens=["sk-token-1", "sk-token-2"], - user_api_key_cache=mock_user_api_key_cache, - user_api_key_dict=user_api_key_dict, - litellm_changed_by="admin-user", - ) - - mock_create_many.assert_called_once() - call_args = mock_create_many.call_args - assert "data" in call_args.kwargs - records = call_args.kwargs["data"] - assert len(records) == 2 - assert all(record["deleted_by"] == "admin-user" for record in records) - assert all(record["litellm_changed_by"] == "admin-user" for record in records) - # delete_data returns the list directly, which gets wrapped in {"deleted_keys": ...} - assert isinstance(result["deleted_keys"], list) - assert set(result["deleted_keys"]) == {"hashed-token-1", "hashed-token-2"} - assert len(deleted_keys) == 2 - - -@pytest.mark.asyncio -async def test_delete_key_fn_persists_deleted_keys(monkeypatch): - from litellm.proxy._types import KeyRequest - from litellm.proxy.management_endpoints.key_management_endpoints import ( - delete_key_fn, - delete_verification_tokens, - ) - - mock_prisma_client = AsyncMock() - mock_user_api_key_cache = MagicMock() - - user_api_key_dict = UserAPIKeyAuth( - user_id="admin-user", - api_key="sk-admin", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - key1 = LiteLLM_VerificationToken( - token="hashed-token-1", - user_id="user-123", - team_id="team-456", - key_alias="test-key-1", - spend=100.0, - max_budget=1000.0, - models=["gpt-4"], - aliases={}, - config={}, - permissions={}, - metadata={}, - model_max_budget={}, - model_spend={}, - soft_budget_cooldown=False, - allowed_routes=[], - ) - - async def mock_delete_verification_tokens(*args, **kwargs): - return ({"deleted_keys": ["sk-token-1"]}, [key1]) - - monkeypatch.setattr( - "litellm.proxy.management_endpoints.key_management_endpoints.delete_verification_tokens", - mock_delete_verification_tokens, - ) - monkeypatch.setattr( - "litellm.proxy.proxy_server.prisma_client", - mock_prisma_client, - ) - monkeypatch.setattr( - "litellm.proxy.proxy_server.user_api_key_cache", - mock_user_api_key_cache, - ) - monkeypatch.setattr( - "litellm.proxy.management_endpoints.key_management_endpoints.KeyManagementEventHooks.async_key_deleted_hook", - AsyncMock(), - ) - - data = KeyRequest(keys=["sk-token-1"]) - - result = await delete_key_fn( - data=data, - user_api_key_dict=user_api_key_dict, - litellm_changed_by="admin-user", - ) - - assert result["deleted_keys"] == ["sk-token-1"] - - @pytest.mark.asyncio async def test_can_delete_verification_token_proxy_admin_team_key(monkeypatch): + """Test that proxy admin can delete any team key.""" + key_info = LiteLLM_VerificationToken( + token="test-token", + user_id="other-user", + team_id="test-team-123", + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + user_id="admin-user", + api_key="sk-admin", + ) + + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = MagicMock() + + result = await can_modify_verification_token( + key_info=key_info, + user_api_key_cache=mock_user_api_key_cache, + user_api_key_dict=user_api_key_dict, + prisma_client=mock_prisma_client, + ) + + assert result is True + + +@pytest.mark.asyncio +async def test_can_delete_verification_token_proxy_admin_personal_key(monkeypatch): + """Test that proxy admin can delete any personal key.""" + key_info = LiteLLM_VerificationToken( + token="test-token", + user_id="other-user", + team_id=None, + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + user_id="admin-user", + api_key="sk-admin", + ) + + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = MagicMock() + + result = await can_modify_verification_token( + key_info=key_info, + user_api_key_cache=mock_user_api_key_cache, + user_api_key_dict=user_api_key_dict, + prisma_client=mock_prisma_client, + ) + + assert result is True + + +@pytest.mark.asyncio +async def test_can_delete_verification_token_team_admin_own_team(monkeypatch): """Test that team admin can delete team keys from their own team.""" key_info = LiteLLM_VerificationToken( token="test-token", 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 a1e8efdbb4..bbff7448e1 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py @@ -33,13 +33,8 @@ from litellm.proxy.management_endpoints.team_endpoints import ( from litellm.proxy.management_endpoints.team_endpoints import ( GetTeamMemberPermissionsResponse, UpdateTeamMemberPermissionsRequest, - _persist_deleted_team_records, - _save_deleted_team_records, - _transform_teams_to_deleted_records, - delete_team, router, team_member_add_duplication_check, - team_member_delete, validate_team_org_change, ) from litellm.proxy.management_helpers.team_member_permission_checks import ( @@ -2265,7 +2260,6 @@ async def test_team_member_delete_cleans_membership(mock_db_client, mock_admin_a # Verification token deletion should be called mock_db_client.db.litellm_verificationtoken = MagicMock() - mock_db_client.db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) mock_db_client.db.litellm_verificationtoken.delete_many = AsyncMock(return_value=MagicMock()) # Execute @@ -2313,7 +2307,6 @@ async def test_team_member_delete_cleans_verification_tokens(mock_db_client, moc mock_db_client.db.litellm_teammembership.delete_many = AsyncMock(return_value=MagicMock()) mock_db_client.db.litellm_verificationtoken = MagicMock() - mock_db_client.db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) mock_db_client.db.litellm_verificationtoken.delete_many = AsyncMock(return_value=MagicMock()) await team_member_delete( @@ -4332,348 +4325,6 @@ async def test_update_team_guardrails_with_org_id(): assert first_call_kwargs["include"]["teams"] is True -def test_transform_teams_to_deleted_records(): - from datetime import datetime, timezone - - user_api_key_dict = UserAPIKeyAuth( - user_id="user-123", - api_key="sk-test", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - team1 = LiteLLM_TeamTable( - team_id="team-1", - team_alias="test-team-1", - members_with_roles=[ - Member(user_id="user-1", role="admin"), - Member(user_id="user-2", role="user"), - ], - metadata={"test": "value"}, - model_max_budget={}, - model_spend={}, - ) - - team2 = LiteLLM_TeamTable( - team_id="team-2", - team_alias="test-team-2", - members_with_roles=[], - metadata=None, - model_max_budget={"gpt-4": {"budget_limit": 100.0}}, - model_spend={}, - ) - - records = _transform_teams_to_deleted_records( - teams=[team1, team2], - user_api_key_dict=user_api_key_dict, - litellm_changed_by="admin-user", - ) - - assert len(records) == 2 - assert all("deleted_at" in record for record in records) - assert all("deleted_by" in record for record in records) - assert all("deleted_by_api_key" in record for record in records) - assert all("litellm_changed_by" in record for record in records) - assert all(record["deleted_by"] == "user-123" for record in records) - # UserAPIKeyAuth hashes the api_key, so we check against the hashed value - assert all(record["deleted_by_api_key"] == user_api_key_dict.api_key for record in records) - assert all(record["litellm_changed_by"] == "admin-user" for record in records) - - record1 = records[0] - assert record1["team_id"] == "team-1" - assert isinstance(record1["members_with_roles"], str) - assert isinstance(record1["metadata"], str) - assert "litellm_model_table" not in record1 - assert "object_permission" not in record1 - assert "id" not in record1 - - record2 = records[1] - assert record2["team_id"] == "team-2" - # model_max_budget should be converted to JSON string if it exists - if "model_max_budget" in record2: - assert isinstance(record2["model_max_budget"], str) - - -def test_transform_teams_to_deleted_records_empty_list(): - user_api_key_dict = UserAPIKeyAuth( - user_id="user-123", - api_key="sk-test", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - records = _transform_teams_to_deleted_records( - teams=[], - user_api_key_dict=user_api_key_dict, - ) - - assert records == [] - - -@pytest.mark.asyncio -async def test_save_deleted_team_records(): - mock_prisma_client = AsyncMock() - mock_create_many = AsyncMock() - mock_prisma_client.db.litellm_deletedteamtable.create_many = mock_create_many - - records = [ - { - "team_id": "team-1", - "team_alias": "test-team-1", - "deleted_at": "2024-01-01T00:00:00Z", - "deleted_by": "admin", - }, - { - "team_id": "team-2", - "team_alias": "test-team-2", - "deleted_at": "2024-01-01T00:00:00Z", - "deleted_by": "admin", - }, - ] - - await _save_deleted_team_records(records=records, prisma_client=mock_prisma_client) - - mock_create_many.assert_called_once_with(data=records) - - -@pytest.mark.asyncio -async def test_save_deleted_team_records_empty_list(): - mock_prisma_client = AsyncMock() - mock_create_many = AsyncMock() - mock_prisma_client.db.litellm_deletedteamtable.create_many = mock_create_many - - await _save_deleted_team_records(records=[], prisma_client=mock_prisma_client) - - mock_create_many.assert_not_called() - - -@pytest.mark.asyncio -async def test_persist_deleted_team_records(): - mock_prisma_client = AsyncMock() - mock_create_many = AsyncMock() - mock_prisma_client.db.litellm_deletedteamtable.create_many = mock_create_many - - user_api_key_dict = UserAPIKeyAuth( - user_id="user-123", - api_key="sk-test", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - team = LiteLLM_TeamTable( - team_id="team-1", - team_alias="test-team", - members_with_roles=[ - Member(user_id="user-1", role="admin"), - ], - metadata={}, - model_max_budget={}, - model_spend={}, - ) - - await _persist_deleted_team_records( - teams=[team], - prisma_client=mock_prisma_client, - user_api_key_dict=user_api_key_dict, - litellm_changed_by="admin-user", - ) - - mock_create_many.assert_called_once() - call_args = mock_create_many.call_args - assert "data" in call_args.kwargs - records = call_args.kwargs["data"] - assert len(records) == 1 - assert records[0]["team_id"] == "team-1" - assert records[0]["deleted_by"] == "user-123" - assert records[0]["litellm_changed_by"] == "admin-user" - - -@pytest.mark.asyncio -async def test_delete_team_persists_deleted_teams(monkeypatch): - from litellm.proxy._types import DeleteTeamRequest - - mock_prisma_client = AsyncMock() - mock_user_api_key_dict = UserAPIKeyAuth( - user_id="admin-user", - api_key="sk-admin", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - team1 = LiteLLM_TeamTable( - team_id="team-1", - team_alias="test-team-1", - members_with_roles=[ - Member(user_id="user-1", role="admin"), - ], - metadata={}, - model_max_budget={}, - model_spend={}, - ) - - mock_find_unique = AsyncMock(return_value=team1) - mock_prisma_client.db.litellm_teamtable.find_unique = mock_find_unique - - mock_delete_data = AsyncMock(return_value={"deleted_teams": ["team-1"]}) - mock_prisma_client.delete_data = mock_delete_data - - mock_create_many_teams = AsyncMock() - mock_prisma_client.db.litellm_deletedteamtable.create_many = mock_create_many_teams - - mock_create_many_keys = AsyncMock() - mock_prisma_client.db.litellm_deletedverificationtoken.create_many = ( - mock_create_many_keys - ) - - mock_find_many_keys = AsyncMock(return_value=[]) - mock_prisma_client.db.litellm_verificationtoken.find_many = mock_find_many_keys - - monkeypatch.setattr( - "litellm.proxy.proxy_server.prisma_client", - mock_prisma_client, - ) - monkeypatch.setattr( - "litellm.proxy.proxy_server.create_audit_log_for_update", - AsyncMock(), - ) - monkeypatch.setattr( - "litellm.proxy.proxy_server.litellm_proxy_admin_name", - "admin", - ) - monkeypatch.setattr( - "litellm.proxy.management_endpoints.team_endpoints.team_member_delete", - AsyncMock(return_value=team1), - ) - - data = DeleteTeamRequest(team_ids=["team-1"]) - - result = await delete_team( - data=data, - http_request=MagicMock(), - user_api_key_dict=mock_user_api_key_dict, - litellm_changed_by="admin-user", - ) - - mock_create_many_teams.assert_called_once() - call_args = mock_create_many_teams.call_args - assert "data" in call_args.kwargs - records = call_args.kwargs["data"] - assert len(records) == 1 - assert records[0]["team_id"] == "team-1" - assert records[0]["deleted_by"] == "admin-user" - assert records[0]["litellm_changed_by"] == "admin-user" - - -@pytest.mark.asyncio -async def test_team_member_delete_persists_deleted_keys(monkeypatch): - from litellm.proxy._types import TeamMemberDeleteRequest - from litellm.proxy.management_endpoints.key_management_endpoints import ( - LiteLLM_VerificationToken, - ) - - mock_prisma_client = AsyncMock() - mock_user_api_key_dict = UserAPIKeyAuth( - user_id="admin-user", - api_key="sk-admin", - user_role=LitellmUserRoles.PROXY_ADMIN.value, - ) - - team = LiteLLM_TeamTable( - team_id="team-1", - team_alias="test-team", - members_with_roles=[ - Member(user_id="user-123", role="admin"), - ], - metadata={}, - model_max_budget={}, - model_spend={}, - ) - - key1 = LiteLLM_VerificationToken( - token="hashed-token-1", - user_id="user-123", - team_id="team-1", - key_alias="test-key-1", - spend=100.0, - max_budget=1000.0, - models=["gpt-4"], - aliases={}, - config={}, - permissions={}, - metadata={}, - model_max_budget={}, - ) - - key2 = LiteLLM_VerificationToken( - token="hashed-token-2", - user_id="user-123", - team_id="team-1", - key_alias="test-key-2", - spend=50.0, - max_budget=500.0, - models=["gpt-3.5-turbo"], - aliases={}, - config={}, - permissions={}, - metadata={}, - model_max_budget={}, - ) - - mock_find_unique_team = AsyncMock(return_value=team) - mock_prisma_client.db.litellm_teamtable.find_unique = mock_find_unique_team - - mock_find_many_user = AsyncMock( - return_value=[ - MagicMock( - user_id="user-123", - teams=["team-1"], - model_dump=lambda: {"user_id": "user-123", "teams": ["team-1"]}, - ) - ] - ) - mock_prisma_client.db.litellm_usertable.find_many = mock_find_many_user - - mock_update_team = AsyncMock() - mock_prisma_client.db.litellm_teamtable.update = mock_update_team - - mock_update_user = AsyncMock() - mock_prisma_client.db.litellm_usertable.update = mock_update_user - - mock_delete_membership = AsyncMock() - mock_prisma_client.db.litellm_teammembership.delete_many = mock_delete_membership - - mock_find_many_keys = AsyncMock(return_value=[key1, key2]) - mock_prisma_client.db.litellm_verificationtoken.find_many = mock_find_many_keys - - mock_delete_keys = AsyncMock() - mock_prisma_client.db.litellm_verificationtoken.delete_many = mock_delete_keys - - mock_create_many_keys = AsyncMock() - mock_prisma_client.db.litellm_deletedverificationtoken.create_many = ( - mock_create_many_keys - ) - - monkeypatch.setattr( - "litellm.proxy.proxy_server.prisma_client", - mock_prisma_client, - ) - monkeypatch.setattr( - "litellm.proxy.management_endpoints.team_endpoints._is_user_team_admin", - lambda **kwargs: True, - ) - - data = TeamMemberDeleteRequest(team_id="team-1", user_id="user-123") - - result = await team_member_delete( - data=data, - user_api_key_dict=mock_user_api_key_dict, - ) - - mock_create_many_keys.assert_called_once() - call_args = mock_create_many_keys.call_args - assert "data" in call_args.kwargs - records = call_args.kwargs["data"] - assert len(records) == 2 - assert all(record["deleted_by"] == "admin-user" for record in records) - assert all(record["team_id"] == "team-1" for record in records) - assert all(record["user_id"] == "user-123" for record in records) - mock_delete_keys.assert_called_once() @pytest.mark.asyncio async def test_new_team_negative_max_budget(): """