wip: doctor/cli/docs/api to vector db consolidation; api hardening for descriptions, tenant, and scopes; migrations and conversions of all DALs to EF v10

This commit is contained in:
master
2026-02-23 15:30:50 +02:00
parent bd8fee6ed8
commit e746577380
1424 changed files with 81225 additions and 25251 deletions

View File

@@ -124,7 +124,7 @@ public sealed class IdentityHeaderPolicyMiddlewareTests
#region Header Overwriting (Not Set-If-Missing)
[Fact]
public async Task InvokeAsync_OverwritesSpoofedTenantWithClaimValue()
public async Task InvokeAsync_RejectsSpoofedTenantHeaderWhenOverrideDisabled()
{
var middleware = CreateMiddleware();
var claims = new[]
@@ -133,15 +133,15 @@ public sealed class IdentityHeaderPolicyMiddlewareTests
new Claim(StellaOpsClaimTypes.Subject, "real-subject")
};
var context = CreateHttpContext("/api/scan", claims);
context.Response.Body = new MemoryStream();
// Client attempts to spoof tenant
context.Request.Headers["X-StellaOps-Tenant"] = "spoofed-tenant";
await middleware.InvokeAsync(context);
Assert.True(_nextCalled);
// Header should contain claim value, not spoofed value
Assert.Equal("real-tenant", context.Request.Headers["X-StellaOps-Tenant"].ToString());
Assert.False(_nextCalled);
Assert.Equal(StatusCodes.Status403Forbidden, context.Response.StatusCode);
}
[Fact]
@@ -225,6 +225,7 @@ public sealed class IdentityHeaderPolicyMiddlewareTests
Assert.True(_nextCalled);
Assert.Equal("tenant-abc", context.Request.Headers["X-StellaOps-Tenant"].ToString());
Assert.Equal("tenant-abc", context.Request.Headers["X-Tenant-Id"].ToString());
Assert.Equal("tenant-abc", context.Items[GatewayContextKeys.TenantId]);
}
@@ -243,6 +244,25 @@ public sealed class IdentityHeaderPolicyMiddlewareTests
Assert.True(_nextCalled);
Assert.Equal("legacy-tenant-456", context.Request.Headers["X-StellaOps-Tenant"].ToString());
Assert.Equal("legacy-tenant-456", context.Request.Headers["X-Tenant-Id"].ToString());
}
[Fact]
public async Task InvokeAsync_AuthenticatedRequestWithoutTenantClaim_DoesNotWriteTenantHeaders()
{
var middleware = CreateMiddleware();
var claims = new[]
{
new Claim(StellaOpsClaimTypes.Subject, "user")
};
var context = CreateHttpContext("/api/scan", claims);
await middleware.InvokeAsync(context);
Assert.True(_nextCalled);
Assert.DoesNotContain("X-StellaOps-Tenant", context.Request.Headers.Keys);
Assert.DoesNotContain("X-Stella-Tenant", context.Request.Headers.Keys);
Assert.DoesNotContain("X-Tenant-Id", context.Request.Headers.Keys);
}
[Fact]
@@ -308,6 +328,109 @@ public sealed class IdentityHeaderPolicyMiddlewareTests
#endregion
#region Tenant Override
[Fact]
public async Task InvokeAsync_OverrideEnabledAndAllowed_UsesRequestedTenant()
{
_options.EnableTenantOverride = true;
var middleware = CreateMiddleware();
var claims = new[]
{
new Claim(StellaOpsClaimTypes.Subject, "user"),
new Claim(StellaOpsClaimTypes.Tenant, "tenant-a"),
new Claim(StellaOpsClaimTypes.AllowedTenants, "tenant-a tenant-b")
};
var context = CreateHttpContext("/api/platform", claims);
context.Request.Headers["X-StellaOps-Tenant"] = "TENANT-B";
await middleware.InvokeAsync(context);
Assert.True(_nextCalled);
Assert.Equal("tenant-b", context.Request.Headers["X-StellaOps-Tenant"].ToString());
Assert.Equal("tenant-b", context.Request.Headers["X-Tenant-Id"].ToString());
Assert.Equal("tenant-b", context.Items[GatewayContextKeys.TenantId]);
}
[Fact]
public async Task InvokeAsync_OverrideEnabledButNotAllowed_ReturnsForbidden()
{
_options.EnableTenantOverride = true;
var middleware = CreateMiddleware();
var claims = new[]
{
new Claim(StellaOpsClaimTypes.Subject, "user"),
new Claim(StellaOpsClaimTypes.Tenant, "tenant-a"),
new Claim(StellaOpsClaimTypes.AllowedTenants, "tenant-a tenant-c")
};
var context = CreateHttpContext("/api/platform", claims);
context.Response.Body = new MemoryStream();
context.Request.Headers["X-StellaOps-Tenant"] = "tenant-b";
await middleware.InvokeAsync(context);
Assert.False(_nextCalled);
Assert.Equal(StatusCodes.Status403Forbidden, context.Response.StatusCode);
}
#endregion
#region Auth Header Passthrough
[Fact]
public async Task InvokeAsync_PreservesAuthorizationHeadersForApprovedConfiguredPrefix()
{
_options.JwtPassthroughPrefixes = ["/connect"];
_options.ApprovedAuthPassthroughPrefixes = ["/connect", "/console"];
var middleware = CreateMiddleware();
var context = CreateHttpContext("/connect/token");
context.Request.Headers.Authorization = "Bearer token-value";
context.Request.Headers["DPoP"] = "proof-value";
await middleware.InvokeAsync(context);
Assert.True(_nextCalled);
Assert.Equal("Bearer token-value", context.Request.Headers.Authorization.ToString());
Assert.Equal("proof-value", context.Request.Headers["DPoP"].ToString());
}
[Fact]
public async Task InvokeAsync_StripsAuthorizationHeadersWhenConfiguredPrefixIsNotApproved()
{
_options.JwtPassthroughPrefixes = ["/api/v1/authority"];
_options.ApprovedAuthPassthroughPrefixes = ["/connect"];
var middleware = CreateMiddleware();
var context = CreateHttpContext("/api/v1/authority/clients");
context.Request.Headers.Authorization = "Bearer token-value";
context.Request.Headers["DPoP"] = "proof-value";
await middleware.InvokeAsync(context);
Assert.True(_nextCalled);
Assert.False(context.Request.Headers.ContainsKey("Authorization"));
Assert.False(context.Request.Headers.ContainsKey("DPoP"));
}
[Fact]
public async Task InvokeAsync_StripsAuthorizationHeadersWhenPrefixIsNotConfigured()
{
_options.JwtPassthroughPrefixes = [];
_options.ApprovedAuthPassthroughPrefixes = ["/connect"];
var middleware = CreateMiddleware();
var context = CreateHttpContext("/connect/token");
context.Request.Headers.Authorization = "Bearer token-value";
context.Request.Headers["DPoP"] = "proof-value";
await middleware.InvokeAsync(context);
Assert.True(_nextCalled);
Assert.False(context.Request.Headers.ContainsKey("Authorization"));
Assert.False(context.Request.Headers.ContainsKey("DPoP"));
}
#endregion
#region Legacy Header Compatibility
[Fact]

