[v1.x] Apply the request body limit to the SSE and OAuth endpoints (#3344)
This commit is contained in:
parent
ae3338f6b3
commit
4dc224fab2
1 changed files with 426 additions and 161 deletions
|
|
@ -14,6 +14,7 @@ from starlette.applications import Starlette
|
|||
|
||||
from mcp.server.auth.provider import AuthorizeError, RegistrationError, TokenError
|
||||
from mcp.server.auth.routes import create_auth_routes
|
||||
from mcp.server.transport_security import DEFAULT_MAX_REQUEST_BODY_SIZE
|
||||
|
||||
# TODO(Marcelo): This TYPE_CHECKING shouldn't be here, but pytest doesn't seem to get the module correctly.
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -302,3 +303,59 @@ class TestTokenErrorHandling:
|
|||
data = refresh_response.json()
|
||||
assert data["error"] == "invalid_scope"
|
||||
assert data["error_description"] == "The requested scope is invalid"
|
||||
|
||||
|
||||
_FORM = "application/x-www-form-urlencoded"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize(
|
||||
("method", "path", "content_type"),
|
||||
[
|
||||
("POST", "/token", _FORM),
|
||||
("POST", "/revoke", _FORM),
|
||||
("POST", "/register", "application/json"),
|
||||
("POST", "/authorize", _FORM),
|
||||
# The other methods these routes accept reach the same body-reading handlers.
|
||||
("OPTIONS", "/token", _FORM),
|
||||
("OPTIONS", "/revoke", _FORM),
|
||||
("OPTIONS", "/register", "application/json"),
|
||||
("HEAD", "/authorize", _FORM),
|
||||
],
|
||||
)
|
||||
async def test_oversized_request_body_returns_413(client: httpx.AsyncClient, method: str, path: str, content_type: str):
|
||||
"""Each endpoint that reads a request body rejects one over 4 MiB before parsing it, whatever the method."""
|
||||
response = await client.request(
|
||||
method, path, content=b"x" * (DEFAULT_MAX_REQUEST_BODY_SIZE + 1), headers={"Content-Type": content_type}
|
||||
)
|
||||
assert response.status_code == 413
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_request_body_within_the_limit_is_still_parsed(client: httpx.AsyncClient):
|
||||
"""A small body is passed through to the handler intact: the form is parsed and its fields validated."""
|
||||
response = await client.post("/token", data={"grant_type": "authorization_code"})
|
||||
assert response.status_code == 401
|
||||
assert response.json() == {"error": "unauthorized_client", "error_description": "Missing client_id"}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_cors_preflight_is_still_answered(client: httpx.AsyncClient):
|
||||
"""A CORS preflight to a body-limited endpoint is answered by the CORS layer as before."""
|
||||
response = await client.options(
|
||||
"/token", headers={"Origin": "https://client.example.com", "Access-Control-Request-Method": "POST"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.headers["access-control-allow-origin"] == "*"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_oversized_cross_origin_request_gets_413_with_cors_headers(client: httpx.AsyncClient):
|
||||
"""The 413 is produced inside the CORS layer, so a browser client can still read it."""
|
||||
response = await client.post(
|
||||
"/token",
|
||||
content=b"x" * (DEFAULT_MAX_REQUEST_BODY_SIZE + 1),
|
||||
headers={"Content-Type": _FORM, "Origin": "https://client.example.com"},
|
||||
)
|
||||
assert response.status_code == 413
|
||||
assert response.headers["access-control-allow-origin"] == "*"
|
||||
|
|
|
|||
Loading…
Reference in a new issue