more audit work
This commit is contained in:
@@ -2,6 +2,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Scanner.Emit.Spdx;
|
||||
using StellaOps.Scanner.WebService.Constants;
|
||||
using StellaOps.Scanner.WebService.Domain;
|
||||
using StellaOps.Scanner.WebService.Infrastructure;
|
||||
@@ -23,6 +24,17 @@ internal static class ExportEndpoints
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(scansGroup);
|
||||
|
||||
// GET /scans/{scanId}/exports/sbom - SPDX 3.0.1 SBOM export with format and profile selection
|
||||
// Sprint: SPRINT_20260107_004_002 Task SG-010
|
||||
scansGroup.MapGet("/{scanId}/exports/sbom", HandleExportSbomAsync)
|
||||
.WithName("scanner.scans.exports.sbom")
|
||||
.WithTags("Exports", "SBOM")
|
||||
.Produces(StatusCodes.Status200OK, contentType: "application/spdx+json")
|
||||
.Produces(StatusCodes.Status200OK, contentType: "application/ld+json")
|
||||
.Produces(StatusCodes.Status200OK, contentType: "application/vnd.cyclonedx+json")
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScannerPolicies.ScansRead);
|
||||
|
||||
// GET /scans/{scanId}/exports/sarif
|
||||
scansGroup.MapGet("/{scanId}/exports/sarif", HandleExportSarifAsync)
|
||||
.WithName("scanner.scans.exports.sarif")
|
||||
@@ -185,4 +197,142 @@ internal static class ExportEndpoints
|
||||
var json = JsonSerializer.Serialize(vexDocument, SerializerOptions);
|
||||
return Results.Content(json, "application/json", System.Text.Encoding.UTF8, StatusCodes.Status200OK);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles SBOM export with format and profile selection.
|
||||
/// Sprint: SPRINT_20260107_004_002 Tasks SG-010, SG-012
|
||||
/// </summary>
|
||||
/// <param name="scanId">The scan identifier.</param>
|
||||
/// <param name="format">SBOM format: spdx3, spdx2, cyclonedx (default: spdx2 for backward compatibility).</param>
|
||||
/// <param name="profile">SPDX 3.0.1 profile: software, lite (default: software). Only applies to spdx3 format.</param>
|
||||
/// <param name="coordinator">The scan coordinator service.</param>
|
||||
/// <param name="sbomExportService">The SBOM export service.</param>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
private static async Task<IResult> HandleExportSbomAsync(
|
||||
string scanId,
|
||||
string? format,
|
||||
string? profile,
|
||||
IScanCoordinator coordinator,
|
||||
ISbomExportService sbomExportService,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(coordinator);
|
||||
ArgumentNullException.ThrowIfNull(sbomExportService);
|
||||
|
||||
if (!ScanId.TryParse(scanId, out var parsed))
|
||||
{
|
||||
return ProblemResultFactory.Create(
|
||||
context,
|
||||
ProblemTypes.Validation,
|
||||
"Invalid scan identifier",
|
||||
StatusCodes.Status400BadRequest,
|
||||
detail: "Scan identifier is required.");
|
||||
}
|
||||
|
||||
var snapshot = await coordinator.GetAsync(parsed, cancellationToken).ConfigureAwait(false);
|
||||
if (snapshot is null)
|
||||
{
|
||||
return ProblemResultFactory.Create(
|
||||
context,
|
||||
ProblemTypes.NotFound,
|
||||
"Scan not found",
|
||||
StatusCodes.Status404NotFound,
|
||||
detail: "Requested scan could not be located.");
|
||||
}
|
||||
|
||||
// SG-012: Format selection logic with fallback to SPDX 2.3 for backward compatibility
|
||||
var selectedFormat = SelectSbomFormat(format);
|
||||
var selectedProfile = SelectSpdx3Profile(profile);
|
||||
|
||||
var exportResult = await sbomExportService.ExportAsync(
|
||||
parsed,
|
||||
selectedFormat,
|
||||
selectedProfile,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (exportResult is null || exportResult.Bytes is null || exportResult.Bytes.Length == 0)
|
||||
{
|
||||
return ProblemResultFactory.Create(
|
||||
context,
|
||||
ProblemTypes.NotFound,
|
||||
"No SBOM data available",
|
||||
StatusCodes.Status404NotFound,
|
||||
detail: "No SBOM data available for export.");
|
||||
}
|
||||
|
||||
// Set appropriate content-type header based on format
|
||||
var contentType = selectedFormat switch
|
||||
{
|
||||
SbomExportFormat.Spdx3 => "application/ld+json; profile=\"https://spdx.org/rdf/3.0.1/terms/Software/ProfileIdentifierType/" + selectedProfile.ToString().ToLowerInvariant() + "\"",
|
||||
SbomExportFormat.Spdx2 => "application/spdx+json; version=2.3",
|
||||
SbomExportFormat.CycloneDx => "application/vnd.cyclonedx+json; version=1.7",
|
||||
_ => "application/json"
|
||||
};
|
||||
|
||||
context.Response.Headers["X-StellaOps-Format"] = selectedFormat.ToString().ToLowerInvariant();
|
||||
if (selectedFormat == SbomExportFormat.Spdx3)
|
||||
{
|
||||
context.Response.Headers["X-StellaOps-Profile"] = selectedProfile.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
return Results.Bytes(exportResult.Bytes, contentType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects SBOM format with fallback to SPDX 2.3 for backward compatibility.
|
||||
/// Sprint: SPRINT_20260107_004_002 Task SG-012
|
||||
/// </summary>
|
||||
private static SbomExportFormat SelectSbomFormat(string? format)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(format))
|
||||
{
|
||||
// Default to SPDX 2.3 for backward compatibility
|
||||
return SbomExportFormat.Spdx2;
|
||||
}
|
||||
|
||||
return format.ToLowerInvariant() switch
|
||||
{
|
||||
"spdx3" or "spdx-3" or "spdx3.0" or "spdx-3.0.1" => SbomExportFormat.Spdx3,
|
||||
"spdx2" or "spdx-2" or "spdx2.3" or "spdx-2.3" or "spdx" => SbomExportFormat.Spdx2,
|
||||
"cyclonedx" or "cdx" or "cdx17" or "cyclonedx-1.7" => SbomExportFormat.CycloneDx,
|
||||
_ => SbomExportFormat.Spdx2 // Fallback for unknown formats
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects SPDX 3.0.1 profile with default to Software.
|
||||
/// </summary>
|
||||
private static Spdx3ProfileType SelectSpdx3Profile(string? profile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(profile))
|
||||
{
|
||||
return Spdx3ProfileType.Software;
|
||||
}
|
||||
|
||||
return profile.ToLowerInvariant() switch
|
||||
{
|
||||
"lite" => Spdx3ProfileType.Lite,
|
||||
"build" => Spdx3ProfileType.Build,
|
||||
"security" => Spdx3ProfileType.Security,
|
||||
"software" or _ => Spdx3ProfileType.Software
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SBOM export format enumeration.
|
||||
/// Sprint: SPRINT_20260107_004_002 Task SG-012
|
||||
/// </summary>
|
||||
public enum SbomExportFormat
|
||||
{
|
||||
/// <summary>SPDX 2.3 JSON format (default for backward compatibility).</summary>
|
||||
Spdx2,
|
||||
|
||||
/// <summary>SPDX 3.0.1 JSON-LD format with profile support.</summary>
|
||||
Spdx3,
|
||||
|
||||
/// <summary>CycloneDX 1.7 JSON format.</summary>
|
||||
CycloneDx
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user