mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(scim): apply default_team_params (incl. models) to SCIM-created teams (#38433)
* fix(scim): apply default_team_params (incl. models) to SCIM-created teams Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(scim): annotate default_team_params regression test parameters Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: yassin <yassin@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
172e3aceaf
commit
2e2c8200ae
2 changed files with 96 additions and 2 deletions
|
|
@ -6,6 +6,7 @@ This is an enterprise feature and requires a premium license.
|
|||
|
||||
import re
|
||||
from collections.abc import Awaitable, Callable, Iterable, Mapping, Sequence
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
|
|
@ -2375,6 +2376,37 @@ async def get_group(
|
|||
raise handle_exception_on_proxy(e)
|
||||
|
||||
|
||||
def _new_team_request_with_defaults(
|
||||
team_id: str,
|
||||
team_alias: str | None,
|
||||
members_with_roles: Sequence[Member],
|
||||
) -> NewTeamRequest:
|
||||
"""Build the SCIM group's team request, applying litellm.default_team_params
|
||||
(including models) the same way SSO auto-created teams do."""
|
||||
default_params: Final = litellm.default_team_params
|
||||
defaults: Final[Mapping[str, object]] = (
|
||||
deepcopy(default_params)
|
||||
if isinstance(default_params, dict)
|
||||
else default_params.model_dump(exclude_none=True)
|
||||
if default_params is not None
|
||||
else {}
|
||||
)
|
||||
default_metadata: Final = defaults.get("metadata")
|
||||
metadata: Final = {
|
||||
**(default_metadata if isinstance(default_metadata, dict) else {}),
|
||||
SCIM_MANAGED_TEAM_METADATA_KEY: True,
|
||||
}
|
||||
return NewTeamRequest.model_validate(
|
||||
{
|
||||
**defaults,
|
||||
"team_id": team_id,
|
||||
"team_alias": team_alias,
|
||||
"members_with_roles": members_with_roles,
|
||||
"metadata": metadata,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@scim_router.post(
|
||||
"/Groups",
|
||||
response_model=SCIMGroup,
|
||||
|
|
@ -2412,11 +2444,10 @@ async def create_group(
|
|||
|
||||
# Create team in database
|
||||
created_team: Final = await new_team(
|
||||
data=NewTeamRequest(
|
||||
data=_new_team_request_with_defaults(
|
||||
team_id=team_id,
|
||||
team_alias=group.displayName,
|
||||
members_with_roles=members_with_roles,
|
||||
metadata={SCIM_MANAGED_TEAM_METADATA_KEY: True},
|
||||
),
|
||||
http_request=Request(scope={"type": "http", "path": "/scim/v2/Groups"}),
|
||||
user_api_key_dict=UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN),
|
||||
|
|
|
|||
|
|
@ -4415,6 +4415,69 @@ async def test_create_group_stamps_scim_provenance(mocker, scim_upsert_user_enab
|
|||
assert new_team_mock.call_args.kwargs["data"].metadata == {SCIM_MANAGED_TEAM_METADATA_KEY: True}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("as_pydantic", [False, True])
|
||||
async def test_create_group_applies_default_team_params(
|
||||
mocker: MockerFixture,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
scim_upsert_user_enabled: None,
|
||||
as_pydantic: bool,
|
||||
):
|
||||
"""SCIM-created teams must honor litellm_settings.default_team_params, including
|
||||
models, the same way SSO auto-created teams do."""
|
||||
import litellm
|
||||
from litellm.types.proxy.management_endpoints.ui_sso import DefaultTeamSSOParams
|
||||
|
||||
default_params = {
|
||||
"models": ["no-default-models"],
|
||||
"max_budget": 25.0,
|
||||
"budget_duration": "30d",
|
||||
"tpm_limit": 100,
|
||||
"rpm_limit": 10,
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
litellm,
|
||||
"default_team_params",
|
||||
DefaultTeamSSOParams(**default_params) if as_pydantic else default_params,
|
||||
)
|
||||
|
||||
scim_group = SCIMGroup(
|
||||
schemas=["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||
id="defaults-group",
|
||||
displayName="Defaults.Apps",
|
||||
members=[],
|
||||
)
|
||||
|
||||
mocker.patch( # test-quality-ok: endpoint collaborators are module-level, not injectable into create_group
|
||||
"litellm.proxy.management_endpoints.scim.scim_v2._get_prisma_client_or_raise_exception",
|
||||
AsyncMock(return_value=_member_resolution_prisma(mocker, users=set(), teams=set())),
|
||||
)
|
||||
new_team_mock = mocker.patch( # test-quality-ok: endpoint collaborators are module-level, not injectable into create_group
|
||||
"litellm.proxy.management_endpoints.scim.scim_v2.new_team",
|
||||
AsyncMock(return_value=mocker.MagicMock()),
|
||||
)
|
||||
mocker.patch( # test-quality-ok: endpoint collaborators are module-level, not injectable into create_group
|
||||
"litellm.proxy.management_endpoints.scim.scim_v2.ScimTransformations.transform_litellm_team_to_scim_group",
|
||||
AsyncMock(return_value=scim_group),
|
||||
)
|
||||
mocker.patch( # test-quality-ok: endpoint collaborators are module-level, not injectable into create_group
|
||||
"litellm.proxy.management_endpoints.scim.scim_v2._recompute_scim_member_roles",
|
||||
AsyncMock(),
|
||||
)
|
||||
|
||||
await create_group(group=scim_group)
|
||||
|
||||
team_request = new_team_mock.call_args.kwargs["data"]
|
||||
assert team_request.models == ["no-default-models"]
|
||||
assert team_request.max_budget == 25.0
|
||||
assert team_request.budget_duration == "30d"
|
||||
assert team_request.tpm_limit == 100
|
||||
assert team_request.rpm_limit == 10
|
||||
assert team_request.team_id == "defaults-group"
|
||||
assert team_request.team_alias == "Defaults.Apps"
|
||||
assert team_request.metadata == {SCIM_MANAGED_TEAM_METADATA_KEY: True}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_group_stamps_scim_provenance(mocker, scim_upsert_user_enabled):
|
||||
"""A PUT full sync adopts a team the identity provider now owns, and the stamp has
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue