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:
@@ -5,6 +5,7 @@ using StellaOps.Scanner.SmartDiff.Detection;
|
||||
using StellaOps.Scanner.SmartDiff.Output;
|
||||
using StellaOps.Scanner.WebService.Security;
|
||||
using StellaOps.Scanner.WebService.Services;
|
||||
using StellaOps.Scanner.WebService.Tenancy;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Endpoints;
|
||||
@@ -87,10 +88,16 @@ internal static class SmartDiffEndpoints
|
||||
IVexCandidateStore candidateStore,
|
||||
IScanMetadataRepository? metadataRepo = null,
|
||||
bool? pretty = null,
|
||||
HttpContext? context = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (!TryResolveTenant(context, out var tenantId, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
// Gather all data for the scan
|
||||
var changes = await changeRepo.GetChangesForScanAsync(scanId, ct);
|
||||
var changes = await changeRepo.GetChangesForScanAsync(scanId, ct, tenantId: tenantId);
|
||||
|
||||
// Get scan metadata if available
|
||||
string? baseDigest = null;
|
||||
@@ -111,7 +118,7 @@ internal static class SmartDiffEndpoints
|
||||
IReadOnlyList<StellaOps.Scanner.SmartDiff.Output.VexCandidate> vexCandidates = [];
|
||||
if (!string.IsNullOrWhiteSpace(targetDigest))
|
||||
{
|
||||
var candidates = await candidateStore.GetCandidatesAsync(targetDigest, ct).ConfigureAwait(false);
|
||||
var candidates = await candidateStore.GetCandidatesAsync(targetDigest, ct, tenantId: tenantId).ConfigureAwait(false);
|
||||
vexCandidates = candidates.Select(ToSarifVexCandidate).ToList();
|
||||
}
|
||||
|
||||
@@ -164,8 +171,14 @@ internal static class SmartDiffEndpoints
|
||||
IVexCandidateStore store,
|
||||
double? minConfidence = null,
|
||||
bool? pendingOnly = null,
|
||||
HttpContext? context = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (!TryResolveTenant(context, out var tenantId, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
var metadata = await metadataRepository.GetScanMetadataAsync(scanId, ct).ConfigureAwait(false);
|
||||
var targetDigest = NormalizeDigest(metadata?.TargetDigest);
|
||||
if (string.IsNullOrWhiteSpace(targetDigest))
|
||||
@@ -173,7 +186,7 @@ internal static class SmartDiffEndpoints
|
||||
return Results.NotFound(new { error = "Scan metadata not found", scanId });
|
||||
}
|
||||
|
||||
return await HandleGetCandidatesAsync(targetDigest, store, minConfidence, pendingOnly, ct).ConfigureAwait(false);
|
||||
return await HandleGetCandidatesAsync(targetDigest, store, minConfidence, pendingOnly, context, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static StellaOps.Scanner.SmartDiff.Output.RiskDirection ToSarifRiskDirection(MaterialRiskChangeResult change)
|
||||
@@ -230,9 +243,15 @@ internal static class SmartDiffEndpoints
|
||||
string scanId,
|
||||
IMaterialRiskChangeRepository repository,
|
||||
double? minPriority = null,
|
||||
HttpContext? context = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var changes = await repository.GetChangesForScanAsync(scanId, ct);
|
||||
if (!TryResolveTenant(context, out var tenantId, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
var changes = await repository.GetChangesForScanAsync(scanId, ct, tenantId: tenantId);
|
||||
|
||||
if (minPriority.HasValue)
|
||||
{
|
||||
@@ -257,15 +276,21 @@ internal static class SmartDiffEndpoints
|
||||
IVexCandidateStore store,
|
||||
double? minConfidence = null,
|
||||
bool? pendingOnly = null,
|
||||
HttpContext? context = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (!TryResolveTenant(context, out var tenantId, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
var normalizedDigest = NormalizeDigest(digest);
|
||||
if (string.IsNullOrWhiteSpace(normalizedDigest))
|
||||
{
|
||||
return Results.BadRequest(new { error = "Invalid image digest" });
|
||||
}
|
||||
|
||||
var candidates = await store.GetCandidatesAsync(normalizedDigest, ct);
|
||||
var candidates = await store.GetCandidatesAsync(normalizedDigest, ct, tenantId: tenantId);
|
||||
|
||||
if (minConfidence.HasValue)
|
||||
{
|
||||
@@ -293,9 +318,15 @@ internal static class SmartDiffEndpoints
|
||||
private static async Task<IResult> HandleGetCandidateAsync(
|
||||
string candidateId,
|
||||
IVexCandidateStore store,
|
||||
HttpContext? context = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var candidate = await store.GetCandidateAsync(candidateId, ct);
|
||||
if (!TryResolveTenant(context, out var tenantId, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
var candidate = await store.GetCandidateAsync(candidateId, ct, tenantId: tenantId);
|
||||
|
||||
if (candidate is null)
|
||||
{
|
||||
@@ -325,6 +356,10 @@ internal static class SmartDiffEndpoints
|
||||
{
|
||||
return Results.BadRequest(new { error = "Invalid action", validActions = new[] { "accept", "reject", "defer" } });
|
||||
}
|
||||
if (!TryResolveTenant(httpContext, out var tenantId, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
var reviewer = httpContext.User.Identity?.Name ?? "anonymous";
|
||||
var review = new VexCandidateReview(
|
||||
@@ -333,7 +368,7 @@ internal static class SmartDiffEndpoints
|
||||
ReviewedAt: timeProvider.GetUtcNow(),
|
||||
Comment: request.Comment);
|
||||
|
||||
var success = await store.ReviewCandidateAsync(candidateId, review, ct);
|
||||
var success = await store.ReviewCandidateAsync(candidateId, review, ct, tenantId: tenantId);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
@@ -365,6 +400,10 @@ internal static class SmartDiffEndpoints
|
||||
{
|
||||
return Results.BadRequest(new { error = "CandidateId is required" });
|
||||
}
|
||||
if (!TryResolveTenant(httpContext, out var tenantId, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
var metadata = await metadataRepository.GetScanMetadataAsync(scanId, ct).ConfigureAwait(false);
|
||||
var targetDigest = NormalizeDigest(metadata?.TargetDigest);
|
||||
@@ -373,7 +412,7 @@ internal static class SmartDiffEndpoints
|
||||
return Results.NotFound(new { error = "Scan metadata not found", scanId });
|
||||
}
|
||||
|
||||
var candidate = await store.GetCandidateAsync(request.CandidateId, ct).ConfigureAwait(false);
|
||||
var candidate = await store.GetCandidateAsync(request.CandidateId, ct, tenantId: tenantId).ConfigureAwait(false);
|
||||
if (candidate is null || !string.Equals(candidate.ImageDigest, targetDigest, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Results.NotFound(new { error = "Candidate not found for scan", scanId, candidateId = request.CandidateId });
|
||||
@@ -482,6 +521,40 @@ internal static class SmartDiffEndpoints
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryResolveTenant(HttpContext? context, out string tenantId, out IResult? failure)
|
||||
{
|
||||
tenantId = string.Empty;
|
||||
failure = null;
|
||||
|
||||
if (context is null)
|
||||
{
|
||||
failure = Results.BadRequest(new
|
||||
{
|
||||
type = "validation-error",
|
||||
title = "Invalid tenant context",
|
||||
detail = "tenant_missing"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ScannerRequestContextResolver.TryResolveTenant(
|
||||
context,
|
||||
out tenantId,
|
||||
out var tenantError,
|
||||
allowDefaultTenant: true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
failure = Results.BadRequest(new
|
||||
{
|
||||
type = "validation-error",
|
||||
title = "Invalid tenant context",
|
||||
detail = tenantError ?? "tenant_conflict"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user