@@ -2936,16 +2936,39 @@ async def test_issuer_binding_re_evaluated_after_asm_when_prm_discovery_failed(
29362936
29372937
29382938@pytest .mark .anyio
2939- async def test_issuer_is_not_stamped_when_registration_falls_back_after_asm_discovery_fails (
2940- oauth_provider : OAuthClientProvider , mock_storage : MockTokenStorage
2939+ @pytest .mark .parametrize (
2940+ "asm_responses" ,
2941+ [
2942+ pytest .param (
2943+ [httpx .Response (404 ), httpx .Response (404 )],
2944+ id = "asm-discovery-failed" ,
2945+ ),
2946+ pytest .param (
2947+ [
2948+ httpx .Response (
2949+ 200 ,
2950+ content = (
2951+ b'{"issuer": "https://new-as.example.com", '
2952+ b'"authorization_endpoint": "https://new-as.example.com/authorize", '
2953+ b'"token_endpoint": "https://new-as.example.com/token"}'
2954+ ),
2955+ )
2956+ ],
2957+ id = "asm-metadata-without-registration-endpoint" ,
2958+ ),
2959+ ],
2960+ )
2961+ async def test_issuer_is_not_stamped_when_registration_falls_back_to_the_resource_origin (
2962+ oauth_provider : OAuthClientProvider , mock_storage : MockTokenStorage , asm_responses : list [httpx .Response ]
29412963):
2942- """SEP-2352: a fallback registration is not recorded as bound to an undiscovered AS.
2964+ """SEP-2352: a fallback registration is not recorded as bound to the PRM-advertised AS.
29432965
29442966 PRM advertises a new authorization server, so the stored credentials (bound to the old
2945- issuer) are discarded. ASM discovery for the new server then fails, so DCR falls back to
2946- the resource-server origin's ``/register``. That registration was not derived from the new
2947- AS's metadata, so persisting it as bound to the new AS would wedge the binding check on
2948- later flows; instead the issuer is left unset.
2967+ issuer) are discarded. DCR then falls back to the resource-server origin's ``/register``
2968+ because the new AS's metadata either could not be discovered or omits
2969+ ``registration_endpoint``. That registration was not derived from the new AS's metadata,
2970+ so persisting it as bound to the new AS would wedge the binding check on later flows;
2971+ instead the issuer is left unset.
29492972 """
29502973 oauth_provider .context .current_tokens = None
29512974 oauth_provider .context .token_expiry_time = None
@@ -2991,16 +3014,18 @@ async def echo_callback() -> AuthorizationCodeResult:
29913014 request = prm_req ,
29923015 )
29933016
2994- # ASM discovery for the new AS fails on every well-known URL.
2995- asm_req = await auth_flow .asend (prm_response )
3017+ # ASM discovery for the new AS yields no usable registration_endpoint — either every
3018+ # well-known URL 404s, or metadata is returned without one.
3019+ next_req = await auth_flow .asend (prm_response )
29963020 assert oauth_provider .context .client_info is None
29973021 assert oauth_provider .context .oauth_metadata is None
2998- assert str (asm_req .url ) == "https://new-as.example.com/.well-known/oauth-authorization-server"
2999- asm_req = await auth_flow .asend (httpx .Response (404 , request = asm_req ))
3000- assert str (asm_req .url ) == "https://new-as.example.com/.well-known/openid-configuration"
3022+ assert str (next_req .url ) == "https://new-as.example.com/.well-known/oauth-authorization-server"
3023+ for asm_response in asm_responses :
3024+ asm_response .request = next_req
3025+ next_req = await auth_flow .asend (asm_response )
30013026
30023027 # Step 4 falls back to the resource-server origin's /register.
3003- dcr_req = await auth_flow . asend ( httpx . Response ( 404 , request = asm_req ))
3028+ dcr_req = next_req
30043029 assert dcr_req .method == "POST"
30053030 assert str (dcr_req .url ) == "https://api.example.com/register"
30063031 dcr_response = httpx .Response (
0 commit comments