diff --git a/docs/my-website/docs/tutorials/msft_sso.md b/docs/my-website/docs/tutorials/msft_sso.md
new file mode 100644
index 00000000000..170e7f9c3ae
--- /dev/null
+++ b/docs/my-website/docs/tutorials/msft_sso.md
@@ -0,0 +1,142 @@
+import Image from '@theme/IdealImage';
+
+# Microsoft SSO: Sync Groups, Members with LiteLLM
+
+Sync Microsoft SSO Groups, Members with LiteLLM Teams.
+
+
+
+
+
+
+## Overview of this tutorial
+
+1. Auto-Create Entra ID Groups on LiteLLM Teams
+2. Sync Entra ID Team Memberships
+3. Set default params for new teams and users auto-created on LiteLLM
+
+## 1. Auto-Create Entra ID Groups on LiteLLM Teams
+
+In this step, our goal is to have LiteLLM automatically create a new team on the LiteLLM DB when there is a new Group Added to the LiteLLM Enterprise App on Azure Entra ID.
+
+### 1.1 Create a new group in Entra ID
+
+
+Navigate to [your Azure Portal](https://portal.azure.com/) > Groups > New Group. Create a new group.
+
+
+
+### 1.2 Assign the group to your LiteLLM Enterprise App
+
+On your Azure Portal, navigate to `Enterprise Applications` > Select your litellm app
+
+
+
+
+
+
+Once you've selected your litellm app, click on `Users and Groups` > `Add user/group`
+
+
+
+
+
+Now select the group you created in step 1.1. And add it to the LiteLLM Enterprise App. At this point we have added `Production LLM Evals Group` to the LiteLLM Enterprise App. The next steps is having LiteLLM automatically create the `Production LLM Evals Group` on the LiteLLM DB when a new user signs in.
+
+
+
+
+### 1.3 Sign in to LiteLLM UI via SSO
+
+Sign into the LiteLLM UI via SSO. You should be redirected to the Entra ID SSO page. This SSO sign in flow will trigger LiteLLM to fetch the latest Groups and Members from Azure Entra ID.
+
+
+
+### 1.4 Check the new team on LiteLLM UI
+
+On the LiteLLM UI, Navigate to `Teams`, You should see the new team `Production LLM Evals Group` auto-created on LiteLLM.
+
+
+
+#### How this works
+
+When a SSO user signs in to LiteLLM, LiteLLM automatically fetches the Groups under the LiteLLM Enterprise App, and found that the `Production LLM Evals Group` was assigned to the LiteLLM Enterprise App. Since the id associated with `Production LLM Evals Group` did not exist in the LiteLLM Teams Table, LiteLLM automatically created a new team with the name `Production LLM Evals Group` and id == id of `Production LLM Evals Group` in Entra ID.
+
+
+## 2. Sync Entra ID Team Memberships
+
+In this step, we will have LiteLLM automatically add a user to the `Production LLM Evals` Team on the LiteLLM DB when a new user is added to the `Production LLM Evals` Group in Entra ID.
+
+### 2.1 Navigate to the `Production LLM Evals` Group in Entra ID
+
+Navigate to the `Production LLM Evals` Group in Entra ID.
+
+
+
+
+### 2.2 Add a member to the group in Entra ID
+
+Select `Members` > `Add members`
+
+In this stage you should add the user you want to add to the `Production LLM Evals` Team.
+
+
+
+
+
+### 2.3 Sign in as the new user on LiteLLM UI
+
+Sign in as the new user on LiteLLM UI. You should be redirected to the Entra ID SSO page. This SSO sign in flow will trigger LiteLLM to fetch the latest Groups and Members from Azure Entra ID. During this step LiteLLM sync it's teams, team members with what is available from Entra ID
+
+
+
+
+
+### 2.4 Check the team membership on LiteLLM UI
+
+On the LiteLLM UI, Navigate to `Teams`, You should see the new team `Production LLM Evals Group`. Since your are now a member of the `Production LLM Evals Group` in Entra ID, you should see the new team `Production LLM Evals Group` on the LiteLLM UI.
+
+
+
+## 3. Set default params for new teams auto-created on LiteLLM
+
+Since litellm auto creates a new team on the LiteLLM DB when there is a new Group Added to the LiteLLM Enterprise App on Azure Entra ID, we can set default params for new teams created.
+
+This allows you to set a default budget, models, etc for new teams created.
+
+### 3.1 Set `default_team_params` on litellm
+
+Navigate to your litellm config file and set the following params
+
+```yaml showLineNumbers title="litellm config with default_team_params"
+litellm_settings:
+ default_team_params: # Default Params to apply when litellm auto creates a team from SSO IDP provider
+ max_budget: 100 # Optional[float], optional): $100 budget for the team
+ budget_duration: 30d # Optional[str], optional): 30 days budget_duration for the team
+ models: ["gpt-3.5-turbo"] # Optional[List[str]], optional): models to be used by the team
+```
+
+### 3.2 Auto-create a new team on LiteLLM
+
+- In this step you should add a new group to the LiteLLM Enterprise App on Azure Entra ID (like we did in step 1.1). We will call this group `Default LiteLLM Prod Team` on Azure Entra ID.
+- Start litellm proxy server with your config
+- Sign into LiteLLM UI via SSO
+- Navigate to `Teams` and you should see the new team `Default LiteLLM Prod Team` auto-created on LiteLLM
+- Note LiteLLM will set the default params for this new team.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/my-website/img/entra_create_team.png b/docs/my-website/img/entra_create_team.png
new file mode 100644
index 00000000000..223a897d879
Binary files /dev/null and b/docs/my-website/img/entra_create_team.png differ
diff --git a/docs/my-website/img/litellm_entra_id.png b/docs/my-website/img/litellm_entra_id.png
new file mode 100644
index 00000000000..4cfbd0747fc
Binary files /dev/null and b/docs/my-website/img/litellm_entra_id.png differ
diff --git a/docs/my-website/img/msft_auto_team.png b/docs/my-website/img/msft_auto_team.png
new file mode 100644
index 00000000000..ac5c631e838
Binary files /dev/null and b/docs/my-website/img/msft_auto_team.png differ
diff --git a/docs/my-website/img/msft_default_settings.png b/docs/my-website/img/msft_default_settings.png
new file mode 100644
index 00000000000..e7b43249c39
Binary files /dev/null and b/docs/my-website/img/msft_default_settings.png differ
diff --git a/docs/my-website/img/msft_enterprise_app.png b/docs/my-website/img/msft_enterprise_app.png
new file mode 100644
index 00000000000..0a8c849a5cd
Binary files /dev/null and b/docs/my-website/img/msft_enterprise_app.png differ
diff --git a/docs/my-website/img/msft_enterprise_assign_group.png b/docs/my-website/img/msft_enterprise_assign_group.png
new file mode 100644
index 00000000000..d43e1c66845
Binary files /dev/null and b/docs/my-website/img/msft_enterprise_assign_group.png differ
diff --git a/docs/my-website/img/msft_enterprise_select_group.png b/docs/my-website/img/msft_enterprise_select_group.png
new file mode 100644
index 00000000000..e49032db9f4
Binary files /dev/null and b/docs/my-website/img/msft_enterprise_select_group.png differ
diff --git a/docs/my-website/img/msft_member_1.png b/docs/my-website/img/msft_member_1.png
new file mode 100644
index 00000000000..2fe627f773e
Binary files /dev/null and b/docs/my-website/img/msft_member_1.png differ
diff --git a/docs/my-website/img/msft_member_2.png b/docs/my-website/img/msft_member_2.png
new file mode 100644
index 00000000000..9757aa9cea9
Binary files /dev/null and b/docs/my-website/img/msft_member_2.png differ
diff --git a/docs/my-website/img/msft_member_3.png b/docs/my-website/img/msft_member_3.png
new file mode 100644
index 00000000000..1d81b1b6166
Binary files /dev/null and b/docs/my-website/img/msft_member_3.png differ
diff --git a/docs/my-website/img/msft_sso_sign_in.png b/docs/my-website/img/msft_sso_sign_in.png
new file mode 100644
index 00000000000..43c5173295c
Binary files /dev/null and b/docs/my-website/img/msft_sso_sign_in.png differ
diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js
index b8591cb9937..80c5346e27d 100644
--- a/docs/my-website/sidebars.js
+++ b/docs/my-website/sidebars.js
@@ -435,6 +435,7 @@ const sidebars = {
label: "Tutorials",
items: [
"tutorials/openweb_ui",
+ "tutorials/msft_sso",
'tutorials/litellm_proxy_aporia',
{
type: "category",
diff --git a/litellm/__init__.py b/litellm/__init__.py
index a3b37da2b4e..1252ee3d6fb 100644
--- a/litellm/__init__.py
+++ b/litellm/__init__.py
@@ -277,7 +277,7 @@ default_key_generate_params: Optional[Dict] = None
upperbound_key_generate_params: Optional[LiteLLM_UpperboundKeyGenerateParams] = None
key_generation_settings: Optional[StandardKeyGenerationConfig] = None
default_internal_user_params: Optional[Dict] = None
-default_team_params: Optional[NewTeamRequest] = None
+default_team_params: Optional[Union[NewTeamRequest, Dict]] = None
default_team_settings: Optional[List] = None
max_user_budget: Optional[float] = None
default_max_internal_user_budget: Optional[float] = None
diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py
index 0365336e730..1804aa9f569 100644
--- a/litellm/proxy/management_endpoints/ui_sso.py
+++ b/litellm/proxy/management_endpoints/ui_sso.py
@@ -11,6 +11,7 @@ Has all /sso/* routes
import asyncio
import os
import uuid
+from copy import deepcopy
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast
from fastapi import APIRouter, Depends, HTTPException, Request, status
@@ -919,13 +920,13 @@ class SSOAuthenticationHandler:
team_alias=litellm_team_name,
)
if litellm.default_team_params:
- team_request = litellm.default_team_params.model_copy(
- deep=True,
- update={
- "team_id": litellm_team_id,
- "team_alias": litellm_team_name,
- },
+ team_request = SSOAuthenticationHandler._cast_and_deepcopy_litellm_default_team_params(
+ default_team_params=litellm.default_team_params,
+ litellm_team_id=litellm_team_id,
+ litellm_team_name=litellm_team_name,
+ team_request=team_request,
)
+
await new_team(
data=team_request,
# params used for Audit Logging
@@ -938,6 +939,35 @@ class SSOAuthenticationHandler:
except Exception as e:
verbose_proxy_logger.exception(f"Error creating Litellm Team: {e}")
+ @staticmethod
+ def _cast_and_deepcopy_litellm_default_team_params(
+ default_team_params: Union[NewTeamRequest, Dict],
+ team_request: NewTeamRequest,
+ litellm_team_id: str,
+ litellm_team_name: Optional[str] = None,
+ ) -> NewTeamRequest:
+ """
+ Casts and deepcopies the litellm.default_team_params to a NewTeamRequest object
+
+ - Ensures we create a new NewTeamRequest object
+ - Handle the case where litellm.default_team_params is a dict or a NewTeamRequest object
+ - Adds the litellm_team_id and litellm_team_name to the NewTeamRequest object
+ """
+ if isinstance(default_team_params, dict):
+ _team_request = deepcopy(default_team_params)
+ _team_request["team_id"] = litellm_team_id
+ _team_request["team_alias"] = litellm_team_name
+ team_request = NewTeamRequest(**_team_request)
+ elif isinstance(litellm.default_team_params, NewTeamRequest):
+ team_request = litellm.default_team_params.model_copy(
+ deep=True,
+ update={
+ "team_id": litellm_team_id,
+ "team_alias": litellm_team_name,
+ },
+ )
+ return team_request
+
class MicrosoftSSOHandler:
"""
diff --git a/tests/litellm/proxy/management_endpoints/test_ui_sso.py b/tests/litellm/proxy/management_endpoints/test_ui_sso.py
index 09e337bf846..6683c2cc914 100644
--- a/tests/litellm/proxy/management_endpoints/test_ui_sso.py
+++ b/tests/litellm/proxy/management_endpoints/test_ui_sso.py
@@ -418,62 +418,21 @@ def test_get_group_ids_from_graph_api_response():
@pytest.mark.asyncio
-async def test_upsert_sso_user_existing_user():
- """
- If a user_id is already in the LiteLLM DB and the user signed in with SSO. Ensure that the user_id is updated with the SSO user_email
-
- SSO Test
- """
- # Arrange
- mock_prisma = MagicMock()
- mock_prisma.db = MagicMock()
- mock_prisma.db.litellm_usertable = MagicMock()
- mock_prisma.db.litellm_usertable.update_many = AsyncMock()
-
- # Create a mock existing user
- mock_user = MagicMock()
- mock_user.user_id = "existing_user_123"
- mock_user.user_email = "old_email@example.com"
-
- # Create mock SSO response
- mock_sso_response = CustomOpenID(
- email="new_email@example.com",
- display_name="Test User",
- provider="microsoft",
- id="existing_user_123",
- first_name="Test",
- last_name="User",
- team_ids=[],
- )
-
- # Create mock user defined values
- mock_user_defined_values = MagicMock()
-
- # Act
- result = await SSOAuthenticationHandler.upsert_sso_user(
- result=mock_sso_response,
- user_info=mock_user,
- user_email="new_email@example.com",
- user_defined_values=mock_user_defined_values,
- prisma_client=mock_prisma,
- )
-
- # Assert
- mock_prisma.db.litellm_usertable.update_many.assert_called_once_with(
- where={"user_id": "existing_user_123"},
- data={"user_email": "new_email@example.com"},
- )
- assert result == mock_user
-
-
-async def test_default_team_params():
+@pytest.mark.parametrize(
+ "team_params",
+ [
+ # Test case 1: Using NewTeamRequest
+ NewTeamRequest(max_budget=10, budget_duration="1d", models=["special-gpt-5"]),
+ # Test case 2: Using Dict
+ {"max_budget": 10, "budget_duration": "1d", "models": ["special-gpt-5"]},
+ ],
+)
+async def test_default_team_params(team_params):
"""
When litellm.default_team_params is set, it should be used to create a new team
"""
# Arrange
- litellm.default_team_params = NewTeamRequest(
- max_budget=10, budget_duration="1d", models=["special-gpt-5"]
- )
+ litellm.default_team_params = team_params
def mock_jsonify_team_object(db_data):
return db_data