Union previously requested scopes on step-up re-authorization (SEP-2350) (#2931)
This commit is contained in:
parent
4573e4ac33
commit
1331131650
2 changed files with 155 additions and 8 deletions
|
|
@ -35,6 +35,7 @@ from mcp.client.auth.utils import (
|
|||
handle_token_response_scopes,
|
||||
is_valid_client_metadata_url,
|
||||
should_use_client_metadata_url,
|
||||
union_scopes,
|
||||
validate_authorization_response_iss,
|
||||
validate_metadata_issuer,
|
||||
)
|
||||
|
|
@ -634,13 +635,19 @@ class OAuthClientProvider(httpx.Auth):
|
|||
# Step 2: Check if we need to step-up authorization
|
||||
if error == "insufficient_scope": # pragma: no branch
|
||||
try:
|
||||
# Step 2a: Update the required scopes
|
||||
self.context.client_metadata.scope = get_client_metadata_scopes(
|
||||
# Step 2a: Union previously requested scopes with the newly challenged
|
||||
# scopes (SEP-2350) so escalating one operation keeps the others' grants.
|
||||
# Fold in the stored token's scope too: on a restart the token is reloaded
|
||||
# but client_metadata.scope is not, so it would otherwise be the only basis.
|
||||
challenged_scope = get_client_metadata_scopes(
|
||||
extract_scope_from_www_auth(response),
|
||||
self.context.protected_resource_metadata,
|
||||
self.context.oauth_metadata,
|
||||
self.context.client_metadata.grant_types,
|
||||
)
|
||||
granted_scope = self.context.current_tokens.scope if self.context.current_tokens else None
|
||||
prior_scope = union_scopes(self.context.client_metadata.scope, granted_scope)
|
||||
self.context.client_metadata.scope = union_scopes(prior_scope, challenged_scope)
|
||||
|
||||
# Step 2b: Perform (re-)authorization and token exchange
|
||||
token_response = yield await self._perform_authorization()
|
||||
|
|
|
|||
|
|
@ -131,6 +131,28 @@ def get_client_metadata_scopes(
|
|||
return selected_scope
|
||||
|
||||
|
||||
def union_scopes(previous_scope: str | None, new_scope: str | None) -> str | None:
|
||||
"""Merge two space-delimited scope strings, preserving order and dropping duplicates.
|
||||
|
||||
SEP-2350: on step-up re-authorization the client requests the union of previously requested
|
||||
scopes and the newly challenged scopes, so escalating one operation does not drop the
|
||||
permissions granted for another. Previously requested scopes come first; new scopes are
|
||||
appended in order.
|
||||
"""
|
||||
if not previous_scope:
|
||||
return new_scope
|
||||
if not new_scope:
|
||||
return previous_scope
|
||||
|
||||
merged = previous_scope.split()
|
||||
seen = set(merged)
|
||||
for scope in new_scope.split():
|
||||
if scope not in seen:
|
||||
merged.append(scope)
|
||||
seen.add(scope)
|
||||
return " ".join(merged)
|
||||
|
||||
|
||||
def build_oauth_authorization_server_metadata_discovery_urls(auth_server_url: str | None, server_url: str) -> list[str]:
|
||||
"""Generate an ordered list of URLs for authorization server metadata discovery.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue