Skip to content

Commit 7f80d32

Browse files
committed
- Added tests cases for few of the missing lines. src/mcp/client/auth/extensions/enterprise_managed_auth.py 232->235, 304->307.
- Resolved pre-commit errors.
1 parent 9759c7a commit 7f80d32

File tree

2 files changed

+147
-19
lines changed

2 files changed

+147
-19
lines changed

tests/client/auth/test_enterprise_managed_auth_client.py

Lines changed: 135 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
)
1919
from mcp.shared.auth import OAuthClientMetadata
2020

21-
2221
# ============================================================================
2322
# Fixtures
2423
# ============================================================================
@@ -376,9 +375,10 @@ async def test_exchange_id_jag_for_access_token_success(sample_id_jag, mock_toke
376375
)
377376

378377
# Set up OAuth metadata
379-
from mcp.shared.auth import OAuthMetadata
380378
from pydantic import HttpUrl
381379

380+
from mcp.shared.auth import OAuthMetadata
381+
382382
provider.context.oauth_metadata = OAuthMetadata(
383383
issuer=HttpUrl("https://auth.mcp-server.example/"),
384384
authorization_endpoint=HttpUrl("https://auth.mcp-server.example/oauth2/authorize"),
@@ -559,6 +559,60 @@ async def test_exchange_token_with_client_authentication(sample_id_token, sample
559559
assert call_args[1]["data"]["client_secret"] == "test-client-secret"
560560

561561

562+
@pytest.mark.anyio
563+
async def test_exchange_token_with_client_id_only(sample_id_token, sample_id_jag, mock_token_storage):
564+
"""Test token exchange with client_id but no client_secret (covers branch 232->235)."""
565+
from mcp.shared.auth import OAuthClientInformationFull
566+
567+
token_exchange_params = TokenExchangeParameters.from_id_token(
568+
id_token=sample_id_token,
569+
mcp_server_auth_issuer="https://auth.mcp-server.example/",
570+
mcp_server_resource_id="https://mcp-server.example/",
571+
scope="read write",
572+
)
573+
574+
provider = EnterpriseAuthOAuthClientProvider(
575+
server_url="https://mcp-server.example/",
576+
client_metadata=OAuthClientMetadata(
577+
redirect_uris=["http://localhost:8080/callback"],
578+
client_name="Test Client",
579+
),
580+
storage=mock_token_storage,
581+
idp_token_endpoint="https://idp.example.com/oauth2/token",
582+
token_exchange_params=token_exchange_params,
583+
)
584+
585+
# Set client info WITHOUT secret (client_secret=None)
586+
provider.context.client_info = OAuthClientInformationFull(
587+
client_id="test-client-id",
588+
client_secret=None, # No secret
589+
redirect_uris=["http://localhost:8080/callback"],
590+
)
591+
592+
# Mock HTTP response
593+
mock_response = httpx.Response(
594+
status_code=200,
595+
json={
596+
"issued_token_type": "urn:ietf:params:oauth:token-type:id-jag",
597+
"access_token": sample_id_jag,
598+
"token_type": "N_A",
599+
"scope": "read write",
600+
"expires_in": 300,
601+
},
602+
)
603+
604+
mock_client = Mock(spec=httpx.AsyncClient)
605+
mock_client.post = AsyncMock(return_value=mock_response)
606+
607+
# Perform token exchange
608+
id_jag = await provider.exchange_token_for_id_jag(mock_client)
609+
610+
# Verify client_id was included but NOT client_secret
611+
call_args = mock_client.post.call_args
612+
assert call_args[1]["data"]["client_id"] == "test-client-id"
613+
assert "client_secret" not in call_args[1]["data"]
614+
615+
562616
@pytest.mark.anyio
563617
async def test_exchange_token_http_error(sample_id_token, mock_token_storage):
564618
"""Test token exchange with HTTP error."""
@@ -656,7 +710,10 @@ async def test_exchange_token_warning_for_non_na_token_type(sample_id_token, sam
656710

657711
# Should succeed but log warning
658712
import logging
659-
with patch.object(logging.getLogger("mcp.client.auth.extensions.enterprise_managed_auth"), "warning") as mock_warning:
713+
714+
with patch.object(
715+
logging.getLogger("mcp.client.auth.extensions.enterprise_managed_auth"), "warning"
716+
) as mock_warning:
660717
id_jag = await provider.exchange_token_for_id_jag(mock_client)
661718
assert id_jag == sample_id_jag
662719
mock_warning.assert_called_once()
@@ -665,9 +722,10 @@ async def test_exchange_token_warning_for_non_na_token_type(sample_id_token, sam
665722
@pytest.mark.anyio
666723
async def test_exchange_id_jag_with_client_authentication(sample_id_jag, mock_token_storage):
667724
"""Test JWT bearer grant with client authentication."""
668-
from mcp.shared.auth import OAuthClientInformationFull, OAuthMetadata
669725
from pydantic import HttpUrl
670726

727+
from mcp.shared.auth import OAuthClientInformationFull, OAuthMetadata
728+
671729
token_exchange_params = TokenExchangeParameters.from_id_token(
672730
id_token="dummy-token",
673731
mcp_server_auth_issuer="https://auth.mcp-server.example/",
@@ -715,18 +773,86 @@ async def test_exchange_id_jag_with_client_authentication(sample_id_jag, mock_to
715773
# Perform JWT bearer grant
716774
token = await provider.exchange_id_jag_for_access_token(mock_client, sample_id_jag)
717775

776+
# Verify token was returned
777+
assert token.access_token == "mcp-access-token-12345"
778+
718779
# Verify client credentials were included
719780
call_args = mock_client.post.call_args
720781
assert call_args[1]["data"]["client_id"] == "test-client-id"
721782
assert call_args[1]["data"]["client_secret"] == "test-client-secret"
722783

723784

785+
@pytest.mark.anyio
786+
async def test_exchange_id_jag_with_client_id_only(sample_id_jag, mock_token_storage):
787+
"""Test JWT bearer grant with client_id but no client_secret (covers branch 304->307)."""
788+
from pydantic import HttpUrl
789+
790+
from mcp.shared.auth import OAuthClientInformationFull, OAuthMetadata
791+
792+
token_exchange_params = TokenExchangeParameters.from_id_token(
793+
id_token="dummy-token",
794+
mcp_server_auth_issuer="https://auth.mcp-server.example/",
795+
mcp_server_resource_id="https://mcp-server.example/",
796+
)
797+
798+
provider = EnterpriseAuthOAuthClientProvider(
799+
server_url="https://mcp-server.example/",
800+
client_metadata=OAuthClientMetadata(
801+
redirect_uris=["http://localhost:8080/callback"],
802+
),
803+
storage=mock_token_storage,
804+
idp_token_endpoint="https://idp.example.com/oauth2/token",
805+
token_exchange_params=token_exchange_params,
806+
)
807+
808+
# Set client info WITHOUT secret (client_secret=None)
809+
provider.context.client_info = OAuthClientInformationFull(
810+
client_id="test-client-id",
811+
client_secret=None, # No secret
812+
redirect_uris=["http://localhost:8080/callback"],
813+
)
814+
815+
# Set up OAuth metadata
816+
provider.context.oauth_metadata = OAuthMetadata(
817+
issuer=HttpUrl("https://auth.mcp-server.example/"),
818+
authorization_endpoint=HttpUrl("https://auth.mcp-server.example/oauth2/authorize"),
819+
token_endpoint=HttpUrl("https://auth.mcp-server.example/oauth2/token"),
820+
)
821+
822+
# Mock HTTP response
823+
mock_response = httpx.Response(
824+
status_code=200,
825+
json={
826+
"token_type": "Bearer",
827+
"access_token": "mcp-access-token-12345",
828+
"expires_in": 3600,
829+
"scope": "read write",
830+
},
831+
)
832+
833+
mock_client = Mock(spec=httpx.AsyncClient)
834+
mock_client.post = AsyncMock(return_value=mock_response)
835+
836+
# Perform JWT bearer grant
837+
token = await provider.exchange_id_jag_for_access_token(mock_client, sample_id_jag)
838+
839+
# Verify token was returned correctly
840+
assert token.access_token == "mcp-access-token-12345"
841+
assert token.token_type == "Bearer"
842+
843+
# Verify client_id was included but NOT client_secret
844+
call_args = mock_client.post.call_args
845+
assert call_args[1]["data"]["client_id"] == "test-client-id"
846+
assert "client_secret" not in call_args[1]["data"]
847+
848+
724849
@pytest.mark.anyio
725850
async def test_exchange_id_jag_error_response(sample_id_jag, mock_token_storage):
726851
"""Test JWT bearer grant with error response."""
727-
from mcp.shared.auth import OAuthMetadata
728852
from pydantic import HttpUrl
729853

854+
from mcp.shared.auth import OAuthMetadata
855+
730856
token_exchange_params = TokenExchangeParameters.from_id_token(
731857
id_token="dummy-token",
732858
mcp_server_auth_issuer="https://auth.mcp-server.example/",
@@ -770,9 +896,10 @@ async def test_exchange_id_jag_error_response(sample_id_jag, mock_token_storage)
770896
@pytest.mark.anyio
771897
async def test_exchange_id_jag_non_json_error(sample_id_jag, mock_token_storage):
772898
"""Test JWT bearer grant with non-JSON error response."""
773-
from mcp.shared.auth import OAuthMetadata
774899
from pydantic import HttpUrl
775900

901+
from mcp.shared.auth import OAuthMetadata
902+
776903
token_exchange_params = TokenExchangeParameters.from_id_token(
777904
id_token="dummy-token",
778905
mcp_server_auth_issuer="https://auth.mcp-server.example/",
@@ -814,9 +941,10 @@ async def test_exchange_id_jag_non_json_error(sample_id_jag, mock_token_storage)
814941
@pytest.mark.anyio
815942
async def test_exchange_id_jag_http_error(sample_id_jag, mock_token_storage):
816943
"""Test JWT bearer grant with HTTP error."""
817-
from mcp.shared.auth import OAuthMetadata
818944
from pydantic import HttpUrl
819945

946+
from mcp.shared.auth import OAuthMetadata
947+
820948
token_exchange_params = TokenExchangeParameters.from_id_token(
821949
id_token="dummy-token",
822950
mcp_server_auth_issuer="https://auth.mcp-server.example/",
@@ -872,5 +1000,3 @@ def test_validate_token_exchange_params_missing_resource():
8721000

8731001
with pytest.raises(ValueError, match="resource is required"):
8741002
validate_token_exchange_params(params)
875-
876-

tests/server/auth/test_enterprise_managed_auth_server.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
ReplayPreventionStore,
1414
)
1515

16-
1716
# ============================================================================
1817
# Fixtures
1918
# ============================================================================
@@ -50,6 +49,7 @@ def valid_id_jag_claims():
5049
@pytest.fixture
5150
def create_id_jag(valid_id_jag_claims):
5251
"""Factory to create ID-JAG tokens."""
52+
5353
def _create(claims=None, secret="test-secret"):
5454
claims_data = valid_id_jag_claims.copy()
5555
if claims:
@@ -60,6 +60,7 @@ def _create(claims=None, secret="test-secret"):
6060
algorithm="HS256",
6161
headers={"typ": "oauth-id-jag+jwt"},
6262
)
63+
6364
return _create
6465

6566

@@ -540,10 +541,11 @@ def test_validate_id_jag_with_jwks_client(create_id_jag):
540541
mock_signing_key = MagicMock()
541542
mock_signing_key.key = "mock-key"
542543

543-
with patch.object(validator.jwks_client, "get_signing_key_from_jwt", return_value=mock_signing_key), \
544-
patch("jwt.decode") as mock_decode, \
545-
patch("jwt.get_unverified_header") as mock_header:
546-
544+
with (
545+
patch.object(validator.jwks_client, "get_signing_key_from_jwt", return_value=mock_signing_key),
546+
patch("jwt.decode") as mock_decode,
547+
patch("jwt.get_unverified_header") as mock_header,
548+
):
547549
mock_header.return_value = {"typ": "oauth-id-jag+jwt", "alg": "RS256"}
548550
mock_decode.return_value = {
549551
"jti": "jti-with-jwks",
@@ -558,6 +560,10 @@ def test_validate_id_jag_with_jwks_client(create_id_jag):
558560

559561
claims = validator.validate_id_jag(id_jag, expected_client_id="client123")
560562

563+
# Verify claims were returned correctly
564+
assert claims.jti == "jti-with-jwks"
565+
assert claims.client_id == "client123"
566+
561567
# Verify JWKS client was called
562568
validator.jwks_client.get_signing_key_from_jwt.assert_called_once_with(id_jag)
563569
# Verify jwt.decode was called with the key
@@ -569,13 +575,9 @@ def test_validate_id_jag_invalid_token_error(jwt_validation_config, create_id_ja
569575
validator = IDJAGValidator(jwt_validation_config)
570576
id_jag = create_id_jag()
571577

572-
with patch("jwt.get_unverified_header") as mock_header, \
573-
patch("jwt.decode") as mock_decode:
574-
578+
with patch("jwt.get_unverified_header") as mock_header, patch("jwt.decode") as mock_decode:
575579
mock_header.return_value = {"typ": "oauth-id-jag+jwt", "alg": "HS256"}
576580
mock_decode.side_effect = jwt.InvalidTokenError("Invalid token")
577581

578582
with pytest.raises(ValueError, match="Invalid ID-JAG: Invalid token"):
579583
validator.validate_id_jag(id_jag, expected_client_id="client123")
580-
581-

0 commit comments

Comments
 (0)