UI work to fill SBOM sourcing management gap. UI planning remaining functionality exposure. Work on CI/Tests stabilization

Introduces CGS determinism test runs to CI workflows for Windows, macOS, Linux, Alpine, and Debian, fulfilling CGS-008 cross-platform requirements. Updates local-ci scripts to support new smoke steps, test timeouts, progress intervals, and project slicing for improved test isolation and diagnostics.
This commit is contained in:
master
2025-12-29 19:12:38 +02:00
parent 41552d26ec
commit a4badc275e
286 changed files with 50918 additions and 992 deletions

View File

@@ -1,8 +1,10 @@
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Auth.Abstractions;
using StellaOps.Scanner.Sources.Configuration;
using StellaOps.Scanner.Sources.Contracts;
using StellaOps.Scanner.Sources.Domain;
using StellaOps.Scanner.Sources.Services;
@@ -139,12 +141,10 @@ internal static class SourcesEndpoints
private static async Task<IResult> HandleListAsync(
[AsParameters] ListSourcesQueryParams queryParams,
ISbomSourceService sourceService,
ITenantContext tenantContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -169,12 +169,10 @@ internal static class SourcesEndpoints
private static async Task<IResult> HandleGetAsync(
Guid sourceId,
ISbomSourceService sourceService,
ITenantContext tenantContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -200,12 +198,10 @@ internal static class SourcesEndpoints
private static async Task<IResult> HandleGetByNameAsync(
string name,
ISbomSourceService sourceService,
ITenantContext tenantContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -231,14 +227,11 @@ internal static class SourcesEndpoints
private static async Task<IResult> HandleCreateAsync(
CreateSourceRequest request,
ISbomSourceService sourceService,
ITenantContext tenantContext,
IUserContext userContext,
LinkGenerator links,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -247,7 +240,7 @@ internal static class SourcesEndpoints
StatusCodes.Status400BadRequest);
}
var userId = userContext.UserId ?? "system";
var userId = ResolveActor(context);
try
{
@@ -289,13 +282,10 @@ internal static class SourcesEndpoints
Guid sourceId,
UpdateSourceRequest request,
ISbomSourceService sourceService,
ITenantContext tenantContext,
IUserContext userContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -304,7 +294,7 @@ internal static class SourcesEndpoints
StatusCodes.Status400BadRequest);
}
var userId = userContext.UserId ?? "system";
var userId = ResolveActor(context);
try
{
@@ -342,12 +332,10 @@ internal static class SourcesEndpoints
private static async Task<IResult> HandleDeleteAsync(
Guid sourceId,
ISbomSourceService sourceService,
ITenantContext tenantContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -374,12 +362,10 @@ internal static class SourcesEndpoints
private static async Task<IResult> HandleTestConnectionAsync(
Guid sourceId,
ISbomSourceService sourceService,
ITenantContext tenantContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -406,12 +392,10 @@ internal static class SourcesEndpoints
private static async Task<IResult> HandleTestNewConnectionAsync(
TestConnectionRequest request,
ISbomSourceService sourceService,
ITenantContext tenantContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -428,13 +412,10 @@ internal static class SourcesEndpoints
Guid sourceId,
PauseSourceRequest request,
ISbomSourceService sourceService,
ITenantContext tenantContext,
IUserContext userContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -443,7 +424,7 @@ internal static class SourcesEndpoints
StatusCodes.Status400BadRequest);
}
var userId = userContext.UserId ?? "system";
var userId = ResolveActor(context);
try
{
@@ -463,13 +444,10 @@ internal static class SourcesEndpoints
private static async Task<IResult> HandleResumeAsync(
Guid sourceId,
ISbomSourceService sourceService,
ITenantContext tenantContext,
IUserContext userContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -478,7 +456,7 @@ internal static class SourcesEndpoints
StatusCodes.Status400BadRequest);
}
var userId = userContext.UserId ?? "system";
var userId = ResolveActor(context);
try
{
@@ -498,13 +476,10 @@ internal static class SourcesEndpoints
private static async Task<IResult> HandleActivateAsync(
Guid sourceId,
ISbomSourceService sourceService,
ITenantContext tenantContext,
IUserContext userContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -513,7 +488,7 @@ internal static class SourcesEndpoints
StatusCodes.Status400BadRequest);
}
var userId = userContext.UserId ?? "system";
var userId = ResolveActor(context);
try
{
@@ -534,13 +509,10 @@ internal static class SourcesEndpoints
Guid sourceId,
TriggerScanRequest? request,
ISbomSourceService sourceService,
ITenantContext tenantContext,
IUserContext userContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -549,7 +521,7 @@ internal static class SourcesEndpoints
StatusCodes.Status400BadRequest);
}
var userId = userContext.UserId ?? "system";
var userId = ResolveActor(context);
try
{
@@ -579,12 +551,10 @@ internal static class SourcesEndpoints
Guid sourceId,
[AsParameters] ListRunsQueryParams queryParams,
ISbomSourceService sourceService,
ITenantContext tenantContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -620,12 +590,10 @@ internal static class SourcesEndpoints
Guid sourceId,
Guid runId,
ISbomSourceService sourceService,
ITenantContext tenantContext,
HttpContext context,
CancellationToken ct)
{
var tenantId = tenantContext.TenantId;
if (string.IsNullOrEmpty(tenantId))
if (!TryResolveTenant(context, out var tenantId))
{
return ProblemResultFactory.Create(
context,
@@ -688,6 +656,57 @@ internal static class SourcesEndpoints
_ => "Unknown source type"
};
private static bool TryResolveTenant(HttpContext context, out string tenantId)
{
tenantId = string.Empty;
var tenant = context.User?.FindFirstValue(StellaOpsClaimTypes.Tenant);
if (!string.IsNullOrWhiteSpace(tenant))
{
tenantId = tenant.Trim();
return true;
}
if (context.Request.Headers.TryGetValue("X-Stella-Tenant", out var headerTenant))
{
var headerValue = headerTenant.ToString();
if (!string.IsNullOrWhiteSpace(headerValue))
{
tenantId = headerValue.Trim();
return true;
}
}
if (context.Request.Headers.TryGetValue("X-Tenant-Id", out var legacyTenant))
{
var headerValue = legacyTenant.ToString();
if (!string.IsNullOrWhiteSpace(headerValue))
{
tenantId = headerValue.Trim();
return true;
}
}
return false;
}
private static string ResolveActor(HttpContext context)
{
var subject = context.User?.FindFirstValue(StellaOpsClaimTypes.Subject);
if (!string.IsNullOrWhiteSpace(subject))
{
return subject.Trim();
}
var clientId = context.User?.FindFirstValue(StellaOpsClaimTypes.ClientId);
if (!string.IsNullOrWhiteSpace(clientId))
{
return clientId.Trim();
}
return "system";
}
private static IResult Json<T>(T value, int statusCode)
{
var payload = JsonSerializer.Serialize(value, SerializerOptions);

View File

@@ -25,6 +25,10 @@ internal static class WebhookEndpoints
PropertyNameCaseInsensitive = true
};
private sealed class WebhookEndpointsLog
{
}
/// <summary>
/// Maps webhook endpoints for receiving push events.
/// </summary>
@@ -98,7 +102,7 @@ internal static class WebhookEndpoints
IEnumerable<ISourceTypeHandler> handlers,
ISourceTriggerDispatcher dispatcher,
ICredentialResolver credentialResolver,
ILogger<WebhookEndpoints> logger,
ILogger<WebhookEndpointsLog> logger,
HttpContext context,
CancellationToken ct)
{
@@ -140,7 +144,7 @@ internal static class WebhookEndpoints
logger.LogWarning("Webhook received without signature for source {SourceId}", sourceId);
return ProblemResultFactory.Create(
context,
ProblemTypes.Unauthorized,
ProblemTypes.Authentication,
"Missing webhook signature",
StatusCodes.Status401Unauthorized);
}
@@ -164,7 +168,7 @@ internal static class WebhookEndpoints
logger.LogWarning("Invalid webhook signature for source {SourceId}", sourceId);
return ProblemResultFactory.Create(
context,
ProblemTypes.Unauthorized,
ProblemTypes.Authentication,
"Invalid webhook signature",
StatusCodes.Status401Unauthorized);
}
@@ -252,7 +256,7 @@ internal static class WebhookEndpoints
IEnumerable<ISourceTypeHandler> handlers,
ISourceTriggerDispatcher dispatcher,
ICredentialResolver credentialResolver,
ILogger<WebhookEndpoints> logger,
ILogger<WebhookEndpointsLog> logger,
HttpContext context,
CancellationToken ct)
{
@@ -290,7 +294,7 @@ internal static class WebhookEndpoints
IEnumerable<ISourceTypeHandler> handlers,
ISourceTriggerDispatcher dispatcher,
ICredentialResolver credentialResolver,
ILogger<WebhookEndpoints> logger,
ILogger<WebhookEndpointsLog> logger,
HttpContext context,
CancellationToken ct)
{
@@ -337,7 +341,7 @@ internal static class WebhookEndpoints
IEnumerable<ISourceTypeHandler> handlers,
ISourceTriggerDispatcher dispatcher,
ICredentialResolver credentialResolver,
ILogger<WebhookEndpoints> logger,
ILogger<WebhookEndpointsLog> logger,
HttpContext context,
CancellationToken ct)
{
@@ -377,7 +381,7 @@ internal static class WebhookEndpoints
IEnumerable<ISourceTypeHandler> handlers,
ISourceTriggerDispatcher dispatcher,
ICredentialResolver credentialResolver,
ILogger<WebhookEndpoints> logger,
ILogger<WebhookEndpointsLog> logger,
HttpContext context,
CancellationToken ct)
{
@@ -420,7 +424,7 @@ internal static class WebhookEndpoints
IEnumerable<ISourceTypeHandler> handlers,
ISourceTriggerDispatcher dispatcher,
ICredentialResolver credentialResolver,
ILogger<WebhookEndpoints> logger,
ILogger<WebhookEndpointsLog> logger,
HttpContext context,
string signatureHeader,
CancellationToken ct)
@@ -459,7 +463,7 @@ internal static class WebhookEndpoints
logger.LogWarning("Webhook received without signature for source {SourceId}", source.SourceId);
return ProblemResultFactory.Create(
context,
ProblemTypes.Unauthorized,
ProblemTypes.Authentication,
"Missing webhook signature",
StatusCodes.Status401Unauthorized);
}
@@ -483,7 +487,7 @@ internal static class WebhookEndpoints
logger.LogWarning("Invalid webhook signature for source {SourceId}", source.SourceId);
return ProblemResultFactory.Create(
context,
ProblemTypes.Unauthorized,
ProblemTypes.Authentication,
"Invalid webhook signature",
StatusCodes.Status401Unauthorized);
}