diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index 82955dadb5..b71e100e15 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -377,6 +377,7 @@ router_settings: | ATHINA_API_KEY | API key for Athina service | ATHINA_BASE_URL | Base URL for Athina service (defaults to `https://log.athina.ai`) | AUTH_STRATEGY | Strategy used for authentication (e.g., OAuth, API key) +| AUTO_REDIRECT_UI_LOGIN_TO_SSO | Flag to enable automatic redirect of UI login page to SSO when SSO is configured. Default is **true** | ANTHROPIC_API_KEY | API key for Anthropic service | ANTHROPIC_API_BASE | Base URL for Anthropic API. Default is https://api.anthropic.com | AWS_ACCESS_KEY_ID | Access Key ID for AWS services diff --git a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py index b297634d6b..9aaa2fb838 100644 --- a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py +++ b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py @@ -1,4 +1,5 @@ #### Analytics Endpoints ##### +import os from fastapi import APIRouter from litellm.types.proxy.discovery_endpoints.ui_discovery_endpoints import ( @@ -15,8 +16,11 @@ router = APIRouter() async def get_ui_config(): from litellm.proxy.utils import get_proxy_base_url, get_server_root_path from litellm.proxy.auth.auth_utils import _has_user_setup_sso + + auto_redirect_ui_login_to_sso = os.getenv("AUTO_REDIRECT_UI_LOGIN_TO_SSO", "true").lower() == "true" + return UiDiscoveryEndpoints( server_root_path=get_server_root_path(), proxy_base_url=get_proxy_base_url(), - is_sso_configured=_has_user_setup_sso(), + auto_redirect_to_sso=_has_user_setup_sso() and auto_redirect_ui_login_to_sso, ) diff --git a/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py b/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py index 88f0627f53..f100dd35fa 100644 --- a/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py +++ b/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py @@ -6,4 +6,4 @@ from pydantic import BaseModel class UiDiscoveryEndpoints(BaseModel): server_root_path: str proxy_base_url: Optional[str] - is_sso_configured: bool + auto_redirect_to_sso: bool diff --git a/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py b/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py index b6e064823c..599d543758 100644 --- a/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py +++ b/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py @@ -20,7 +20,8 @@ def test_ui_discovery_endpoints_with_defaults(): with patch("litellm.proxy.utils.get_server_root_path", return_value="/"), \ patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), \ - patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False): + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), \ + patch.dict(os.environ, {}, clear=False): response = client.get("/.well-known/litellm-ui-config") @@ -28,7 +29,7 @@ def test_ui_discovery_endpoints_with_defaults(): data = response.json() assert data["server_root_path"] == "/" assert data["proxy_base_url"] is None - assert data["is_sso_configured"] is False + assert data["auto_redirect_to_sso"] is False def test_ui_discovery_endpoints_with_custom_server_root_path(): @@ -38,7 +39,8 @@ def test_ui_discovery_endpoints_with_custom_server_root_path(): with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), \ - patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False): + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), \ + patch.dict(os.environ, {}, clear=False): response = client.get("/.well-known/litellm-ui-config") @@ -46,7 +48,7 @@ def test_ui_discovery_endpoints_with_custom_server_root_path(): data = response.json() assert data["server_root_path"] == "/litellm" assert data["proxy_base_url"] is None - assert data["is_sso_configured"] is False + assert data["auto_redirect_to_sso"] is False def test_ui_discovery_endpoints_with_proxy_base_url_when_set(): @@ -56,7 +58,8 @@ def test_ui_discovery_endpoints_with_proxy_base_url_when_set(): with patch("litellm.proxy.utils.get_server_root_path", return_value="/"), \ patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ - patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False): + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), \ + patch.dict(os.environ, {}, clear=False): response = client.get("/litellm/.well-known/litellm-ui-config") @@ -64,17 +67,18 @@ def test_ui_discovery_endpoints_with_proxy_base_url_when_set(): data = response.json() assert data["server_root_path"] == "/" assert data["proxy_base_url"] == "https://proxy.example.com" - assert data["is_sso_configured"] is False + assert data["auto_redirect_to_sso"] is False -def test_ui_discovery_endpoints_with_sso_configured_when_sso_is_setup(): +def test_ui_discovery_endpoints_with_sso_configured_and_auto_redirect_enabled(): app = FastAPI() app.include_router(router) client = TestClient(app) with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ - patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=True): + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=True), \ + patch.dict(os.environ, {"AUTO_REDIRECT_UI_LOGIN_TO_SSO": "true"}, clear=False): response = client.get("/.well-known/litellm-ui-config") @@ -82,5 +86,61 @@ def test_ui_discovery_endpoints_with_sso_configured_when_sso_is_setup(): data = response.json() assert data["server_root_path"] == "/litellm" assert data["proxy_base_url"] == "https://proxy.example.com" - assert data["is_sso_configured"] is True + assert data["auto_redirect_to_sso"] is True + + +def test_ui_discovery_endpoints_with_sso_configured_but_auto_redirect_disabled(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=True), \ + patch.dict(os.environ, {"AUTO_REDIRECT_UI_LOGIN_TO_SSO": "false"}, clear=False): + + response = client.get("/.well-known/litellm-ui-config") + + assert response.status_code == 200 + data = response.json() + assert data["server_root_path"] == "/litellm" + assert data["proxy_base_url"] == "https://proxy.example.com" + assert data["auto_redirect_to_sso"] is False + + +def test_ui_discovery_endpoints_with_sso_not_configured_but_auto_redirect_enabled(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), \ + patch.dict(os.environ, {"AUTO_REDIRECT_UI_LOGIN_TO_SSO": "true"}, clear=False): + + response = client.get("/.well-known/litellm-ui-config") + + assert response.status_code == 200 + data = response.json() + assert data["server_root_path"] == "/" + assert data["proxy_base_url"] is None + assert data["auto_redirect_to_sso"] is False + + +def test_ui_discovery_endpoints_both_routes_return_same_data(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with patch("litellm.proxy.utils.get_server_root_path", return_value="/litellm"), \ + patch("litellm.proxy.utils.get_proxy_base_url", return_value="https://proxy.example.com"), \ + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=True), \ + patch.dict(os.environ, {"AUTO_REDIRECT_UI_LOGIN_TO_SSO": "true"}, clear=False): + + response1 = client.get("/.well-known/litellm-ui-config") + response2 = client.get("/litellm/.well-known/litellm-ui-config") + + assert response1.status_code == 200 + assert response2.status_code == 200 + assert response1.json() == response2.json()