mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
fix(identity): invalidate identity cache on key deletion
delete_verification_tokens (the /key/delete path) cleared the legacy key cache but never the identity cache, so a revoked key kept authenticating from its identity:v1:<hash> entry until the TTL expired. Wire invalidate_identity_for_token into the deletion loop. The other deletion sites already route through _delete_cache_key_object, which invalidates; this was the one path that bypassed it (Veria review finding).
This commit is contained in:
parent
c99588c7f4
commit
80fcb674cf
2 changed files with 55 additions and 0 deletions
|
|
@ -3936,11 +3936,19 @@ async def delete_verification_tokens(
|
|||
verbose_proxy_logger.debug(traceback.format_exc())
|
||||
raise e
|
||||
|
||||
from litellm.identity.invalidation import invalidate_identity_for_token
|
||||
|
||||
for key in tokens:
|
||||
user_api_key_cache.delete_cache(key)
|
||||
# remove hash token from cache
|
||||
hashed_token = hash_token(cast(str, key))
|
||||
user_api_key_cache.delete_cache(hashed_token)
|
||||
# `tokens` are already hashed (see `_hash_token_if_needed` above), so
|
||||
# `key` is the same hash the identity cache is keyed under. Without this
|
||||
# the `identity:v1:<hash>` entry outlives the deleted key until its TTL.
|
||||
await invalidate_identity_for_token(
|
||||
token_hash=key, dual_cache=user_api_key_cache
|
||||
)
|
||||
|
||||
return {
|
||||
"deleted_keys": deleted_tokens,
|
||||
|
|
|
|||
|
|
@ -11668,3 +11668,50 @@ async def test_ghsa_q775_default_team_id_does_not_grant_session_token_exemption(
|
|||
msg = str(getattr(err, "detail", "")) + str(getattr(err, "message", ""))
|
||||
assert str(code) == "400"
|
||||
assert "cannot exceed" in msg.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_verification_tokens_clears_identity_cache():
|
||||
"""A revoked key must not survive in the identity cache.
|
||||
|
||||
delete_verification_tokens clears the legacy key cache; it must also
|
||||
invalidate the ``identity:v1:<hash>`` entry, otherwise a deleted key
|
||||
keeps authenticating until the identity-cache TTL expires.
|
||||
"""
|
||||
from litellm.identity.cache import IdentityCache
|
||||
from litellm.proxy._types import LitellmUserRoles, hash_token
|
||||
from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache
|
||||
from litellm.proxy.management_endpoints import key_management_endpoints as kme
|
||||
|
||||
raw_key = "sk-delete-identity"
|
||||
hashed = hash_token(raw_key)
|
||||
|
||||
backend = UserApiKeyCache()
|
||||
identity_cache = IdentityCache(dual_cache=backend)
|
||||
await identity_cache.set(hashed, UserAPIKeyAuth(token=hashed, user_id="u1"))
|
||||
assert await identity_cache.get(hashed) is not None
|
||||
|
||||
admin = UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin", api_key="sk-admin"
|
||||
)
|
||||
|
||||
prisma = MagicMock()
|
||||
prisma.delete_data = AsyncMock(return_value=[hashed])
|
||||
|
||||
key_row = MagicMock()
|
||||
key_row.token = hashed
|
||||
repo = MagicMock()
|
||||
repo.table.find_many = AsyncMock(return_value=[key_row])
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.prisma_client", prisma),
|
||||
patch.object(kme, "VerificationTokenRepository", MagicMock(return_value=repo)),
|
||||
patch.object(kme, "_persist_deleted_verification_tokens", new=AsyncMock()),
|
||||
):
|
||||
await kme.delete_verification_tokens(
|
||||
tokens=[raw_key],
|
||||
user_api_key_cache=backend,
|
||||
user_api_key_dict=admin,
|
||||
)
|
||||
|
||||
assert await identity_cache.get(hashed) is None
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue