diff --git a/readme.txt b/readme.txt index fe31e9e..39c2b3d 100644 --- a/readme.txt +++ b/readme.txt @@ -32,7 +32,9 @@ Einstellungen: Einstellungen → LIT MCP (Verbindung, Zugriff, Tools, Protokoll, * Jede Verbindung ist ein Anwendungspasswort des Benutzers (Name = Client, z. B. „Claude“). Es ist im Benutzerprofil sichtbar und unter Einstellungen → LIT MCP → Zugriff widerrufbar. * Die Capability lit_mcp_use wird zur Laufzeit an die freigegebenen Rollen vergeben; Rollen in der - Datenbank werden nicht verändert. Nicht freigegebene Benutzer sehen beim Verbinden weder die Werkzeugliste + Datenbank werden nicht verändert. Nicht freigegebene Benutzer können keine Verbindung herstellen: die + Anmeldung beim Verbinden endet mit „Dein Benutzer hat keinen Zugriff auf LIT MCP.“, es entsteht kein + Anwendungspasswort. Verlieren bereits verbundene Benutzer die Freigabe, sehen sie weder die Werkzeugliste noch Angaben zur Website – nur den Hinweis, dass ihr Konto nicht freigegeben ist. * Welche Werkzeuge direkt sichtbar, nur über die Meta-Werkzeuge erreichbar oder abgeschaltet sind, lässt sich unter Einstellungen → LIT MCP → Tools ändern. diff --git a/src/Plugin.php b/src/Plugin.php index d695b0c..e183a7c 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -30,6 +30,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\AuthorizeGate; use LIT\MCP\Server\DiscoveryCanonical; use LIT\MCP\Server\RequestContext; use LIT\MCP\Server\ServerRegistrar; @@ -60,6 +61,7 @@ final class Plugin (new RequestContext())->register(); (new VisibilityFilter())->register(); (new DiscoveryCanonical())->register(); + (new AuthorizeGate())->register(); $limits = new Limits(); $changeLog = new ChangeLog(); diff --git a/src/Server/AuthorizeGate.php b/src/Server/AuthorizeGate.php new file mode 100644 index 0000000..fd6208e --- /dev/null +++ b/src/Server/AuthorizeGate.php @@ -0,0 +1,43 @@ + 403] + ); + } +} diff --git a/tests/Unit/Server/AuthorizeGateTest.php b/tests/Unit/Server/AuthorizeGateTest.php new file mode 100644 index 0000000..9186696 --- /dev/null +++ b/tests/Unit/Server/AuthorizeGateTest.php @@ -0,0 +1,79 @@ +alias( + static fn (string $var, $default = '') => $var === 'mcp_oauth_endpoint' ? $endpoint : $default + ); + Functions\when('is_user_logged_in')->justReturn($loggedIn); + Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap === Access::CAPABILITY && $allowed); + Functions\when('__')->returnArg(); + Functions\when('esc_html')->returnArg(); + } + + public function testRunsBeforeMcpOauthsOwnHandler(): void + { + Actions\expectAdded('template_redirect')->once()->with(Mockery::type('array'), 1); + + (new AuthorizeGate())->register(); + } + + /** @return array */ + public static function userFacingEndpoints(): array + { + return ['authorize' => ['authorize'], 'authorize-callback' => ['authorize-callback'], 'consent' => ['consent']]; + } + + /** @dataProvider userFacingEndpoints */ + public function testUsersWithoutAccessAreStoppedWithAClearMessage(string $endpoint): void + { + $this->given($endpoint, true, false); + Functions\expect('wp_die')->once()->with( + 'Dein Benutzer hat keinen Zugriff auf LIT MCP.', + Mockery::type('string'), + Mockery::on(static fn (array $args): bool => ($args['response'] ?? null) === 403) + ); + + (new AuthorizeGate())->check(); + } + + public function testAllowedUsersLoginAndOtherEndpointsPass(): void + { + Functions\expect('wp_die')->never(); + + $this->given('consent', true, true); + (new AuthorizeGate())->check(); + + $this->given('authorize', false, false); // mcp-oauth sends them to the login first + (new AuthorizeGate())->check(); + + foreach (['token', 'revoke', ''] as $endpoint) { // no browser session there + $this->given($endpoint, true, false); + (new AuthorizeGate())->check(); + } + } + + public function testQueryVarMatchesMcpOauth(): void + { + $gate = new \ReflectionClass(AuthorizeGate::class); + + self::assertSame(\WPMedia\MCP\OAuth\Auth\Rewrite::OAUTH_QUERY_VAR, $gate->getConstant('QUERY_VAR')); + } +}