Accept additional response_types values from OAuth servers (#1323)
This commit is contained in:
parent
146d7efb0f
commit
1940040ac3
3 changed files with 70 additions and 2 deletions
|
|
@ -77,6 +77,17 @@ class RegistrationHandler:
|
|||
status_code=400,
|
||||
)
|
||||
|
||||
# The MCP spec requires servers to use the authorization `code` flow
|
||||
# with PKCE
|
||||
if "code" not in client_metadata.response_types:
|
||||
return PydanticJSONResponse(
|
||||
content=RegistrationErrorResponse(
|
||||
error="invalid_client_metadata",
|
||||
error_description="response_types must include 'code' for authorization_code grant",
|
||||
),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
client_id_issued_at = int(time.time())
|
||||
client_secret_expires_at = (
|
||||
client_id_issued_at + self.options.client_secret_expiry_seconds
|
||||
|
|
|
|||
|
|
@ -51,8 +51,9 @@ class OAuthClientMetadata(BaseModel):
|
|||
"authorization_code",
|
||||
"refresh_token",
|
||||
]
|
||||
# this implementation only supports code; ie: it does not support implicit grants
|
||||
response_types: list[Literal["code"]] = ["code"]
|
||||
# The MCP spec requires the "code" response type, but OAuth
|
||||
# servers may also return additional types they support
|
||||
response_types: list[str] = ["code"]
|
||||
scope: str | None = None
|
||||
|
||||
# these fields are currently unused, but we support & store them for potential
|
||||
|
|
|
|||
|
|
@ -937,6 +937,62 @@ class TestAuthEndpoints:
|
|||
assert error_data["error"] == "invalid_client_metadata"
|
||||
assert error_data["error_description"] == "grant_types must be authorization_code and refresh_token"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_client_registration_with_additional_response_types(
|
||||
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider
|
||||
):
|
||||
"""Test that registration accepts additional response_types values alongside 'code'."""
|
||||
client_metadata = {
|
||||
"redirect_uris": ["https://client.example.com/callback"],
|
||||
"client_name": "Test Client",
|
||||
"grant_types": ["authorization_code", "refresh_token"],
|
||||
"response_types": ["code", "none"], # Keycloak-style response with additional value
|
||||
}
|
||||
|
||||
response = await test_client.post("/register", json=client_metadata)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
|
||||
client = await mock_oauth_provider.get_client(data["client_id"])
|
||||
assert client is not None
|
||||
assert "code" in client.response_types
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_client_registration_response_types_without_code(self, test_client: httpx.AsyncClient):
|
||||
"""Test that registration rejects response_types that don't include 'code'."""
|
||||
client_metadata = {
|
||||
"redirect_uris": ["https://client.example.com/callback"],
|
||||
"client_name": "Test Client",
|
||||
"grant_types": ["authorization_code", "refresh_token"],
|
||||
"response_types": ["token", "none", "nonsense-string"],
|
||||
}
|
||||
|
||||
response = await test_client.post("/register", json=client_metadata)
|
||||
assert response.status_code == 400
|
||||
error_data = response.json()
|
||||
assert "error" in error_data
|
||||
assert error_data["error"] == "invalid_client_metadata"
|
||||
assert "response_types must include 'code'" in error_data["error_description"]
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_client_registration_default_response_types(
|
||||
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider
|
||||
):
|
||||
"""Test that registration uses default response_types of ['code'] when not specified."""
|
||||
client_metadata = {
|
||||
"redirect_uris": ["https://client.example.com/callback"],
|
||||
"client_name": "Test Client",
|
||||
"grant_types": ["authorization_code", "refresh_token"],
|
||||
# response_types not specified, should default to ["code"]
|
||||
}
|
||||
|
||||
response = await test_client.post("/register", json=client_metadata)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
|
||||
assert "response_types" in data
|
||||
assert data["response_types"] == ["code"]
|
||||
|
||||
|
||||
class TestAuthorizeEndpointErrors:
|
||||
"""Test error handling in the OAuth authorization endpoint."""
|
||||
|
|
|
|||
Loading…
Reference in a new issue