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
|
|
@ -3265,6 +3265,15 @@ REQUIREMENTS: dict[str, Requirement] = {
|
|||
transports=("streamable-http",),
|
||||
note="OAuth is HTTP-only.",
|
||||
),
|
||||
"client-auth:403-scope-union": Requirement(
|
||||
source=f"{SPEC_BASE_URL}/basic/authorization#step-up-authorization-flow",
|
||||
behavior=(
|
||||
"On a 403 insufficient_scope step-up, the re-authorization request carries the union of the "
|
||||
"previously requested scopes and the newly challenged scopes (SEP-2350)."
|
||||
),
|
||||
transports=("streamable-http",),
|
||||
note="OAuth is HTTP-only.",
|
||||
),
|
||||
"client-auth:as-metadata-discovery:priority-order": Requirement(
|
||||
source=f"{SPEC_BASE_URL}/basic/authorization#authorization-server-metadata-discovery",
|
||||
behavior=(
|
||||
|
|
|
|||
|
|
@ -178,6 +178,35 @@ async def test_a_403_insufficient_scope_triggers_one_reauthorize_with_the_challe
|
|||
assert counts[("POST", "/token")] == 2
|
||||
|
||||
|
||||
@requirement("client-auth:403-scope-union")
|
||||
async def test_a_403_step_up_re_authorizes_with_the_union_of_prior_and_challenged_scopes() -> None:
|
||||
"""The step-up re-authorize requests the union of the previously requested and challenged scopes.
|
||||
|
||||
The first authorization requests `mcp`; the 403 challenges a disjoint `write` (not naming
|
||||
`mcp`). Per SEP-2350 the client must re-authorize with `mcp write`, not drop `mcp`. The client
|
||||
is pre-registered with both scopes so the server's authorize handler accepts the wider request.
|
||||
"""
|
||||
provider = InMemoryAuthorizationServerProvider()
|
||||
storage = InMemoryTokenStorage(client_info=seeded_client(provider, scope="mcp write"))
|
||||
server = Server("guarded", on_list_tools=list_tools)
|
||||
settings = auth_settings(required_scopes=["mcp"], valid_scopes=["mcp", "write"])
|
||||
challenge = 'Bearer error="insufficient_scope", scope="write"'
|
||||
|
||||
with anyio.fail_after(5):
|
||||
async with connect_with_oauth(
|
||||
server,
|
||||
provider=provider,
|
||||
storage=storage,
|
||||
settings=settings,
|
||||
app_shim=step_up_shim(challenge),
|
||||
) as (client, headless):
|
||||
await client.list_tools()
|
||||
|
||||
assert len(headless.authorize_urls) == 2
|
||||
assert authorize_params(headless.authorize_urls[0])["scope"] == "mcp"
|
||||
assert authorize_params(headless.authorize_urls[1])["scope"] == "mcp write"
|
||||
|
||||
|
||||
@requirement("client-auth:401-after-auth-throws")
|
||||
async def test_a_second_401_after_a_completed_oauth_flow_surfaces_without_looping() -> None:
|
||||
"""A 401 on the post-auth retry surfaces as an error rather than re-entering discovery.
|
||||
|
|
|
|||
Loading…
Reference in a new issue