View File

@@ -464,7 +464,7 @@ public sealed class OpenApiDocumentGeneratorTests
}
[Fact]
public void GenerateDocument_WithoutExplicitDescription_UsesSummaryAsDescriptionFallback()
public void GenerateDocument_WithoutExplicitDescription_LeavesDescriptionUnset()
{
var endpoint = new EndpointDescriptor
{
@@ -485,7 +485,37 @@ public sealed class OpenApiDocumentGeneratorTests
var operation = document["paths"]!["/api/v1/timeline"]!["get"]!.AsObject();
operation["summary"]!.GetValue<string>().Should().Be("GET /api/v1/timeline");
operation["description"]!.GetValue<string>().Should().Be("GET /api/v1/timeline");
operation["description"].Should().BeNull();
}
[Fact]
public void GenerateDocument_WithSchemaDescription_PreservesEndpointDescription()
{
var endpoint = new EndpointDescriptor
{
ServiceName = "timelineindexer",
Version = "1.0.0",
Method = "GET",
Path = "/api/v1/timeline",
SchemaInfo = new EndpointSchemaInfo
{
Summary = "Get timeline",
Description = "Return the timeline entries in reverse chronological order."
}
};
var routingState = new Mock<IGlobalRoutingState>();
routingState.Setup(state => state.GetAllConnections()).Returns([CreateConnection("timelineindexer", endpoint)]);
var generator = new OpenApiDocumentGenerator(
routingState.Object,
Options.Create(new OpenApiAggregationOptions()));
var document = JsonNode.Parse(generator.GenerateDocument())!.AsObject();
var operation = document["paths"]!["/api/v1/timeline"]!["get"]!.AsObject();
operation["summary"]!.GetValue<string>().Should().Be("Get timeline");
operation["description"]!.GetValue<string>().Should().Be("Return the timeline entries in reverse chronological order.");
}
private static ConnectionState CreateConnection(