-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Expand file tree
/
Copy pathtest_auth.py
More file actions
141 lines (122 loc) · 5.19 KB
/
test_auth.py
File metadata and controls
141 lines (122 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
"""Tests for OAuth 2.0 shared code."""
import pytest
from pydantic import ValidationError
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthMetadata
class TestOAuthMetadata:
"""Tests for OAuthMetadata parsing."""
def test_oauth(self):
"""Should not throw when parsing OAuth metadata."""
OAuthMetadata.model_validate(
{
"issuer": "https://example.com",
"authorization_endpoint": "https://example.com/oauth2/authorize",
"token_endpoint": "https://example.com/oauth2/token",
"scopes_supported": ["read", "write"],
"response_types_supported": ["code", "token"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
}
)
def test_oidc(self):
"""Should not throw when parsing OIDC metadata."""
OAuthMetadata.model_validate(
{
"issuer": "https://example.com",
"authorization_endpoint": "https://example.com/oauth2/authorize",
"token_endpoint": "https://example.com/oauth2/token",
"end_session_endpoint": "https://example.com/logout",
"id_token_signing_alg_values_supported": ["RS256"],
"jwks_uri": "https://example.com/.well-known/jwks.json",
"response_types_supported": ["code", "token"],
"revocation_endpoint": "https://example.com/oauth2/revoke",
"scopes_supported": ["openid", "read", "write"],
"subject_types_supported": ["public"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
"userinfo_endpoint": "https://example.com/oauth2/userInfo",
}
)
def test_oauth_with_jarm(self):
"""Should not throw when parsing OAuth metadata that includes JARM response modes."""
OAuthMetadata.model_validate(
{
"issuer": "https://example.com",
"authorization_endpoint": "https://example.com/oauth2/authorize",
"token_endpoint": "https://example.com/oauth2/token",
"scopes_supported": ["read", "write"],
"response_types_supported": ["code", "token"],
"response_modes_supported": [
"query",
"fragment",
"form_post",
"query.jwt",
"fragment.jwt",
"form_post.jwt",
"jwt",
],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
}
)
# RFC 7591 §2 marks client_uri/logo_uri/tos_uri/policy_uri/jwks_uri as OPTIONAL.
# Some authorization servers echo the client's omitted metadata back as ""
# instead of dropping the keys; without coercion, AnyHttpUrl rejects "" and
# the whole registration response is thrown away even though the server
# returned a valid client_id.
@pytest.mark.parametrize(
"empty_field",
["client_uri", "logo_uri", "tos_uri", "policy_uri", "jwks_uri"],
)
def test_optional_url_empty_string_coerced_to_none(empty_field: str):
data = {
"redirect_uris": ["https://example.com/callback"],
empty_field: "",
}
metadata = OAuthClientMetadata.model_validate(data)
assert getattr(metadata, empty_field) is None
def test_all_optional_urls_empty_together():
data = {
"redirect_uris": ["https://example.com/callback"],
"client_uri": "",
"logo_uri": "",
"tos_uri": "",
"policy_uri": "",
"jwks_uri": "",
}
metadata = OAuthClientMetadata.model_validate(data)
assert metadata.client_uri is None
assert metadata.logo_uri is None
assert metadata.tos_uri is None
assert metadata.policy_uri is None
assert metadata.jwks_uri is None
def test_valid_url_passes_through_unchanged():
data = {
"redirect_uris": ["https://example.com/callback"],
"client_uri": "https://udemy.com/",
}
metadata = OAuthClientMetadata.model_validate(data)
assert str(metadata.client_uri) == "https://udemy.com/"
def test_information_full_inherits_coercion():
"""OAuthClientInformationFull subclasses OAuthClientMetadata, so the
same coercion applies to DCR responses parsed via the full model."""
data = {
"client_id": "abc123",
"redirect_uris": ["https://example.com/callback"],
"client_uri": "",
"logo_uri": "",
"tos_uri": "",
"policy_uri": "",
"jwks_uri": "",
}
info = OAuthClientInformationFull.model_validate(data)
assert info.client_id == "abc123"
assert info.client_uri is None
assert info.logo_uri is None
assert info.tos_uri is None
assert info.policy_uri is None
assert info.jwks_uri is None
def test_invalid_non_empty_url_still_rejected():
"""Coercion must only touch empty strings — garbage URLs still raise."""
data = {
"redirect_uris": ["https://example.com/callback"],
"client_uri": "not a url",
}
with pytest.raises(ValidationError):
OAuthClientMetadata.model_validate(data)