Files
ColaFlow/colaflow-api/tests/Modules/Mcp/ColaFlow.Modules.Mcp.Tests/Domain/ApiKeyPermissionsTests.cs
Yaojia Wang 0857a8ba2a feat(backend): Implement MCP API Key Management System (Story 5.2)
Implemented comprehensive API Key authentication and management system
for MCP Server to ensure only authorized AI agents can access ColaFlow.

## Domain Layer
- Created McpApiKey aggregate root with BCrypt password hashing
- Implemented ApiKeyPermissions value object (read/write, resource/tool filtering)
- Added ApiKeyStatus enum (Active, Revoked)
- Created domain events (ApiKeyCreatedEvent, ApiKeyRevokedEvent)
- API key format: cola_<36 random chars> (cryptographically secure)
- Default expiration: 90 days

## Application Layer
- Implemented McpApiKeyService with full CRUD operations
- Created DTOs for API key creation, validation, and updates
- Validation logic: hash verification, expiration check, IP whitelist
- Usage tracking: last_used_at, usage_count

## Infrastructure Layer
- Created McpDbContext with PostgreSQL configuration
- EF Core entity configuration with JSONB for permissions/IP whitelist
- Implemented McpApiKeyRepository with prefix-based lookup
- Database migration: mcp_api_keys table with indexes
- Created McpApiKeyAuthenticationMiddleware for API key validation
- Middleware validates Authorization: Bearer <api_key> header

## API Layer
- Created McpApiKeysController with REST endpoints:
  - POST /api/mcp/keys - Create API Key (returns plain key once!)
  - GET /api/mcp/keys - List tenant's API Keys
  - GET /api/mcp/keys/{id} - Get API Key details
  - PATCH /api/mcp/keys/{id}/metadata - Update name/description
  - PATCH /api/mcp/keys/{id}/permissions - Update permissions
  - DELETE /api/mcp/keys/{id} - Revoke API Key
- Requires JWT authentication (not API key auth)

## Testing
- Created 17 unit tests for McpApiKey entity
- Created 7 unit tests for ApiKeyPermissions value object
- All 49 tests passing (including existing MCP tests)
- Test coverage > 80% for Domain layer

## Security Features
- BCrypt hashing with work factor 12
- API key shown only once at creation (never logged)
- Key prefix lookup for fast validation (indexed)
- Multi-tenant isolation (tenant_id filter)
- IP whitelist support
- Permission scopes (read/write, resources, tools)
- Automatic expiration after 90 days

## Database Schema
Table: mcp.mcp_api_keys
- Indexes: key_prefix (unique), tenant_id, tenant_user, expires_at, status
- JSONB columns for permissions and IP whitelist
- Soft delete via revoked_at

## Integration
- Updated Program.cs to register MCP module with configuration
- Added MCP DbContext migration in development mode
- Authentication middleware runs before MCP protocol handler

Changes:
- Created 31 new files (2321+ lines)
- Domain: 6 files (McpApiKey, events, repository, value objects)
- Application: 9 files (service, DTOs)
- Infrastructure: 8 files (DbContext, repository, middleware, migration)
- API: 1 file (McpApiKeysController)
- Tests: 2 files (17 + 7 unit tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 18:40:56 +01:00

103 lines
3.2 KiB
C#

using ColaFlow.Modules.Mcp.Domain.ValueObjects;
using FluentAssertions;
using Xunit;
namespace ColaFlow.Modules.Mcp.Tests.Domain;
public class ApiKeyPermissionsTests
{
[Fact]
public void ReadOnly_ShouldCreateReadOnlyPermissions()
{
// Act
var permissions = ApiKeyPermissions.ReadOnly();
// Assert
permissions.Read.Should().BeTrue();
permissions.Write.Should().BeFalse();
permissions.AllowedResources.Should().BeEmpty();
permissions.AllowedTools.Should().BeEmpty();
}
[Fact]
public void ReadWrite_ShouldCreateReadWritePermissions()
{
// Act
var permissions = ApiKeyPermissions.ReadWrite();
// Assert
permissions.Read.Should().BeTrue();
permissions.Write.Should().BeTrue();
permissions.AllowedResources.Should().BeEmpty();
permissions.AllowedTools.Should().BeEmpty();
}
[Fact]
public void Custom_ShouldCreateCustomPermissions()
{
// Arrange
var allowedResources = new List<string> { "project://123" };
var allowedTools = new List<string> { "create_task" };
// Act
var permissions = ApiKeyPermissions.Custom(true, true, allowedResources, allowedTools);
// Assert
permissions.Read.Should().BeTrue();
permissions.Write.Should().BeTrue();
permissions.AllowedResources.Should().BeEquivalentTo(allowedResources);
permissions.AllowedTools.Should().BeEquivalentTo(allowedTools);
}
[Fact]
public void CanAccessResource_WithNoRestrictions_ShouldReturnTrue()
{
// Arrange
var permissions = ApiKeyPermissions.ReadOnly();
// Act
var result = permissions.CanAccessResource("project://123");
// Assert
result.Should().BeTrue();
}
[Fact]
public void CanAccessResource_WithRestrictions_ShouldValidateCorrectly()
{
// Arrange
var allowedResources = new List<string> { "project://123", "epic://456" };
var permissions = ApiKeyPermissions.Custom(true, false, allowedResources);
// Act & Assert
permissions.CanAccessResource("project://123").Should().BeTrue();
permissions.CanAccessResource("epic://456").Should().BeTrue();
permissions.CanAccessResource("task://789").Should().BeFalse();
}
[Fact]
public void CanUseTool_WithNoRestrictions_ShouldReturnWritePermission()
{
// Arrange
var readOnlyPermissions = ApiKeyPermissions.ReadOnly();
var readWritePermissions = ApiKeyPermissions.ReadWrite();
// Act & Assert
readOnlyPermissions.CanUseTool("create_task").Should().BeFalse();
readWritePermissions.CanUseTool("create_task").Should().BeTrue();
}
[Fact]
public void CanUseTool_WithRestrictions_ShouldValidateCorrectly()
{
// Arrange
var allowedTools = new List<string> { "create_task", "update_story" };
var permissions = ApiKeyPermissions.Custom(true, true, allowedTools: allowedTools);
// Act & Assert
permissions.CanUseTool("create_task").Should().BeTrue();
permissions.CanUseTool("update_story").Should().BeTrue();
permissions.CanUseTool("delete_project").Should().BeFalse();
}
}