|
11 | 11 | from mcp.types import Tool as MCPLibTool |
12 | 12 | from pydantic import AnyUrl, BaseModel, Field, model_validator |
13 | 13 |
|
| 14 | +from onyx.auth.oauth_token_manager import conflicting_authorization_params |
14 | 15 | from onyx.db.enums import ( |
15 | 16 | EndpointPolicy, |
16 | 17 | MCPAuthenticationPerformer, |
|
19 | 20 | MCPServerStatus, |
20 | 21 | MCPTransport, |
21 | 22 | ) |
| 23 | +from onyx.oauth.models import SafeOAuthReturnPath |
22 | 24 |
|
23 | 25 | # Matches `{placeholder_name}` inside header value templates. |
24 | 26 | _PLACEHOLDER_RE = re.compile(r"\{([^}]+)\}") |
25 | 27 | # RFC 9110 field-name syntax: a non-empty sequence of HTTP token characters. |
26 | 28 | _HTTP_FIELD_NAME_RE = re.compile(r"[!#$%&'*+\-.^_`|~0-9A-Za-z]+") |
27 | | -RESERVED_MCP_OAUTH_AUTHORIZATION_PARAMS = { |
28 | | - "client_id", |
29 | | - "code_challenge", |
30 | | - "code_challenge_method", |
31 | | - "redirect_uri", |
32 | | - "resource", |
33 | | - "response_type", |
34 | | - "scope", |
35 | | - "state", |
36 | | -} |
37 | 29 |
|
38 | 30 |
|
39 | 31 | def _build_auto_substitution_map(*, user_email: str) -> dict[str, str]: |
@@ -387,8 +379,8 @@ def validate_auth_configuration(self) -> "MCPToolCreateRequest": |
387 | 379 | raise ValueError( |
388 | 380 | "oauth_token_endpoint is required for known-provider OAuth mode" |
389 | 381 | ) |
390 | | - reserved_params = RESERVED_MCP_OAUTH_AUTHORIZATION_PARAMS.intersection( |
391 | | - self.oauth_additional_auth_params or {} |
| 382 | + reserved_params = conflicting_authorization_params( |
| 383 | + self.oauth_additional_auth_params |
392 | 384 | ) |
393 | 385 | if reserved_params: |
394 | 386 | raise ValueError( |
@@ -511,7 +503,9 @@ class MCPOAuthConnectResponse(BaseModel): |
511 | 503 |
|
512 | 504 | class MCPUserOAuthConnectRequest(BaseModel): |
513 | 505 | server_id: int = Field(..., description="ID of the MCP server") |
514 | | - return_path: str = Field(..., description="Path to redirect to after callback") |
| 506 | + return_path: SafeOAuthReturnPath = Field( |
| 507 | + ..., description="Path to redirect to after callback" |
| 508 | + ) |
515 | 509 | include_resource_param: bool = Field(..., description="Include resource parameter") |
516 | 510 | force_reauthentication: bool = Field( |
517 | 511 | default=False, |
@@ -539,17 +533,6 @@ class MCPUserOAuthConnectRequest(BaseModel): |
539 | 533 | ), |
540 | 534 | ) |
541 | 535 |
|
542 | | - @model_validator(mode="after") |
543 | | - def validate_return_path(self) -> "MCPUserOAuthConnectRequest": |
544 | | - if ( |
545 | | - not self.return_path.startswith("/") |
546 | | - or self.return_path.startswith("//") |
547 | | - or "\\" in self.return_path |
548 | | - or any(not character.isprintable() for character in self.return_path) |
549 | | - ): |
550 | | - raise ValueError("return_path must be a safe internal path") |
551 | | - return self |
552 | | - |
553 | 536 |
|
554 | 537 | class MCPUserOAuthConnectResponse(BaseModel): |
555 | 538 | server_id: int |
@@ -592,7 +575,7 @@ class MCPOAuthServerSnapshot(BaseModel): |
592 | 575 | class MCPOAuthFlowState(BaseModel): |
593 | 576 | server_id: int |
594 | 577 | connection_config_id: int |
595 | | - return_path: str |
| 578 | + return_path: SafeOAuthReturnPath |
596 | 579 | code_verifier: str |
597 | 580 | redirect_uri: AnyUrl |
598 | 581 | server_snapshot: MCPOAuthServerSnapshot |
|
0 commit comments