mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
[Fix] Key Expiry: remove check_db_only, add service account guard, fix uncheck UI bug
- _validate_regenerate_key_duration_against_team: remove check_db_only=True from get_team_object call so cache is used on hits and DB only on misses, consistent with the guideline of avoiding unnecessary direct DB queries. - Add service account guard (user_id is None → skip validation), mirroring the enterprise add_team_member_key_duration guard so service account keys are not subject to team member duration limits during regeneration. - regenerate_key_modal.tsx: add else branch to the Never Expires onChange handler so unchecking resets the form field to undefined instead of leaving it as null (which the backend would interpret as never-expires). - Tests: set mock_key.user_id = "user-123" explicitly in all regeneration tests where validation should fire; add test_service_account_key_skips_validation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fe6fe252bb
commit
ca0aef1a4d
3 changed files with 37 additions and 1 deletions
|
|
@ -3408,6 +3408,11 @@ async def _validate_regenerate_key_duration_against_team(
|
|||
if data is None:
|
||||
return
|
||||
|
||||
# Service account keys are exempt from team member duration limits,
|
||||
# mirroring the enterprise add_team_member_key_duration guard.
|
||||
if getattr(key_in_db, "user_id", None) is None:
|
||||
return
|
||||
|
||||
# Determine the user's requested duration in seconds.
|
||||
# Distinguish "duration not sent" (leave unchanged) from "duration: null" (never expires).
|
||||
if data.duration is None:
|
||||
|
|
@ -3427,7 +3432,6 @@ async def _validate_regenerate_key_duration_against_team(
|
|||
prisma_client=prisma_client,
|
||||
user_api_key_cache=user_api_key_cache,
|
||||
parent_otel_span=None,
|
||||
check_db_only=True,
|
||||
)
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.warning(
|
||||
|
|
|
|||
|
|
@ -6602,6 +6602,7 @@ class TestValidateRegenerateKeyDurationAgainstTeam:
|
|||
|
||||
mock_key = MagicMock(spec=LiteLLM_VerificationToken)
|
||||
mock_key.team_id = "team-123"
|
||||
mock_key.user_id = "user-123"
|
||||
mock_prisma = AsyncMock()
|
||||
mock_cache = MagicMock()
|
||||
# Should not raise
|
||||
|
|
@ -6622,6 +6623,7 @@ class TestValidateRegenerateKeyDurationAgainstTeam:
|
|||
|
||||
mock_key = MagicMock(spec=LiteLLM_VerificationToken)
|
||||
mock_key.team_id = "team-123"
|
||||
mock_key.user_id = "user-123"
|
||||
# No duration kwarg at all → not in model_fields_set
|
||||
data = RegenerateKeyRequest()
|
||||
mock_prisma = AsyncMock()
|
||||
|
|
@ -6644,6 +6646,7 @@ class TestValidateRegenerateKeyDurationAgainstTeam:
|
|||
|
||||
mock_key = MagicMock(spec=LiteLLM_VerificationToken)
|
||||
mock_key.team_id = "team-123"
|
||||
mock_key.user_id = "user-123"
|
||||
# Explicitly set duration=None → "duration" IS in model_fields_set
|
||||
data = RegenerateKeyRequest(duration=None)
|
||||
assert "duration" in data.model_fields_set
|
||||
|
|
@ -6678,6 +6681,7 @@ class TestValidateRegenerateKeyDurationAgainstTeam:
|
|||
|
||||
mock_key = MagicMock(spec=LiteLLM_VerificationToken)
|
||||
mock_key.team_id = "team-123"
|
||||
mock_key.user_id = "user-123"
|
||||
data = RegenerateKeyRequest(duration="999d")
|
||||
|
||||
mock_team = MagicMock(spec=LiteLLM_TeamTableCachedObj)
|
||||
|
|
@ -6708,6 +6712,7 @@ class TestValidateRegenerateKeyDurationAgainstTeam:
|
|||
|
||||
mock_key = MagicMock(spec=LiteLLM_VerificationToken)
|
||||
mock_key.team_id = None
|
||||
mock_key.user_id = "user-123"
|
||||
data = RegenerateKeyRequest(duration="10d")
|
||||
mock_prisma = AsyncMock()
|
||||
mock_cache = MagicMock()
|
||||
|
|
@ -6719,6 +6724,28 @@ class TestValidateRegenerateKeyDurationAgainstTeam:
|
|||
user_api_key_cache=mock_cache,
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_account_key_skips_validation(self):
|
||||
"""Service account keys (user_id=None) are exempt from team duration limits."""
|
||||
from litellm.proxy.management_endpoints.key_management_endpoints import (
|
||||
_validate_regenerate_key_duration_against_team,
|
||||
)
|
||||
from litellm.proxy._types import RegenerateKeyRequest
|
||||
|
||||
mock_key = MagicMock(spec=LiteLLM_VerificationToken)
|
||||
mock_key.team_id = "team-123"
|
||||
mock_key.user_id = None # service account key has no user_id
|
||||
data = RegenerateKeyRequest(duration="999d")
|
||||
mock_prisma = AsyncMock()
|
||||
mock_cache = MagicMock()
|
||||
# Should not raise even though "999d" would exceed a finite team max
|
||||
await _validate_regenerate_key_duration_against_team(
|
||||
data=data,
|
||||
key_in_db=mock_key,
|
||||
prisma_client=mock_prisma,
|
||||
user_api_key_cache=mock_cache,
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_duration_within_team_max_passes(self):
|
||||
from litellm.proxy.management_endpoints.key_management_endpoints import (
|
||||
|
|
@ -6728,6 +6755,7 @@ class TestValidateRegenerateKeyDurationAgainstTeam:
|
|||
|
||||
mock_key = MagicMock(spec=LiteLLM_VerificationToken)
|
||||
mock_key.team_id = "team-123"
|
||||
mock_key.user_id = "user-123"
|
||||
data = RegenerateKeyRequest(duration="3d")
|
||||
|
||||
mock_team = MagicMock(spec=LiteLLM_TeamTableCachedObj)
|
||||
|
|
@ -6758,6 +6786,7 @@ class TestValidateRegenerateKeyDurationAgainstTeam:
|
|||
|
||||
mock_key = MagicMock(spec=LiteLLM_VerificationToken)
|
||||
mock_key.team_id = "team-123"
|
||||
mock_key.user_id = "user-123"
|
||||
data = RegenerateKeyRequest(duration="10d")
|
||||
|
||||
mock_team = MagicMock(spec=LiteLLM_TeamTableCachedObj)
|
||||
|
|
@ -6790,6 +6819,7 @@ class TestValidateRegenerateKeyDurationAgainstTeam:
|
|||
|
||||
mock_key = MagicMock(spec=LiteLLM_VerificationToken)
|
||||
mock_key.team_id = "team-123"
|
||||
mock_key.user_id = "user-123"
|
||||
data = RegenerateKeyRequest(duration=None) # null = never expires = infinite
|
||||
|
||||
mock_team = MagicMock(spec=LiteLLM_TeamTableCachedObj)
|
||||
|
|
|
|||
|
|
@ -240,6 +240,8 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
if (checked) {
|
||||
form.setFieldValue("duration", null);
|
||||
setRegenerateFormData((prev: any) => ({ ...prev, duration: null }));
|
||||
} else {
|
||||
form.setFieldValue("duration", undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue