189 lines
7.2 KiB
C#
189 lines
7.2 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using StellaOps.Scanner.WebService.Constants;
|
|
using StellaOps.Scanner.WebService.Domain;
|
|
using StellaOps.Scanner.WebService.Infrastructure;
|
|
using StellaOps.Scanner.WebService.Security;
|
|
using StellaOps.Scanner.WebService.Services;
|
|
|
|
namespace StellaOps.Scanner.WebService.Endpoints;
|
|
|
|
internal static class ExportEndpoints
|
|
{
|
|
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
|
{
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
|
Converters = { new JsonStringEnumConverter() },
|
|
WriteIndented = true
|
|
};
|
|
|
|
public static void MapExportEndpoints(this RouteGroupBuilder scansGroup)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(scansGroup);
|
|
|
|
// GET /scans/{scanId}/exports/sarif
|
|
scansGroup.MapGet("/{scanId}/exports/sarif", HandleExportSarifAsync)
|
|
.WithName("scanner.scans.exports.sarif")
|
|
.WithTags("Exports")
|
|
.Produces(StatusCodes.Status200OK, contentType: "application/sarif+json")
|
|
.Produces(StatusCodes.Status404NotFound)
|
|
.RequireAuthorization(ScannerPolicies.ScansRead);
|
|
|
|
// GET /scans/{scanId}/exports/cdxr
|
|
scansGroup.MapGet("/{scanId}/exports/cdxr", HandleExportCycloneDxRAsync)
|
|
.WithName("scanner.scans.exports.cdxr")
|
|
.WithTags("Exports")
|
|
.Produces(StatusCodes.Status200OK, contentType: "application/vnd.cyclonedx+json; version=1.7")
|
|
.Produces(StatusCodes.Status404NotFound)
|
|
.RequireAuthorization(ScannerPolicies.ScansRead);
|
|
|
|
// GET /scans/{scanId}/exports/openvex
|
|
scansGroup.MapGet("/{scanId}/exports/openvex", HandleExportOpenVexAsync)
|
|
.WithName("scanner.scans.exports.openvex")
|
|
.WithTags("Exports")
|
|
.Produces(StatusCodes.Status200OK, contentType: "application/json")
|
|
.Produces(StatusCodes.Status404NotFound)
|
|
.RequireAuthorization(ScannerPolicies.ScansRead);
|
|
}
|
|
|
|
private static async Task<IResult> HandleExportSarifAsync(
|
|
string scanId,
|
|
IScanCoordinator coordinator,
|
|
ISarifExportService exportService,
|
|
HttpContext context,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(coordinator);
|
|
ArgumentNullException.ThrowIfNull(exportService);
|
|
|
|
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.");
|
|
}
|
|
|
|
var sarifDocument = await exportService.ExportAsync(parsed, cancellationToken).ConfigureAwait(false);
|
|
if (sarifDocument is null)
|
|
{
|
|
return ProblemResultFactory.Create(
|
|
context,
|
|
ProblemTypes.NotFound,
|
|
"No findings available",
|
|
StatusCodes.Status404NotFound,
|
|
detail: "No findings available for SARIF export.");
|
|
}
|
|
|
|
var json = JsonSerializer.Serialize(sarifDocument, SerializerOptions);
|
|
return Results.Content(json, "application/sarif+json", System.Text.Encoding.UTF8, StatusCodes.Status200OK);
|
|
}
|
|
|
|
private static async Task<IResult> HandleExportCycloneDxRAsync(
|
|
string scanId,
|
|
IScanCoordinator coordinator,
|
|
ICycloneDxExportService exportService,
|
|
HttpContext context,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(coordinator);
|
|
ArgumentNullException.ThrowIfNull(exportService);
|
|
|
|
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.");
|
|
}
|
|
|
|
var cdxDocument = await exportService.ExportWithReachabilityAsync(parsed, cancellationToken).ConfigureAwait(false);
|
|
if (cdxDocument is null)
|
|
{
|
|
return ProblemResultFactory.Create(
|
|
context,
|
|
ProblemTypes.NotFound,
|
|
"No findings available",
|
|
StatusCodes.Status404NotFound,
|
|
detail: "No findings available for CycloneDX export.");
|
|
}
|
|
|
|
var json = JsonSerializer.Serialize(cdxDocument, SerializerOptions);
|
|
return Results.Content(json, "application/vnd.cyclonedx+json; version=1.7", System.Text.Encoding.UTF8, StatusCodes.Status200OK);
|
|
}
|
|
|
|
private static async Task<IResult> HandleExportOpenVexAsync(
|
|
string scanId,
|
|
IScanCoordinator coordinator,
|
|
IOpenVexExportService exportService,
|
|
HttpContext context,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(coordinator);
|
|
ArgumentNullException.ThrowIfNull(exportService);
|
|
|
|
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.");
|
|
}
|
|
|
|
var vexDocument = await exportService.ExportAsync(parsed, cancellationToken).ConfigureAwait(false);
|
|
if (vexDocument is null)
|
|
{
|
|
return ProblemResultFactory.Create(
|
|
context,
|
|
ProblemTypes.NotFound,
|
|
"No VEX data available",
|
|
StatusCodes.Status404NotFound,
|
|
detail: "No VEX data available for export.");
|
|
}
|
|
|
|
var json = JsonSerializer.Serialize(vexDocument, SerializerOptions);
|
|
return Results.Content(json, "application/json", System.Text.Encoding.UTF8, StatusCodes.Status200OK);
|
|
}
|
|
}
|