new advisories work and features gaps work
This commit is contained in:
@@ -6,6 +6,8 @@ using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Evidence.Pack.Models;
|
||||
|
||||
@@ -267,6 +269,9 @@ internal sealed class EvidencePackService : IEvidencePackService
|
||||
EvidencePackExportFormat.Markdown => ExportAsMarkdown(pack),
|
||||
EvidencePackExportFormat.Html => ExportAsHtml(pack),
|
||||
EvidencePackExportFormat.Pdf => throw new NotSupportedException("PDF export requires additional configuration"),
|
||||
// Sprint: SPRINT_20260112_005_BE_evidence_card_api (EVPCARD-BE-001)
|
||||
EvidencePackExportFormat.EvidenceCard => await ExportAsEvidenceCard(pack, compact: false, cancellationToken).ConfigureAwait(false),
|
||||
EvidencePackExportFormat.EvidenceCardCompact => await ExportAsEvidenceCard(pack, compact: true, cancellationToken).ConfigureAwait(false),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format), format, "Unsupported export format")
|
||||
};
|
||||
}
|
||||
@@ -417,6 +422,95 @@ internal sealed class EvidencePackService : IEvidencePackService
|
||||
};
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260112_005_BE_evidence_card_api (EVPCARD-BE-001)
|
||||
private async Task<EvidencePackExport> ExportAsEvidenceCard(
|
||||
EvidencePack pack,
|
||||
bool compact,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Get signed pack if available
|
||||
var signedPack = await _store.GetSignedByIdAsync(pack.TenantId, pack.PackId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Compute content digest for this pack
|
||||
var contentDigest = pack.ComputeContentDigest();
|
||||
|
||||
// Build evidence card structure using simple object
|
||||
var card = new
|
||||
{
|
||||
schema_version = "1.0.0",
|
||||
pack_id = pack.PackId,
|
||||
created_at = pack.CreatedAt,
|
||||
finding_id = pack.Subject.FindingId,
|
||||
cve_id = pack.Subject.CveId,
|
||||
component = pack.Subject.Component,
|
||||
claims = pack.Claims.Select(c => new
|
||||
{
|
||||
claim_type = c.Type.ToString(),
|
||||
text = c.Text,
|
||||
status = c.Status,
|
||||
confidence = c.Confidence
|
||||
}).ToList(),
|
||||
sbom_excerpt = compact ? null : BuildSbomExcerptFromEvidence(pack),
|
||||
dsse_envelope = signedPack is not null
|
||||
? new
|
||||
{
|
||||
payload_type = signedPack.Envelope.PayloadType,
|
||||
payload_digest = signedPack.Envelope.PayloadDigest,
|
||||
signatures = signedPack.Envelope.Signatures.Select(s => new
|
||||
{
|
||||
key_id = s.KeyId,
|
||||
sig = s.Sig
|
||||
}).ToList()
|
||||
}
|
||||
: null,
|
||||
signed_at = signedPack?.SignedAt,
|
||||
content_digest = contentDigest
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(card, EvidenceCardJsonOptions);
|
||||
var format = compact ? EvidencePackExportFormat.EvidenceCardCompact : EvidencePackExportFormat.EvidenceCard;
|
||||
|
||||
return new EvidencePackExport
|
||||
{
|
||||
PackId = pack.PackId,
|
||||
Format = format,
|
||||
Content = Encoding.UTF8.GetBytes(json),
|
||||
ContentType = "application/vnd.stellaops.evidence-card+json",
|
||||
FileName = $"evidence-card-{pack.PackId}.json"
|
||||
};
|
||||
}
|
||||
|
||||
private static object? BuildSbomExcerptFromEvidence(EvidencePack pack)
|
||||
{
|
||||
// Extract components from evidence items for determinism
|
||||
var components = pack.Evidence
|
||||
.Where(e => e.Type == EvidenceType.Sbom && !string.IsNullOrEmpty(e.Uri))
|
||||
.OrderBy(e => e.Uri, StringComparer.Ordinal)
|
||||
.Take(50)
|
||||
.Select(e => new { uri = e.Uri, digest = e.Digest })
|
||||
.ToList();
|
||||
|
||||
if (components.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
total_evidence_count = pack.Evidence.Length,
|
||||
excerpt_count = components.Count,
|
||||
components
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions EvidenceCardJsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
private const string HtmlTemplate = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
Reference in New Issue
Block a user