mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 05:24:18 +02:00
fix: resolve EntraOBOToken dependency injection through MultiAuth (#3609)
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
This commit is contained in:
parent
00df243171
commit
492db9972f
2 changed files with 59 additions and 3 deletions
|
|
@ -14,6 +14,7 @@ import httpx
|
|||
from key_value.aio.protocols import AsyncKeyValue
|
||||
|
||||
from fastmcp.dependencies import Dependency
|
||||
from fastmcp.server.auth.auth import MultiAuth
|
||||
from fastmcp.server.auth.oauth_proxy import OAuthProxy
|
||||
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
||||
from fastmcp.utilities.auth import decode_jwt_payload, parse_scopes
|
||||
|
|
@ -24,6 +25,8 @@ if TYPE_CHECKING:
|
|||
from mcp.server.auth.provider import AuthorizationParams
|
||||
from mcp.shared.auth import OAuthClientInformationFull
|
||||
|
||||
from fastmcp.server.auth.auth import AuthProvider
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Standard OIDC scopes that should never be prefixed with identifier_uri.
|
||||
|
|
@ -663,6 +666,17 @@ def _require_azure_identity(feature: str) -> None:
|
|||
) from e
|
||||
|
||||
|
||||
def _find_azure_provider(auth: AuthProvider | None) -> AzureProvider | None:
|
||||
"""Extract an AzureProvider from an auth provider, unwrapping MultiAuth if needed."""
|
||||
if isinstance(auth, AzureProvider):
|
||||
return auth
|
||||
|
||||
if isinstance(auth, MultiAuth) and isinstance(auth.server, AzureProvider):
|
||||
return auth.server
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class _EntraOBOToken(Dependency[str]):
|
||||
"""Dependency that performs OBO token exchange for Microsoft Entra.
|
||||
|
||||
|
|
@ -687,13 +701,14 @@ class _EntraOBOToken(Dependency[str]):
|
|||
)
|
||||
|
||||
server = get_server()
|
||||
if not isinstance(server.auth, AzureProvider):
|
||||
azure_provider = _find_azure_provider(server.auth)
|
||||
if azure_provider is None:
|
||||
raise RuntimeError(
|
||||
"EntraOBOToken requires an AzureProvider as the auth provider. "
|
||||
f"Current provider: {type(server.auth).__name__}"
|
||||
)
|
||||
|
||||
credential = await server.auth.get_obo_credential(
|
||||
credential = await azure_provider.get_obo_credential(
|
||||
user_assertion=access_token.token,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
import pytest
|
||||
from key_value.aio.stores.memory import MemoryStore
|
||||
|
||||
from fastmcp.server.auth.auth import MultiAuth
|
||||
from fastmcp.server.auth.providers.azure import (
|
||||
OIDC_SCOPES,
|
||||
AzureJWTVerifier,
|
||||
AzureProvider,
|
||||
_find_azure_provider,
|
||||
)
|
||||
from fastmcp.server.auth.providers.jwt import RSAKeyPair
|
||||
from fastmcp.server.auth.providers.jwt import RSAKeyPair, StaticTokenVerifier
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -725,3 +727,42 @@ class TestAzureOBOIntegration:
|
|||
|
||||
dep = _EntraOBOToken(["scope"])
|
||||
assert isinstance(dep, Dependency)
|
||||
|
||||
|
||||
class TestFindAzureProvider:
|
||||
"""Tests for _find_azure_provider helper used by EntraOBOToken."""
|
||||
|
||||
def test_returns_azure_provider_directly(self, memory_storage):
|
||||
"""When auth is an AzureProvider, return it directly."""
|
||||
provider = AzureProvider(
|
||||
tenant_id="test-tenant",
|
||||
client_id="test-client",
|
||||
client_secret="test-secret",
|
||||
client_storage=memory_storage,
|
||||
base_url="https://example.com",
|
||||
required_scopes=["read"],
|
||||
)
|
||||
assert _find_azure_provider(provider) is provider
|
||||
|
||||
def test_unwraps_multiauth_with_azure_server(self, memory_storage):
|
||||
"""When auth is a MultiAuth wrapping an AzureProvider, return the inner provider."""
|
||||
provider = AzureProvider(
|
||||
tenant_id="test-tenant",
|
||||
client_id="test-client",
|
||||
client_secret="test-secret",
|
||||
client_storage=memory_storage,
|
||||
base_url="https://example.com",
|
||||
required_scopes=["read"],
|
||||
)
|
||||
multi = MultiAuth(server=provider)
|
||||
assert _find_azure_provider(multi) is provider
|
||||
|
||||
def test_returns_none_for_no_auth(self):
|
||||
"""When auth is None, return None."""
|
||||
assert _find_azure_provider(None) is None
|
||||
|
||||
def test_returns_none_for_multiauth_without_azure_server(self):
|
||||
"""When MultiAuth has no server or a non-Azure server, return None."""
|
||||
verifier = StaticTokenVerifier(tokens={"t": {"client_id": "c", "scopes": []}})
|
||||
multi = MultiAuth(verifiers=[verifier])
|
||||
assert _find_azure_provider(multi) is None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue