mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix(scim): treat NULL blocked column as unblocked when deprovisioning
The Prisma schema declares LiteLLM_VerificationToken.blocked as a
nullable Boolean with no default, so virtual keys created via the
key management endpoint persist with blocked=NULL. SQL equality
(`blocked = false`) never matches NULL rows, so the previous
`where={'blocked': not blocked}` filter silently skipped virtually
all real keys when SCIM tried to block them. This made SCIM
deprovisioning a no-op — and especially dangerous in DELETE flows
where the user row is removed afterwards, leaving orphaned but
fully-functional keys.
Match both `False` and `None` when blocking, and only `True`
when unblocking, so the state flip (and cache invalidation) actually
fires for the keys it should.
This commit is contained in:
parent
236e896189
commit
9aef4ee695
36 changed files with 25 additions and 6 deletions
|
|
@ -351,15 +351,25 @@ async def _set_user_keys_blocked(user_id: str, blocked: bool) -> int:
|
|||
prisma_client = await _get_prisma_client_or_raise_exception()
|
||||
|
||||
# Only flip keys whose current state differs — avoids touching keys that
|
||||
# were already (un)blocked manually by an admin.
|
||||
# were already (un)blocked manually by an admin. `blocked` is a nullable
|
||||
# column with no default, so existing keys typically have `blocked=NULL`;
|
||||
# we must treat NULL as "not blocked" so SQL equality on NULL doesn't
|
||||
# silently skip them.
|
||||
if blocked:
|
||||
state_filter: Dict[str, Any] = {"OR": [{"blocked": False}, {"blocked": None}]}
|
||||
else:
|
||||
state_filter = {"blocked": True}
|
||||
|
||||
where_clause: Dict[str, Any] = {"user_id": user_id, **state_filter}
|
||||
|
||||
affected_keys = await prisma_client.db.litellm_verificationtoken.find_many(
|
||||
where={"user_id": user_id, "blocked": not blocked},
|
||||
where=where_clause,
|
||||
)
|
||||
if not affected_keys:
|
||||
return 0
|
||||
|
||||
await prisma_client.db.litellm_verificationtoken.update_many(
|
||||
where={"user_id": user_id, "blocked": not blocked},
|
||||
where=where_clause,
|
||||
data={"blocked": blocked},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,10 @@ async def test_set_user_keys_blocked_flips_state_and_invalidates_cache():
|
|||
|
||||
assert flipped == 2
|
||||
mock_db.litellm_verificationtoken.update_many.assert_awaited_once_with(
|
||||
where={"user_id": "user-x", "blocked": False},
|
||||
where={
|
||||
"user_id": "user-x",
|
||||
"OR": [{"blocked": False}, {"blocked": None}],
|
||||
},
|
||||
data={"blocked": True},
|
||||
)
|
||||
assert sorted(cache_deletions) == ["hash-1", "hash-2"]
|
||||
|
|
@ -129,7 +132,10 @@ async def test_scim_delete_user_blocks_keys_before_deleting_user():
|
|||
|
||||
assert response.status_code == 204
|
||||
mock_db.litellm_verificationtoken.update_many.assert_awaited_once_with(
|
||||
where={"user_id": user_id, "blocked": False},
|
||||
where={
|
||||
"user_id": user_id,
|
||||
"OR": [{"blocked": False}, {"blocked": None}],
|
||||
},
|
||||
data={"blocked": True},
|
||||
)
|
||||
mock_db.litellm_usertable.delete.assert_awaited_once_with(
|
||||
|
|
@ -187,7 +193,10 @@ async def test_scim_patch_user_active_false_blocks_keys():
|
|||
await patch_user(user_id=user_id, patch_ops=patch_ops)
|
||||
|
||||
mock_db.litellm_verificationtoken.update_many.assert_awaited_once_with(
|
||||
where={"user_id": user_id, "blocked": False},
|
||||
where={
|
||||
"user_id": user_id,
|
||||
"OR": [{"blocked": False}, {"blocked": None}],
|
||||
},
|
||||
data={"blocked": True},
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue