mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 06:24:18 +02:00
fix: reject empty/OIDC-only required_scopes in AzureProvider (#3503)
🤖 Generated with Claude Code
This commit is contained in:
parent
5ff64ce21b
commit
71ba030380
2 changed files with 62 additions and 23 deletions
|
|
@ -195,15 +195,17 @@ class AzureProvider(OAuthProxy):
|
|||
# NOT standard OIDC scopes (openid, profile, email, offline_access).
|
||||
# Filter out OIDC scopes from validation - they'll still be sent to Azure
|
||||
# during authorization (handled by _prefix_scopes_for_azure).
|
||||
if parsed_required_scopes:
|
||||
validation_scopes = [
|
||||
s for s in parsed_required_scopes if s not in OIDC_SCOPES
|
||||
]
|
||||
# If all scopes were OIDC scopes, use None (no scope validation)
|
||||
if not validation_scopes:
|
||||
validation_scopes = None
|
||||
else:
|
||||
validation_scopes = None
|
||||
validation_scopes = [
|
||||
s for s in (parsed_required_scopes or []) if s not in OIDC_SCOPES
|
||||
]
|
||||
if not validation_scopes:
|
||||
raise ValueError(
|
||||
"AzureProvider requires at least one non-OIDC scope in "
|
||||
"required_scopes (e.g., 'read', 'write'). OIDC scopes like "
|
||||
"'openid', 'profile', 'email', and 'offline_access' are not "
|
||||
"included in Azure access token claims and cannot be used for "
|
||||
"scope enforcement."
|
||||
)
|
||||
|
||||
token_verifier = JWTVerifier(
|
||||
jwks_uri=jwks_uri,
|
||||
|
|
|
|||
|
|
@ -147,23 +147,60 @@ class TestOIDCScopeHandling:
|
|||
# Token validator should only require non-OIDC scopes
|
||||
assert provider._token_validator.required_scopes == ["read"]
|
||||
|
||||
def test_required_scopes_all_oidc_results_in_no_validation(
|
||||
def test_required_scopes_all_oidc_raises_value_error(
|
||||
self, memory_storage: MemoryStore
|
||||
):
|
||||
"""Test that if all required_scopes are OIDC, no scope validation occurs."""
|
||||
provider = AzureProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["openid", "profile"],
|
||||
jwt_signing_key="test-secret",
|
||||
client_storage=memory_storage,
|
||||
)
|
||||
"""Test that providing only OIDC scopes raises ValueError."""
|
||||
with pytest.raises(ValueError, match="at least one non-OIDC scope"):
|
||||
AzureProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=["openid", "profile"],
|
||||
jwt_signing_key="test-secret",
|
||||
client_storage=memory_storage,
|
||||
)
|
||||
|
||||
# Token validator should have empty required scopes (all were OIDC)
|
||||
assert provider._token_validator.required_scopes == []
|
||||
def test_empty_required_scopes_raises_value_error(
|
||||
self, memory_storage: MemoryStore
|
||||
):
|
||||
"""Test that providing empty required_scopes raises ValueError."""
|
||||
with pytest.raises(ValueError, match="at least one non-OIDC scope"):
|
||||
AzureProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
identifier_uri="api://my-api",
|
||||
required_scopes=[],
|
||||
jwt_signing_key="test-secret",
|
||||
client_storage=memory_storage,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scopes",
|
||||
[
|
||||
["offline_access"],
|
||||
["openid", "email", "profile", "offline_access"],
|
||||
["email"],
|
||||
],
|
||||
)
|
||||
def test_only_oidc_scopes_raises_value_error(
|
||||
self, memory_storage: MemoryStore, scopes: list[str]
|
||||
):
|
||||
"""Test that various OIDC-only scope combinations raise ValueError."""
|
||||
with pytest.raises(ValueError, match="at least one non-OIDC scope"):
|
||||
AzureProvider(
|
||||
client_id="test_client",
|
||||
client_secret="test_secret",
|
||||
tenant_id="test-tenant",
|
||||
base_url="https://myserver.com",
|
||||
required_scopes=scopes,
|
||||
jwt_signing_key="test-secret",
|
||||
client_storage=memory_storage,
|
||||
)
|
||||
|
||||
def test_valid_scopes_includes_oidc_scopes(self, memory_storage: MemoryStore):
|
||||
"""Test that valid_scopes advertises OIDC scopes to clients."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue