mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
fix(proxy): resolve all-team-models for project model access checks
Projects with models=["all-team-models"] were failing with 401 because _check_model_access_helper only recognized "all-proxy-models" as a special value. The literal string "all-team-models" was compared against the requested model name, which always failed. - Update can_project_access_model to accept team_models param and resolve "all-team-models" to the team's actual model list before passing to the access check helper - Pass team_models from valid_token in _run_project_checks - Fix circular no-op in get_team_models where all-team-models sentinel caused a self-update; now strips it instead
This commit is contained in:
parent
e40f68aec4
commit
86f9b5d59c
4 changed files with 131 additions and 2 deletions
|
|
@ -208,6 +208,7 @@ async def _run_project_checks(
|
|||
model=_model,
|
||||
project_object=project_object,
|
||||
llm_router=llm_router,
|
||||
team_models=valid_token.team_models if valid_token else None,
|
||||
)
|
||||
|
||||
if not skip_budget_checks:
|
||||
|
|
@ -2802,16 +2803,24 @@ def can_project_access_model(
|
|||
model: Union[str, List[str]],
|
||||
project_object: LiteLLM_ProjectTableCachedObj,
|
||||
llm_router: Optional[Router],
|
||||
team_models: Optional[List[str]] = None,
|
||||
) -> Literal[True]:
|
||||
"""
|
||||
Returns True if the project can access a specific model.
|
||||
|
||||
Raises ProxyException if access is denied.
|
||||
"""
|
||||
project_models = list(project_object.models) if project_object else []
|
||||
|
||||
# Resolve "all-team-models" sentinel to the team's actual model list,
|
||||
# matching the pattern used in get_key_models() for key-level resolution.
|
||||
if SpecialModelNames.all_team_models.value in project_models:
|
||||
project_models = list(team_models) if team_models else []
|
||||
|
||||
return _can_object_call_model(
|
||||
model=model,
|
||||
llm_router=llm_router,
|
||||
models=project_object.models if project_object else [],
|
||||
models=project_models,
|
||||
object_type="project",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -148,7 +148,10 @@ def get_team_models(
|
|||
if len(team_models) > 0:
|
||||
all_models_set.update(team_models)
|
||||
if SpecialModelNames.all_team_models.value in all_models_set:
|
||||
all_models_set.update(team_models)
|
||||
# "all-team-models" is a key/project-level sentinel meaning
|
||||
# "inherit the team's models". It has no meaning on the team
|
||||
# itself, so just strip it out.
|
||||
all_models_set.discard(SpecialModelNames.all_team_models.value)
|
||||
if SpecialModelNames.all_proxy_models.value in all_models_set:
|
||||
all_models_set.update(proxy_model_list)
|
||||
if include_model_access_groups:
|
||||
|
|
|
|||
|
|
@ -1629,3 +1629,100 @@ async def test_custom_auth_common_checks_opt_in():
|
|||
parent_otel_span=None,
|
||||
)
|
||||
mock_common.assert_called_once()
|
||||
|
||||
|
||||
def test_can_project_access_model_all_team_models_resolves():
|
||||
"""
|
||||
Regression test: project with models=["all-team-models"] should resolve
|
||||
to the team's actual models, not compare the literal string.
|
||||
"""
|
||||
from litellm.proxy._types import LiteLLM_ProjectTableCachedObj
|
||||
from litellm.proxy.auth.auth_checks import can_project_access_model
|
||||
|
||||
project = LiteLLM_ProjectTableCachedObj(
|
||||
project_id="test-project",
|
||||
models=["all-team-models"],
|
||||
created_by="test",
|
||||
updated_by="test",
|
||||
)
|
||||
|
||||
# Team has gpt-5 → project should be able to access it
|
||||
result = can_project_access_model(
|
||||
model="gpt-5",
|
||||
project_object=project,
|
||||
llm_router=None,
|
||||
team_models=["gpt-5", "gpt-4"],
|
||||
)
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_can_project_access_model_all_team_models_denies_unlisted():
|
||||
"""
|
||||
Project with models=["all-team-models"] should deny a model that
|
||||
the team doesn't have.
|
||||
"""
|
||||
from litellm.proxy._types import LiteLLM_ProjectTableCachedObj
|
||||
from litellm.proxy.auth.auth_checks import can_project_access_model
|
||||
|
||||
project = LiteLLM_ProjectTableCachedObj(
|
||||
project_id="test-project",
|
||||
models=["all-team-models"],
|
||||
created_by="test",
|
||||
updated_by="test",
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
can_project_access_model(
|
||||
model="gpt-5",
|
||||
project_object=project,
|
||||
llm_router=None,
|
||||
team_models=["gpt-4"],
|
||||
)
|
||||
|
||||
|
||||
def test_can_project_access_model_all_team_models_with_all_proxy_models():
|
||||
"""
|
||||
Project has all-team-models, team has all-proxy-models.
|
||||
Should grant access to any model.
|
||||
"""
|
||||
from litellm.proxy._types import LiteLLM_ProjectTableCachedObj
|
||||
from litellm.proxy.auth.auth_checks import can_project_access_model
|
||||
|
||||
project = LiteLLM_ProjectTableCachedObj(
|
||||
project_id="test-project",
|
||||
models=["all-team-models"],
|
||||
created_by="test",
|
||||
updated_by="test",
|
||||
)
|
||||
|
||||
result = can_project_access_model(
|
||||
model="gpt-5",
|
||||
project_object=project,
|
||||
llm_router=None,
|
||||
team_models=["all-proxy-models"],
|
||||
)
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_can_project_access_model_all_team_models_no_team():
|
||||
"""
|
||||
Project has all-team-models but team_models is None/empty.
|
||||
Empty resolved list → all access (matches existing behavior for empty models).
|
||||
"""
|
||||
from litellm.proxy._types import LiteLLM_ProjectTableCachedObj
|
||||
from litellm.proxy.auth.auth_checks import can_project_access_model
|
||||
|
||||
project = LiteLLM_ProjectTableCachedObj(
|
||||
project_id="test-project",
|
||||
models=["all-team-models"],
|
||||
created_by="test",
|
||||
updated_by="test",
|
||||
)
|
||||
|
||||
result = can_project_access_model(
|
||||
model="gpt-5",
|
||||
project_object=project,
|
||||
llm_router=None,
|
||||
team_models=None,
|
||||
)
|
||||
assert result is True
|
||||
|
|
|
|||
|
|
@ -220,3 +220,23 @@ def test_get_complete_model_list_byok_wildcard_expansion():
|
|||
assert len(result) > 0
|
||||
assert all(m.startswith("openai/") for m in result)
|
||||
assert "openai/*" not in result
|
||||
|
||||
|
||||
def test_get_team_models_strips_all_team_models_sentinel():
|
||||
"""
|
||||
Regression test: 'all-team-models' is a key/project-level sentinel and
|
||||
should be stripped from team model lists rather than causing a circular
|
||||
self-update.
|
||||
"""
|
||||
from litellm.proxy.auth.model_checks import get_team_models
|
||||
|
||||
result = get_team_models(
|
||||
team_models=["all-team-models", "gpt-4"],
|
||||
proxy_model_list=["model1", "model2"],
|
||||
model_access_groups={},
|
||||
include_model_access_groups=False,
|
||||
)
|
||||
assert "all-team-models" not in result
|
||||
assert "gpt-4" in result
|
||||
# Should NOT include proxy models since all-proxy-models wasn't set
|
||||
assert "model1" not in result
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue