This commit is contained in:
Tufail Akram 2026-10-17 08:01:50 -07:00 • committed by GitHub
commit 76cf04bcf4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 65 additions and 71 deletions

View file

@ -1234,6 +1234,17 @@ async def patch_model(
param=None,
)
# Pause/resume (`blocked`) is proxy-admin only. A non-admin team member
# otherwise goes through member auto-router authorization, which refuses
# a blocked-only patch as an auto-router write. Check the flag first.
if patch_data.blocked is not None and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN:
raise ProxyException(
message="Only proxy admins can change a model's blocked flag.",
type=ProxyErrorTypes.auth_error.value,
code=status.HTTP_403_FORBIDDEN,
param="blocked",
)
write_authorization: Final = await ModelManagementAuthChecks.can_user_make_model_call(
model_params=db_model,
user_api_key_dict=user_api_key_dict,
@ -1270,17 +1281,6 @@ async def patch_model(
else patch_data
)
# Pause/resume (`blocked`) is a proxy-admin-only privilege. Team admins
# passed the auth check above for team-scoped models, but they must not
# be able to unblock (or block) a model their proxy admin has paused.
if patch_data.blocked is not None and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN:
raise ProxyException(
message="Only proxy admins can change a model's blocked flag.",
type=ProxyErrorTypes.auth_error.value,
code=status.HTTP_403_FORBIDDEN,
param="blocked",
)
ModelManagementAuthChecks.can_user_attach_credential(
litellm_params=patch_data.litellm_params,
user_api_key_dict=user_api_key_dict,

View file

@ -4949,6 +4949,60 @@ class TestPatchModelBlockedAuthGate:
assert getattr(err, "param", "") == "blocked"
assert "proxy admin" in getattr(err, "message", "").lower()
@pytest.mark.asyncio
@pytest.mark.parametrize("role", [LitellmUserRoles.INTERNAL_USER, LitellmUserRoles.ORG_ADMIN])
async def test_non_admin_blocked_only_patch_is_not_an_auto_router_refusal(self, role: LitellmUserRoles):
"""A member who sends only `blocked` must get the blocked-flag refusal.
Member auto-router authorization runs for non-admins and would answer
"This team does not allow you to manage your own auto routers." The
blocked check has to win before that path is entered.
"""
from litellm.proxy.management_endpoints.model_management_endpoints import (
patch_model,
)
member = UserAPIKeyAuth(user_id="member", user_role=role)
existing_row = MagicMock()
existing_row.litellm_params = {"model": "openai/gpt-4o-mini"}
existing_row.model_dump.return_value = {
"model_name": "gpt-4o-mini",
"litellm_params": existing_row.litellm_params,
"model_info": {"id": "m1", "team_id": "team-1"},
}
existing_row.model_dump_json.return_value = "{}"
team = LiteLLM_TeamTable(
team_id="team-1",
members_with_roles=[Member(user_id="member", role="user")],
team_member_permissions=[],
)
team_row = MagicMock()
team_row.model_dump.return_value = team.model_dump()
mock_prisma = MagicMock()
mock_prisma.db.litellm_proxymodeltable.find_unique = AsyncMock(return_value=existing_row)
mock_prisma.db.litellm_proxymodeltable.update = AsyncMock()
mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=team_row)
with (
patch("litellm.proxy.proxy_server.prisma_client", mock_prisma),
patch("litellm.proxy.proxy_server.llm_router", MagicMock(**{"get_model_ids.return_value": ["m1"]})),
patch("litellm.proxy.proxy_server.store_model_in_db", True),
patch("litellm.proxy.proxy_server.premium_user", True),
):
with pytest.raises(
ProxyException, match="Only proxy admins can change a model's blocked flag\\."
) as exc_info:
await patch_model(
model_id="m1",
patch_data=updateDeployment(blocked=True),
user_api_key_dict=member,
)
assert exc_info.value.code == "403"
assert exc_info.value.param == "blocked"
mock_prisma.db.litellm_proxymodeltable.update.assert_not_awaited()
@pytest.mark.asyncio
async def test_proxy_admin_can_toggle_blocked(self):
from litellm.proxy.management_endpoints.model_management_endpoints import (

View file

@ -1598,9 +1598,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1617,9 +1614,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1636,9 +1630,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1655,9 +1646,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1674,9 +1662,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1693,9 +1678,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1712,9 +1694,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1731,9 +1710,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -1750,9 +1726,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1775,9 +1748,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1800,9 +1770,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1825,9 +1792,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1850,9 +1814,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1875,9 +1836,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1900,9 +1858,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -1925,9 +1880,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@ -2208,9 +2160,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@ -2227,9 +2176,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@ -2246,9 +2192,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@ -2265,9 +2208,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [