Rename Concelier Source modules to Connector
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Common;
|
||||
using StellaOps.Concelier.Connector.Common.Packages;
|
||||
using StellaOps.Concelier.Storage.Mongo.Documents;
|
||||
using StellaOps.Concelier.Storage.Mongo.Dtos;
|
||||
using StellaOps.Concelier.Storage.Mongo.PsirtFlags;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Vndr.Apple.Internal;
|
||||
|
||||
internal static class AppleMapper
|
||||
{
|
||||
public static (Advisory Advisory, PsirtFlagRecord? Flag) Map(
|
||||
AppleDetailDto dto,
|
||||
DocumentRecord document,
|
||||
DtoRecord dtoRecord)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dto);
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
ArgumentNullException.ThrowIfNull(dtoRecord);
|
||||
|
||||
var recordedAt = dtoRecord.ValidatedAt.ToUniversalTime();
|
||||
|
||||
var fetchProvenance = new AdvisoryProvenance(
|
||||
VndrAppleConnectorPlugin.SourceName,
|
||||
"document",
|
||||
document.Uri,
|
||||
document.FetchedAt.ToUniversalTime());
|
||||
|
||||
var mapProvenance = new AdvisoryProvenance(
|
||||
VndrAppleConnectorPlugin.SourceName,
|
||||
"map",
|
||||
dto.AdvisoryId,
|
||||
recordedAt);
|
||||
|
||||
var aliases = BuildAliases(dto);
|
||||
var references = BuildReferences(dto, recordedAt);
|
||||
var affected = BuildAffected(dto, recordedAt);
|
||||
|
||||
var advisory = new Advisory(
|
||||
advisoryKey: dto.AdvisoryId,
|
||||
title: dto.Title,
|
||||
summary: dto.Summary,
|
||||
language: "en",
|
||||
published: dto.Published.ToUniversalTime(),
|
||||
modified: dto.Updated?.ToUniversalTime(),
|
||||
severity: null,
|
||||
exploitKnown: false,
|
||||
aliases: aliases,
|
||||
references: references,
|
||||
affectedPackages: affected,
|
||||
cvssMetrics: Array.Empty<CvssMetric>(),
|
||||
provenance: new[] { fetchProvenance, mapProvenance });
|
||||
|
||||
PsirtFlagRecord? flag = dto.RapidSecurityResponse
|
||||
? new PsirtFlagRecord(dto.AdvisoryId, "Apple", VndrAppleConnectorPlugin.SourceName, dto.ArticleId, recordedAt)
|
||||
: null;
|
||||
|
||||
return (advisory, flag);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> BuildAliases(AppleDetailDto dto)
|
||||
{
|
||||
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
dto.AdvisoryId,
|
||||
dto.ArticleId,
|
||||
};
|
||||
|
||||
foreach (var cve in dto.CveIds)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cve))
|
||||
{
|
||||
set.Add(cve.Trim());
|
||||
}
|
||||
}
|
||||
|
||||
var aliases = set.ToList();
|
||||
aliases.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
return aliases;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AdvisoryReference> BuildReferences(AppleDetailDto dto, DateTimeOffset recordedAt)
|
||||
{
|
||||
if (dto.References.Count == 0)
|
||||
{
|
||||
return Array.Empty<AdvisoryReference>();
|
||||
}
|
||||
|
||||
var list = new List<AdvisoryReference>(dto.References.Count);
|
||||
foreach (var reference in dto.References)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reference.Url))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var provenance = new AdvisoryProvenance(
|
||||
VndrAppleConnectorPlugin.SourceName,
|
||||
"reference",
|
||||
reference.Url,
|
||||
recordedAt);
|
||||
|
||||
list.Add(new AdvisoryReference(
|
||||
url: reference.Url,
|
||||
kind: reference.Kind,
|
||||
sourceTag: null,
|
||||
summary: reference.Title,
|
||||
provenance: provenance));
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// ignore invalid URLs
|
||||
}
|
||||
}
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
return Array.Empty<AdvisoryReference>();
|
||||
}
|
||||
|
||||
list.Sort(static (left, right) => StringComparer.OrdinalIgnoreCase.Compare(left.Url, right.Url));
|
||||
return list;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AffectedPackage> BuildAffected(AppleDetailDto dto, DateTimeOffset recordedAt)
|
||||
{
|
||||
if (dto.Affected.Count == 0)
|
||||
{
|
||||
return Array.Empty<AffectedPackage>();
|
||||
}
|
||||
|
||||
var packages = new List<AffectedPackage>(dto.Affected.Count);
|
||||
foreach (var product in dto.Affected)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(product.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var provenance = new[]
|
||||
{
|
||||
new AdvisoryProvenance(
|
||||
VndrAppleConnectorPlugin.SourceName,
|
||||
"affected",
|
||||
product.Name,
|
||||
recordedAt),
|
||||
};
|
||||
|
||||
var ranges = BuildRanges(product, recordedAt);
|
||||
var normalizedVersions = BuildNormalizedVersions(product, ranges);
|
||||
|
||||
packages.Add(new AffectedPackage(
|
||||
type: AffectedPackageTypes.Vendor,
|
||||
identifier: product.Name,
|
||||
platform: product.Platform,
|
||||
versionRanges: ranges,
|
||||
statuses: Array.Empty<AffectedPackageStatus>(),
|
||||
provenance: provenance,
|
||||
normalizedVersions: normalizedVersions));
|
||||
}
|
||||
|
||||
return packages.Count == 0 ? Array.Empty<AffectedPackage>() : packages;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AffectedVersionRange> BuildRanges(AppleAffectedProductDto product, DateTimeOffset recordedAt)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(product.Version) && string.IsNullOrWhiteSpace(product.Build))
|
||||
{
|
||||
return Array.Empty<AffectedVersionRange>();
|
||||
}
|
||||
|
||||
var provenance = new AdvisoryProvenance(
|
||||
VndrAppleConnectorPlugin.SourceName,
|
||||
"range",
|
||||
product.Name,
|
||||
recordedAt);
|
||||
|
||||
var extensions = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
if (!string.IsNullOrWhiteSpace(product.Version))
|
||||
{
|
||||
extensions["apple.version.raw"] = product.Version;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(product.Build))
|
||||
{
|
||||
extensions["apple.build"] = product.Build;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(product.Platform))
|
||||
{
|
||||
extensions["apple.platform"] = product.Platform;
|
||||
}
|
||||
|
||||
var primitives = extensions.Count == 0
|
||||
? null
|
||||
: new RangePrimitives(
|
||||
SemVer: TryCreateSemVerPrimitive(product.Version),
|
||||
Nevra: null,
|
||||
Evr: null,
|
||||
VendorExtensions: extensions);
|
||||
|
||||
var sanitizedVersion = PackageCoordinateHelper.TryParseSemVer(product.Version, out _, out var normalizedVersion)
|
||||
? normalizedVersion
|
||||
: product.Version;
|
||||
|
||||
return new[]
|
||||
{
|
||||
new AffectedVersionRange(
|
||||
rangeKind: "vendor",
|
||||
introducedVersion: null,
|
||||
fixedVersion: sanitizedVersion,
|
||||
lastAffectedVersion: null,
|
||||
rangeExpression: product.Version,
|
||||
provenance: provenance,
|
||||
primitives: primitives),
|
||||
};
|
||||
}
|
||||
|
||||
private static SemVerPrimitive? TryCreateSemVerPrimitive(string? version)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!PackageCoordinateHelper.TryParseSemVer(version, out _, out var normalized))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// treat as fixed version, unknown introduced/last affected
|
||||
return new SemVerPrimitive(
|
||||
Introduced: null,
|
||||
IntroducedInclusive: true,
|
||||
Fixed: normalized,
|
||||
FixedInclusive: true,
|
||||
LastAffected: null,
|
||||
LastAffectedInclusive: true,
|
||||
ConstraintExpression: null);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<NormalizedVersionRule> BuildNormalizedVersions(
|
||||
AppleAffectedProductDto product,
|
||||
IReadOnlyList<AffectedVersionRange> ranges)
|
||||
{
|
||||
if (ranges.Count == 0)
|
||||
{
|
||||
return Array.Empty<NormalizedVersionRule>();
|
||||
}
|
||||
|
||||
var segments = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(product.Platform))
|
||||
{
|
||||
segments.Add(product.Platform.Trim());
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(product.Name))
|
||||
{
|
||||
segments.Add(product.Name.Trim());
|
||||
}
|
||||
|
||||
var note = segments.Count == 0 ? null : $"apple:{string.Join(':', segments)}";
|
||||
|
||||
var rules = new List<NormalizedVersionRule>(ranges.Count);
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
var rule = range.ToNormalizedVersionRule(note);
|
||||
if (rule is not null)
|
||||
{
|
||||
rules.Add(rule);
|
||||
}
|
||||
}
|
||||
|
||||
return rules.Count == 0 ? Array.Empty<NormalizedVersionRule>() : rules.ToArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user