207 lines
6.3 KiB
C#
207 lines
6.3 KiB
C#
using System.Collections.Immutable;
|
|
using System.Runtime.Serialization;
|
|
|
|
namespace StellaOps.Excititor.Core;
|
|
|
|
public sealed record VexConsensus
|
|
{
|
|
public VexConsensus(
|
|
string vulnerabilityId,
|
|
VexProduct product,
|
|
VexConsensusStatus status,
|
|
DateTimeOffset calculatedAt,
|
|
IEnumerable<VexConsensusSource> sources,
|
|
IEnumerable<VexConsensusConflict>? conflicts = null,
|
|
VexSignalSnapshot? signals = null,
|
|
string? policyVersion = null,
|
|
string? summary = null,
|
|
string? policyRevisionId = null,
|
|
string? policyDigest = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(vulnerabilityId))
|
|
{
|
|
throw new ArgumentException("Vulnerability id must be provided.", nameof(vulnerabilityId));
|
|
}
|
|
|
|
VulnerabilityId = vulnerabilityId.Trim();
|
|
Product = product ?? throw new ArgumentNullException(nameof(product));
|
|
Status = status;
|
|
CalculatedAt = calculatedAt;
|
|
Sources = NormalizeSources(sources);
|
|
Conflicts = NormalizeConflicts(conflicts);
|
|
Signals = signals;
|
|
PolicyVersion = string.IsNullOrWhiteSpace(policyVersion) ? null : policyVersion.Trim();
|
|
Summary = string.IsNullOrWhiteSpace(summary) ? null : summary.Trim();
|
|
PolicyRevisionId = string.IsNullOrWhiteSpace(policyRevisionId) ? null : policyRevisionId.Trim();
|
|
PolicyDigest = string.IsNullOrWhiteSpace(policyDigest) ? null : policyDigest.Trim();
|
|
}
|
|
|
|
public string VulnerabilityId { get; }
|
|
|
|
public VexProduct Product { get; }
|
|
|
|
public VexConsensusStatus Status { get; }
|
|
|
|
public DateTimeOffset CalculatedAt { get; }
|
|
|
|
public ImmutableArray<VexConsensusSource> Sources { get; }
|
|
|
|
public ImmutableArray<VexConsensusConflict> Conflicts { get; }
|
|
|
|
public VexSignalSnapshot? Signals { get; }
|
|
|
|
public string? PolicyVersion { get; }
|
|
|
|
public string? Summary { get; }
|
|
|
|
public string? PolicyRevisionId { get; }
|
|
|
|
public string? PolicyDigest { get; }
|
|
|
|
private static ImmutableArray<VexConsensusSource> NormalizeSources(IEnumerable<VexConsensusSource> sources)
|
|
{
|
|
if (sources is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(sources));
|
|
}
|
|
|
|
var builder = ImmutableArray.CreateBuilder<VexConsensusSource>();
|
|
builder.AddRange(sources);
|
|
if (builder.Count == 0)
|
|
{
|
|
return ImmutableArray<VexConsensusSource>.Empty;
|
|
}
|
|
|
|
return builder
|
|
.OrderBy(static x => x.ProviderId, StringComparer.Ordinal)
|
|
.ThenBy(static x => x.DocumentDigest, StringComparer.Ordinal)
|
|
.ToImmutableArray();
|
|
}
|
|
|
|
private static ImmutableArray<VexConsensusConflict> NormalizeConflicts(IEnumerable<VexConsensusConflict>? conflicts)
|
|
{
|
|
if (conflicts is null)
|
|
{
|
|
return ImmutableArray<VexConsensusConflict>.Empty;
|
|
}
|
|
|
|
var items = conflicts.ToArray();
|
|
return items.Length == 0
|
|
? ImmutableArray<VexConsensusConflict>.Empty
|
|
: items
|
|
.OrderBy(static x => x.ProviderId, StringComparer.Ordinal)
|
|
.ThenBy(static x => x.DocumentDigest, StringComparer.Ordinal)
|
|
.ToImmutableArray();
|
|
}
|
|
}
|
|
|
|
public sealed record VexConsensusSource
|
|
{
|
|
public VexConsensusSource(
|
|
string providerId,
|
|
VexClaimStatus status,
|
|
string documentDigest,
|
|
double weight,
|
|
VexJustification? justification = null,
|
|
string? detail = null,
|
|
VexConfidence? confidence = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(providerId))
|
|
{
|
|
throw new ArgumentException("Provider id must be provided.", nameof(providerId));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(documentDigest))
|
|
{
|
|
throw new ArgumentException("Document digest must be provided.", nameof(documentDigest));
|
|
}
|
|
|
|
if (double.IsNaN(weight) || double.IsInfinity(weight) || weight < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(weight), "Weight must be a finite, non-negative number.");
|
|
}
|
|
|
|
ProviderId = providerId.Trim();
|
|
Status = status;
|
|
DocumentDigest = documentDigest.Trim();
|
|
Weight = weight;
|
|
Justification = justification;
|
|
Detail = string.IsNullOrWhiteSpace(detail) ? null : detail.Trim();
|
|
Confidence = confidence;
|
|
}
|
|
|
|
public string ProviderId { get; }
|
|
|
|
public VexClaimStatus Status { get; }
|
|
|
|
public string DocumentDigest { get; }
|
|
|
|
public double Weight { get; }
|
|
|
|
public VexJustification? Justification { get; }
|
|
|
|
public string? Detail { get; }
|
|
|
|
public VexConfidence? Confidence { get; }
|
|
}
|
|
|
|
public sealed record VexConsensusConflict
|
|
{
|
|
public VexConsensusConflict(
|
|
string providerId,
|
|
VexClaimStatus status,
|
|
string documentDigest,
|
|
VexJustification? justification = null,
|
|
string? detail = null,
|
|
string? reason = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(providerId))
|
|
{
|
|
throw new ArgumentException("Provider id must be provided.", nameof(providerId));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(documentDigest))
|
|
{
|
|
throw new ArgumentException("Document digest must be provided.", nameof(documentDigest));
|
|
}
|
|
|
|
ProviderId = providerId.Trim();
|
|
Status = status;
|
|
DocumentDigest = documentDigest.Trim();
|
|
Justification = justification;
|
|
Detail = string.IsNullOrWhiteSpace(detail) ? null : detail.Trim();
|
|
Reason = string.IsNullOrWhiteSpace(reason) ? null : reason.Trim();
|
|
}
|
|
|
|
public string ProviderId { get; }
|
|
|
|
public VexClaimStatus Status { get; }
|
|
|
|
public string DocumentDigest { get; }
|
|
|
|
public VexJustification? Justification { get; }
|
|
|
|
public string? Detail { get; }
|
|
|
|
public string? Reason { get; }
|
|
}
|
|
|
|
[DataContract]
|
|
public enum VexConsensusStatus
|
|
{
|
|
[EnumMember(Value = "affected")]
|
|
Affected,
|
|
|
|
[EnumMember(Value = "not_affected")]
|
|
NotAffected,
|
|
|
|
[EnumMember(Value = "fixed")]
|
|
Fixed,
|
|
|
|
[EnumMember(Value = "under_investigation")]
|
|
UnderInvestigation,
|
|
|
|
[EnumMember(Value = "divergent")]
|
|
Divergent,
|
|
}
|