mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
Merge pull request #32587 from BerriAI/litellm_fix_batch_model_access_hash_32580
fix(auth): resolve managed batch/file deployment model_id to model name for team access checks
This commit is contained in:
commit
52fc276f05
4 changed files with 125 additions and 3 deletions
|
|
@ -1432,7 +1432,7 @@ def _extract_models_from_managed_resource_id(
|
|||
)
|
||||
_append_model_candidates(
|
||||
candidates=candidates,
|
||||
value=get_model_id_from_unified_batch_id(unified_file_id),
|
||||
value=_resolve_model_id_with_router(get_model_id_from_unified_batch_id(unified_file_id), llm_router),
|
||||
)
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.debug("Unable to extract model from managed file/batch ID: %s", str(e))
|
||||
|
|
@ -1442,7 +1442,10 @@ def _extract_models_from_managed_resource_id(
|
|||
|
||||
parsed_id = parse_unified_id(resource_id)
|
||||
if parsed_id:
|
||||
_append_model_candidates(candidates=candidates, value=parsed_id.get("model_id"))
|
||||
_append_model_candidates(
|
||||
candidates=candidates,
|
||||
value=_resolve_model_id_with_router(parsed_id.get("model_id"), llm_router),
|
||||
)
|
||||
_append_model_candidates(candidates=candidates, value=parsed_id.get("target_model_names"))
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.debug("Unable to extract model from unified managed resource ID: %s", str(e))
|
||||
|
|
|
|||
|
|
@ -9560,7 +9560,12 @@ class Router:
|
|||
return None
|
||||
|
||||
# Strategy 1: Check if model_id directly matches a model_name or deployment ID
|
||||
if model_id in self.model_names or self.has_model_id(model_id):
|
||||
if model_id in self.model_names:
|
||||
return model_id
|
||||
if self.has_model_id(model_id):
|
||||
deployment = self.get_deployment(model_id=model_id)
|
||||
if deployment is not None and deployment.model_name:
|
||||
return deployment.model_name
|
||||
return model_id
|
||||
|
||||
# Strategy 2: Search through router's model_list to find by litellm_params.model
|
||||
|
|
|
|||
|
|
@ -2659,6 +2659,23 @@ def test_resolve_model_name_from_model_id():
|
|||
result = router.resolve_model_name_from_model_id("gpt-5-mini")
|
||||
assert result == "gpt-5-mini"
|
||||
|
||||
# Test case 10: model_id is a deployment ID (hash) that differs from the
|
||||
# public model_name. Regression for #32580: managed batch/file IDs embed the
|
||||
# deployment model_id, and it must resolve back to the public model_name so
|
||||
# team model-access checks compare against the model group, not the hash.
|
||||
model_list = [
|
||||
{
|
||||
"model_name": "bedrock-batch-model",
|
||||
"litellm_params": {
|
||||
"model": "bedrock/global.anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
},
|
||||
"model_info": {"id": "8d0eaa7e6c6f54a425dfd0062cb6b0dc"},
|
||||
},
|
||||
]
|
||||
router = Router(model_list=model_list)
|
||||
result = router.resolve_model_name_from_model_id("8d0eaa7e6c6f54a425dfd0062cb6b0dc")
|
||||
assert result == "bedrock-batch-model"
|
||||
|
||||
|
||||
def test_get_valid_args():
|
||||
"""Test get_valid_args static method returns valid Router.__init__ arguments"""
|
||||
|
|
|
|||
|
|
@ -569,6 +569,103 @@ def test_get_model_from_request_resolves_video_id_model_with_router():
|
|||
)
|
||||
|
||||
|
||||
_BATCH_DEPLOYMENT_ID = "8d0eaa7e6c6f54a425dfd0062cb6b0dc"
|
||||
|
||||
|
||||
def _managed_batch_router():
|
||||
from litellm.router import Router
|
||||
|
||||
return Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "bedrock-batch-model",
|
||||
"litellm_params": {
|
||||
"model": "bedrock/global.anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
},
|
||||
"model_info": {"id": _BATCH_DEPLOYMENT_ID},
|
||||
},
|
||||
{
|
||||
"model_name": "some-other-model",
|
||||
"litellm_params": {"model": "openai/gpt-4o-mini", "api_key": "test-key"},
|
||||
"model_info": {"id": "a-different-deployment-id"},
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _encode_managed_id(decoded: str) -> str:
|
||||
return base64.urlsafe_b64encode(decoded.encode()).decode().rstrip("=")
|
||||
|
||||
|
||||
_MANAGED_BATCH_ID = _encode_managed_id(
|
||||
f"litellm_proxy;model_id:{_BATCH_DEPLOYMENT_ID};llm_batch_id:provider-batch-123"
|
||||
)
|
||||
_MANAGED_BATCH_OUTPUT_FILE_ID = _encode_managed_id(
|
||||
f"litellm_proxy;model_id:{_BATCH_DEPLOYMENT_ID};llm_batch_id:provider-batch-123;"
|
||||
"llm_output_file_id:provider-file-456"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"route, request_data",
|
||||
[
|
||||
("/v1/batches/{batch_id}", {"batch_id": _MANAGED_BATCH_ID}),
|
||||
("/v1/batches/{batch_id}/cancel", {"batch_id": _MANAGED_BATCH_ID}),
|
||||
("/v1/files/{file_id}", {"file_id": _MANAGED_BATCH_OUTPUT_FILE_ID}),
|
||||
("/v1/files/{file_id}/content", {"file_id": _MANAGED_BATCH_OUTPUT_FILE_ID}),
|
||||
],
|
||||
)
|
||||
def test_get_model_from_request_resolves_batch_id_deployment_to_model_name(route, request_data):
|
||||
"""Regression for #32580: managed batch retrieve/cancel and managed batch output
|
||||
file reads encode the deployment model_id into the resource id. The auth layer must
|
||||
resolve that id back to the public model group name so model-access checks compare
|
||||
against the model group, not the raw deployment id."""
|
||||
assert (
|
||||
get_model_from_request(
|
||||
request_data=request_data,
|
||||
route=route,
|
||||
llm_router=_managed_batch_router(),
|
||||
)
|
||||
== "bedrock-batch-model"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"route, request_data",
|
||||
[
|
||||
("/v1/batches/{batch_id}", {"batch_id": _MANAGED_BATCH_ID}),
|
||||
("/v1/batches/{batch_id}/cancel", {"batch_id": _MANAGED_BATCH_ID}),
|
||||
("/v1/files/{file_id}/content", {"file_id": _MANAGED_BATCH_OUTPUT_FILE_ID}),
|
||||
],
|
||||
)
|
||||
async def test_managed_batch_routes_pass_team_model_access_check(route, request_data):
|
||||
"""End-to-end regression for #32580: a team scoped to the batch model group got
|
||||
``team_model_access_denied`` on retrieve/cancel because the deployment id, not the
|
||||
model group, was authorized. Fails pre-fix with the deployment id in the message."""
|
||||
from litellm.proxy._types import LiteLLM_TeamTable
|
||||
from litellm.proxy.auth.auth_checks import can_team_access_model
|
||||
|
||||
llm_router = _managed_batch_router()
|
||||
model = get_model_from_request(request_data=request_data, route=route, llm_router=llm_router)
|
||||
|
||||
assert (
|
||||
await can_team_access_model(
|
||||
model=model,
|
||||
team_object=LiteLLM_TeamTable(team_id="team-batch", models=["bedrock-batch-model"]),
|
||||
llm_router=llm_router,
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
with pytest.raises(Exception, match="team not allowed to access model"):
|
||||
await can_team_access_model(
|
||||
model=model,
|
||||
team_object=LiteLLM_TeamTable(team_id="team-other", models=["some-other-model"]),
|
||||
llm_router=llm_router,
|
||||
)
|
||||
|
||||
|
||||
def test_get_model_from_request_resolves_character_id_model_with_router():
|
||||
from litellm.types.videos.utils import encode_character_id_with_provider
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue