diff --git a/src/Plugin.php b/src/Plugin.php index eeb8a72..c1fad1e 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -29,6 +29,7 @@ use LIT\MCP\Integrations\WordPress\Schema\ContentSchema; use LIT\MCP\Integrations\WordPress\Schema\RegisteredMetaSource; use LIT\MCP\Integrations\WordPress\Schema\SampledMetaSource; use LIT\MCP\Integrations\WordPress\WordPressIntegration; +use LIT\MCP\Server\DiscoveryCanonical; use LIT\MCP\Server\RequestContext; use LIT\MCP\Server\ServerRegistrar; use LIT\MCP\Server\VisibilityFilter; @@ -57,6 +58,7 @@ final class Plugin $access->register(); (new RequestContext())->register(); (new VisibilityFilter())->register(); + (new DiscoveryCanonical())->register(); $limits = new Limits(); $changeLog = new ChangeLog(); diff --git a/src/Server/DiscoveryCanonical.php b/src/Server/DiscoveryCanonical.php new file mode 100644 index 0000000..13c9f0e --- /dev/null +++ b/src/Server/DiscoveryCanonical.php @@ -0,0 +1,29 @@ + 401 with WWW-Authenticate UNAUTH=$(curl -s --max-time 20 -i -X POST "$ENDPOINT" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}') diff --git a/tests/Unit/Server/DiscoveryCanonicalTest.php b/tests/Unit/Server/DiscoveryCanonicalTest.php new file mode 100644 index 0000000..a5b4d2a --- /dev/null +++ b/tests/Unit/Server/DiscoveryCanonicalTest.php @@ -0,0 +1,46 @@ +alias('parse_url'); + } + + /** @return array */ + public static function discoveryUrls(): array + { + return [ + 'protected resource' => ['https://example.test/.well-known/oauth-protected-resource'], + 'authorization server' => ['https://example.test/.well-known/oauth-authorization-server'], + 'subdirectory install' => ['https://example.test/wp/.well-known/oauth-protected-resource'], + ]; + } + + /** @dataProvider discoveryUrls */ + public function testDiscoveryDocumentsAreNeverRedirected(string $requested): void + { + self::assertFalse((new DiscoveryCanonical())->filter($requested . '/', $requested)); + } + + public function testOtherRedirectsStayUntouched(): void + { + $filter = new DiscoveryCanonical(); + + $page = 'https://example.test/seite'; + $acme = 'https://example.test/.well-known/acme-challenge/x'; + + self::assertSame($page . '/', $filter->filter($page . '/', $page)); + self::assertSame($acme . '/', $filter->filter($acme . '/', $acme)); + self::assertFalse($filter->filter(false, 'https://example.test/.well-known/oauth-authorization-server')); + } +}