feat: give every MCP client its own token, rate limit and log name
This commit is contained in:
1 parent
3af41c06ab
commit
bfdedefae3
8 files changed
+134
-33
No files matched your search
@@ -5,27 +5,66 @@ declare(strict_types=1);
|
||||
namespace App\Tests\Security;
|
||||
|
||||
use App\Security\McpAccessTokenHandler;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
|
||||
class McpAccessTokenHandlerTest extends TestCase
|
||||
{
|
||||
public function testTheConfiguredTokenAuthenticatesTheN8nUser(): void
|
||||
public function testEachTokenAuthenticatesItsOwnClient(): void
|
||||
{
|
||||
self::assertSame(McpAccessTokenHandler::USER, (new McpAccessTokenHandler('s3cret'))->getUserBadgeFrom('s3cret')->getUserIdentifier());
|
||||
$handler = new McpAccessTokenHandler('n8n:token-a, claude-desktop:token-b');
|
||||
|
||||
$badge = $handler->getUserBadgeFrom('token-b');
|
||||
|
||||
self::assertSame('claude-desktop', $badge->getUserIdentifier());
|
||||
self::assertSame(['ROLE_CHATBOT_MCP'], $badge->getUser()->getRoles());
|
||||
self::assertSame('n8n', $handler->getUserBadgeFrom('token-a')->getUserIdentifier());
|
||||
}
|
||||
|
||||
public function testAWrongTokenIsRejected(): void
|
||||
public function testATokenMayContainColons(): void
|
||||
{
|
||||
self::assertSame('n8n', (new McpAccessTokenHandler('n8n:a:b:c'))->getUserBadgeFrom('a:b:c')->getUserIdentifier());
|
||||
}
|
||||
|
||||
public function testAnUnknownTokenIsRejected(): void
|
||||
{
|
||||
$this->expectException(BadCredentialsException::class);
|
||||
|
||||
(new McpAccessTokenHandler('s3cret'))->getUserBadgeFrom('guess');
|
||||
(new McpAccessTokenHandler('n8n:token-a'))->getUserBadgeFrom('token-b');
|
||||
}
|
||||
|
||||
public function testAnUnconfiguredTokenRejectsEverything(): void
|
||||
public function testAClientNameIsNotAToken(): void
|
||||
{
|
||||
$this->expectException(BadCredentialsException::class);
|
||||
|
||||
(new McpAccessTokenHandler(''))->getUserBadgeFrom('');
|
||||
(new McpAccessTokenHandler('n8n:token-a'))->getUserBadgeFrom('n8n');
|
||||
}
|
||||
|
||||
public function testAnEmptyListRejectsEverything(): void
|
||||
{
|
||||
$this->expectException(BadCredentialsException::class);
|
||||
|
||||
(new McpAccessTokenHandler(' '))->getUserBadgeFrom('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, array{string}>
|
||||
*/
|
||||
public static function malformedLists(): iterable
|
||||
{
|
||||
yield 'token without name' => ['token-a'];
|
||||
yield 'empty token' => ['n8n:'];
|
||||
yield 'invalid name' => ['n 8 n:token-a'];
|
||||
yield 'duplicate name' => ['n8n:token-a,n8n:token-b'];
|
||||
yield 'shared token' => ['n8n:token-a,claude-desktop:token-a'];
|
||||
}
|
||||
|
||||
#[DataProvider('malformedLists')]
|
||||
public function testAMalformedListFailsLoudly(string $tokens): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
new McpAccessTokenHandler($tokens);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user