mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
fix(team): hydrate object_permission on cache-refreshing team updates
Some checks failed
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
Some checks failed
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
The Prisma update calls in update_team, team_model_add, and
team_model_delete returned a team row with object_permission_id set
but object_permission=None (the relation was not requested via
include=). _refresh_cached_team then wrote that to the in-memory
LiteLLM_TeamTableCachedObj, and the cache-hit path in get_team_object
returns the cached object without re-hydrating. Downstream consumers
(validate_key_search_tools_against_team, the MCP/agent authz paths)
treat a missing object_permission as no team-level restriction, so
a team-write op silently dropped object-permission enforcement until
the cache TTL expired or a DB-fetch path re-hydrated it.
Add include={"object_permission": True} to all three updates so the
refresh writes a complete cached team. Extend the LIT-3244 regression
test to pin both the cached object_permission and the include shape
on the Prisma call.
Surfaced in PR review of LIT-3244.
This commit is contained in:
parent
e5d63af402
commit
5235471e3b
2 changed files with 50 additions and 5 deletions
|
|
@ -1879,7 +1879,13 @@ async def update_team( # noqa: PLR0915
|
|||
await prisma_client.db.litellm_teamtable.update(
|
||||
where={"team_id": data.team_id},
|
||||
data=updated_kv,
|
||||
include={"litellm_model_table": True}, # type: ignore
|
||||
# `object_permission` is included so `_refresh_cached_team`
|
||||
# doesn't write a cached team with the relation nulled out —
|
||||
# see team_model_add for the full rationale.
|
||||
include={
|
||||
"litellm_model_table": True,
|
||||
"object_permission": True,
|
||||
}, # type: ignore
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -4624,9 +4630,15 @@ async def team_model_add(
|
|||
)
|
||||
|
||||
updated_models = add_new_models_to_team(team_obj=team_obj, new_models=data.models)
|
||||
# Update team
|
||||
# Update team. `include` mirrors the relations the auth path consumes
|
||||
# off the cached team object so that `_refresh_cached_team` doesn't
|
||||
# null them out — see object_permission_utils.validate_key_search_tools_against_team
|
||||
# and the MCP/agent authz paths, which treat a missing object_permission
|
||||
# as "no team-level restriction".
|
||||
updated_team = await prisma_client.db.litellm_teamtable.update(
|
||||
where={"team_id": data.team_id}, data={"models": updated_models}
|
||||
where={"team_id": data.team_id},
|
||||
data={"models": updated_models},
|
||||
include={"object_permission": True}, # type: ignore
|
||||
)
|
||||
|
||||
await _refresh_cached_team(
|
||||
|
|
@ -4710,9 +4722,11 @@ async def team_model_delete(
|
|||
# Remove specified models
|
||||
updated_models = [m for m in current_models if m not in data.models]
|
||||
|
||||
# Update team
|
||||
# Update team. See team_model_add for the rationale on `include`.
|
||||
updated_team = await prisma_client.db.litellm_teamtable.update(
|
||||
where={"team_id": data.team_id}, data={"models": updated_models}
|
||||
where={"team_id": data.team_id},
|
||||
data={"models": updated_models},
|
||||
include={"object_permission": True}, # type: ignore
|
||||
)
|
||||
|
||||
await _refresh_cached_team(
|
||||
|
|
|
|||
|
|
@ -1582,6 +1582,11 @@ async def test_team_model_add_delete_refresh_team_cache(endpoint_name):
|
|||
existing_team.model_dump.return_value = {
|
||||
"team_id": "team-1234",
|
||||
"models": ["bedrock-claude-sonnet-4", "openai/*"],
|
||||
"object_permission_id": "op-1234",
|
||||
"object_permission": {
|
||||
"object_permission_id": "op-1234",
|
||||
"search_tools": ["allowed-tool-A"],
|
||||
},
|
||||
}
|
||||
|
||||
updated_team = MagicMock()
|
||||
|
|
@ -1589,6 +1594,14 @@ async def test_team_model_add_delete_refresh_team_cache(endpoint_name):
|
|||
updated_team.model_dump.return_value = {
|
||||
"team_id": "team-1234",
|
||||
"models": ["bedrock-claude-sonnet-4", "openai/*", "team-byok-1"],
|
||||
# The Prisma update must come back with `object_permission` populated
|
||||
# (via `include={"object_permission": True}`), otherwise the cache
|
||||
# write below would null it out — see LIT-3244 follow-up.
|
||||
"object_permission_id": "op-1234",
|
||||
"object_permission": {
|
||||
"object_permission_id": "op-1234",
|
||||
"search_tools": ["allowed-tool-A"],
|
||||
},
|
||||
}
|
||||
|
||||
with (
|
||||
|
|
@ -1638,6 +1651,24 @@ async def test_team_model_add_delete_refresh_team_cache(endpoint_name):
|
|||
"openai/*",
|
||||
"team-byok-1",
|
||||
]
|
||||
# And the cached object MUST carry the `object_permission` relation
|
||||
# (LIT-3244 follow-up). If the Prisma update were missing
|
||||
# `include={"object_permission": True}`, the cached team would have
|
||||
# object_permission=None, and downstream consumers like
|
||||
# `validate_key_search_tools_against_team` would treat that as
|
||||
# "no team-level restriction" and stop enforcing the team's
|
||||
# search-tool allowlist on key issuance.
|
||||
assert call_kwargs["team_table"].object_permission is not None
|
||||
assert call_kwargs["team_table"].object_permission.search_tools == [
|
||||
"allowed-tool-A"
|
||||
]
|
||||
# Pin the Prisma call shape too — the regression is in *what the
|
||||
# update returns*, so the contract that the update asks for
|
||||
# `object_permission` belongs in this test.
|
||||
update_call_kwargs = (
|
||||
mock_prisma_client.db.litellm_teamtable.update.call_args.kwargs
|
||||
)
|
||||
assert update_call_kwargs.get("include", {}).get("object_permission") is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue