Additive-only — no existing files modified. - McpApiKeyAuth: validates Bearer or X-MCP-API-Key header, attaches workspace context - CheckMcpQuota: consumes via McpQuotaService, exposes MCP quota headers - ValidateWorkspaceContext: normalises + enforces authenticated workspace scope - ValidateToolDependencies: JSON-RPC + flat tool-call payload validation via ToolDependencyService - McpAuthenticate: combined auth gate chaining the full stack Pest Feature tests _Good/_Bad/_Ugly per AX-10 for each middleware. pest skipped (vendor binaries missing in sandbox). Co-authored-by: Codex <noreply@openai.com> Closes tasks.lthn.sh/view.php?id=852
62 lines
2.6 KiB
PHP
62 lines
2.6 KiB
PHP
<?php
|
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Core\Mod\Agentic\Services\AgentApiKeyService;
|
|
use Core\Mod\Agentic\Website\Mcp\Middleware\McpApiKeyAuth;
|
|
use Core\Tenant\Models\Workspace;
|
|
use Illuminate\Http\Request;
|
|
|
|
test('McpApiKeyAuth_handle_Good_attaches_workspace_context_for_a_valid_mcp_api_key', function (): void {
|
|
$workspace = createWorkspace();
|
|
$apiKey = createApiKey($workspace, 'MCP Key');
|
|
$middleware = new McpApiKeyAuth(app(AgentApiKeyService::class));
|
|
|
|
$request = Request::create('/api/v1/mcp/tools/call', 'POST');
|
|
$request->headers->set('Authorization', 'Bearer '.$apiKey->plainTextKey);
|
|
|
|
$capturedContext = null;
|
|
$response = $middleware->handle($request, function (Request $authenticatedRequest) use (&$capturedContext) {
|
|
$capturedContext = $authenticatedRequest->attributes->get('mcp_workspace_context');
|
|
|
|
return response()->json([
|
|
'workspace_id' => $authenticatedRequest->attributes->get('workspace_id'),
|
|
]);
|
|
});
|
|
|
|
$data = json_decode((string) $response->getContent(), true);
|
|
|
|
expect($response->getStatusCode())->toBe(200)
|
|
->and($capturedContext)->toBeArray()
|
|
->and($capturedContext['workspace_id'])->toBe($workspace->id)
|
|
->and($data['workspace_id'])->toBe($workspace->id)
|
|
->and($response->headers->get('X-MCP-Workspace-ID'))->toBe((string) $workspace->id);
|
|
});
|
|
|
|
test('McpApiKeyAuth_handle_Bad_rejects_requests_without_an_mcp_api_key', function (): void {
|
|
$middleware = new McpApiKeyAuth(app(AgentApiKeyService::class));
|
|
$request = Request::create('/api/v1/mcp/tools/call', 'POST');
|
|
|
|
$response = $middleware->handle($request, fn () => response()->json(['success' => true]));
|
|
$data = json_decode((string) $response->getContent(), true);
|
|
|
|
expect($response->getStatusCode())->toBe(401)
|
|
->and($data['error'])->toBe('unauthorised');
|
|
});
|
|
|
|
test('McpApiKeyAuth_handle_Ugly_blocks_api_keys_for_inactive_workspaces', function (): void {
|
|
$workspace = Workspace::factory()->inactive()->create();
|
|
$apiKey = createApiKey($workspace, 'Inactive Workspace Key');
|
|
$middleware = new McpApiKeyAuth(app(AgentApiKeyService::class));
|
|
|
|
$request = Request::create('/api/v1/mcp/tools/call', 'POST');
|
|
$request->headers->set('X-MCP-API-Key', (string) $apiKey->plainTextKey);
|
|
|
|
$response = $middleware->handle($request, fn () => response()->json(['success' => true]));
|
|
$data = json_decode((string) $response->getContent(), true);
|
|
|
|
expect($response->getStatusCode())->toBe(403)
|
|
->and($data['error'])->toBe('workspace_inactive');
|
|
});
|