| @@ -110,8 +110,18 @@ | ||
| 110 | 110 | $result['message'] = __( 'No connection token exists yet. Click Connect to mint one, then run the test again.', 'thinkrank' ); |
| 111 | 111 | return $result; |
| 112 | 112 | } |
| 113 | 113 | |
| 114 | + if ( Mcp_Pairing::state()['token_sealed'] ) { | |
| 115 | + // A token exists and still authenticates the clients holding it, | |
| 116 | + // but this site can no longer decrypt it, so there is nothing to | |
| 117 | + // present. Probing with '' would report an authentication failure | |
| 118 | + // and point support at entirely the wrong thing. | |
| 119 | + $result['stage'] = 'token_sealed'; | |
| 120 | + $result['message'] = __( 'A connection token exists but can no longer be read on this site — the security keys in wp-config.php changed after it was minted. Clients already set up with it keep working. Use Reset token to mint one this site can show, then run the test again.', 'thinkrank' ); | |
| 121 | + return $result; | |
| 122 | + } | |
| 123 | + | |
| 114 | 124 | $token = Mcp_Pairing::site_token(); |
| 115 | 125 | |
| 116 | 126 | $pretty = self::probe_jsonrpc( $endpoint, $token ); |
| 117 | 127 | $rest = self::probe_jsonrpc( $fallback, $token ); |
| @@ -345,8 +355,41 @@ | ||
| 345 | 355 | * @return array{stage:string,detail:string} |
| 346 | 356 | */ |
| 347 | 357 | private static function probe_discovery(): array { |
| 348 | 358 | $documents = []; |
| 359 | + | |
| 360 | + // --- Published files vs. the identity this site has NOW ----------- | |
| 361 | + // The static /.well-known/ documents embed absolute home_url()-derived | |
| 362 | + // identifiers, and the whole reason they exist is that the host serves | |
| 363 | + // them before WordPress. After a domain change, an http->https switch | |
| 364 | + // or a staging clone, the stale copy therefore wins over the correct | |
| 365 | + // dynamic route and the site advertises an issuer it no longer owns, | |
| 366 | + // which a spec-compliant client must refuse (#486). | |
| 367 | + // | |
| 368 | + // Checked on disk, ahead of the HTTP probes below, because loopback | |
| 369 | + // does not always take the path an external client does — a site can | |
| 370 | + // serve a stale document to the internet while our own request never | |
| 371 | + // sees it, and every probe below then passes. | |
| 372 | + $stale = Mcp_Static_Discovery::stale_document(); | |
| 373 | + if ( null !== $stale ) { | |
| 374 | + Mcp_Static_Discovery::refresh(); | |
| 375 | + $still_stale = Mcp_Static_Discovery::stale_document(); | |
| 376 | + | |
| 377 | + if ( null !== $still_stale ) { | |
| 378 | + return [ | |
| 379 | + 'stage' => 'stale_static_discovery', | |
| 380 | + 'documents' => $documents, | |
| 381 | + 'detail' => sprintf( | |
| 382 | + /* translators: 1: file path relative to the site root, 2: identifier name, 3: value found in the file, 4: value it should carry. */ | |
| 383 | + __( 'The static discovery file %1$s advertises %2$s as %3$s, but this site is %4$s. It was written before the site URL changed, the host serves it ahead of WordPress, and it could not be rewritten or removed — so clients read the old identity and refuse to connect. Delete that file from the site root, or restore write access there and run this test again.', 'thinkrank' ), | |
| 384 | + $still_stale['file'], | |
| 385 | + $still_stale['key'], | |
| 386 | + '' === $still_stale['found'] ? __( 'nothing', 'thinkrank' ) : $still_stale['found'], | |
| 387 | + $still_stale['expected'] | |
| 388 | + ), | |
| 389 | + ]; | |
| 390 | + } | |
| 391 | + } | |
| 349 | 392 | |
| 350 | 393 | // --- The documents clients are POINTED at (must work) ------------- |
| 351 | 394 | // The 401 challenge advertises the REST-served resource metadata, and |
| 352 | 395 | // spec-compliant clients derive the OIDC-suffix form of the AS |