/// <summary> /// Updates the database with <paramref name="vulnerability"/>. /// </summary> /// <param name="vulnerability">The <see cref="PackageVulnerability"/> to persist in the database.</param> /// <param name="withdrawn">Whether or not this vulnerability has been withdrawn.</param> /// <param name="packagesToUpdate">The set of <see cref="Package"/>s affected by this operation that should be marked as updated.</param> /// <returns> /// The set of packages that were updated as part of this operation. /// </returns> private HashSet <Package> UpdateVulnerabilityInternal(PackageVulnerability vulnerability, bool withdrawn) { if (vulnerability == null) { throw new ArgumentNullException(nameof(vulnerability)); } var packagesToUpdate = new HashSet <Package>(); _logger.LogInformation("Updating vulnerability with GitHub key {GitHubDatabaseKey}", vulnerability.GitHubDatabaseKey); // Determine if we already have this vulnerability. var existingVulnerability = _entitiesContext.Vulnerabilities .Include(v => v.AffectedRanges) .Include(v => v.AffectedRanges.Select(pv => pv.Packages)) .SingleOrDefault(v => v.GitHubDatabaseKey == vulnerability.GitHubDatabaseKey); if (existingVulnerability == null) { _logger.LogInformation("Did not find existing vulnerability with GitHub key {GitHubDatabaseKey}", vulnerability.GitHubDatabaseKey); AddNewVulnerability(vulnerability, withdrawn, packagesToUpdate); } else { _logger.LogInformation("Found existing vulnerability with GitHub key {GitHubDatabaseKey}", vulnerability.GitHubDatabaseKey); UpdateExistingVulnerability(vulnerability, withdrawn, existingVulnerability, packagesToUpdate); } return(packagesToUpdate); }
private void AddNewVulnerability(PackageVulnerability vulnerability, bool withdrawn, HashSet <Package> packagesToUpdate) { if (withdrawn) { _logger.LogInformation( "Will not add vulnerability with GitHub key {GitHubDatabaseKey} to database because it is withdrawn", vulnerability.GitHubDatabaseKey); return; } if (!vulnerability.AffectedRanges.Any()) { // If the vulnerability does not have any vulnerable ranges, it cannot affect any packages. // Even if no packages are currently vulnerable to the vulnerability, as long as it has a vulnerable range, // there is at least one package that could be uploaded that would be vulnerable to it. _logger.LogInformation( "Will not add vulnerability with GitHub key {GitHubDatabaseKey} to database because it affects no packages", vulnerability.GitHubDatabaseKey); return; } _logger.LogInformation("Adding vulnerability with GitHub key {GitHubDatabaseKey} to database", vulnerability.GitHubDatabaseKey); _entitiesContext.Vulnerabilities.Add(vulnerability); _entitiesContext.VulnerableRanges.AddRange(vulnerability.AffectedRanges); foreach (var newRange in vulnerability.AffectedRanges) { _logger.LogInformation( "ID {VulnerablePackageId} and version range {VulnerablePackageVersionRange} is now vulnerable to vulnerability with GitHub key {GitHubDatabaseKey}", newRange.PackageId, newRange.PackageVersionRange, vulnerability.GitHubDatabaseKey); ProcessNewVulnerabilityRange(newRange, packagesToUpdate); } }
private void UpdateExistingVulnerability(PackageVulnerability vulnerability, bool withdrawn, PackageVulnerability existingVulnerability, HashSet <Package> packagesToUpdate) { // We already have this vulnerability, so we should update it. var vulnerablePackages = existingVulnerability.AffectedRanges.SelectMany(pv => pv.Packages); if (withdrawn || !vulnerability.AffectedRanges.Any()) { // If the vulnerability was withdrawn or lost all its ranges, all packages marked vulnerable need to be unmarked and updated. _logger.LogInformation("Removing vulnerability with GitHub key {GitHubDatabaseKey} from database", vulnerability.GitHubDatabaseKey); packagesToUpdate.UnionWith(vulnerablePackages); _entitiesContext.Vulnerabilities.Remove(existingVulnerability); _entitiesContext.VulnerableRanges.RemoveRange(existingVulnerability.AffectedRanges); } else { if (UpdatePackageVulnerabilityMetadata(vulnerability, existingVulnerability)) { // If the vulnerability's metadata was updated, all packages marked vulnerable need to be updated. _logger.LogInformation("Vulnerability with GitHub key {GitHubDatabaseKey} had its metadata updated", vulnerability.GitHubDatabaseKey); packagesToUpdate.UnionWith(vulnerablePackages); } UpdateRangesOfPackageVulnerability(vulnerability, existingVulnerability, packagesToUpdate); } }
public async Task WithNoExistingVulnerability_Withdrawn_DoesNotAdd() { // Arrange var id = "theId"; var versionRange = new VersionRange(NuGetVersion.Parse("1.0.0")).ToNormalizedString(); var vulnerability = new PackageVulnerability(); var range = new VulnerablePackageVersionRange { Vulnerability = vulnerability, PackageId = id, PackageVersionRange = versionRange }; vulnerability.AffectedRanges.Add(range); // Act await Service.UpdateVulnerabilityAsync(vulnerability, true); // Assert Assert.False(Context.Vulnerabilities.AnySafe()); Assert.False(Context.VulnerableRanges.AnySafe()); UpdateServiceMock.Verify( x => x.UpdatePackagesAsync(It.IsAny <IReadOnlyList <Package> >(), It.IsAny <bool>()), Times.Never); VerifyTransaction(); }
private VulnerablePackageVersionRange FromVulnerability(PackageVulnerability vulnerability, SecurityVulnerability securityVulnerability) { return(new VulnerablePackageVersionRange { Vulnerability = vulnerability, PackageId = securityVulnerability.Package.Name, PackageVersionRange = _gitHubVersionRangeParser.ToNuGetVersionRange(securityVulnerability.VulnerableVersionRange).ToNormalizedString() }); }
public async Task WithExistingVulnerability_Withdrawn_RemovesAndUnmarks(bool hasExistingVulnerablePackages) { // Arrange var key = 1; var vulnerability = new PackageVulnerability { GitHubDatabaseKey = key }; var existingVulnerability = new PackageVulnerability { GitHubDatabaseKey = key, Severity = PackageVulnerabilitySeverity.Moderate }; Context.Vulnerabilities.Add(existingVulnerability); var id = "theId"; var versionRange = new VersionRange(NuGetVersion.Parse("1.0.0")).ToNormalizedString(); var range = new VulnerablePackageVersionRange { Vulnerability = vulnerability, PackageId = id, PackageVersionRange = versionRange }; vulnerability.AffectedRanges.Add(range); if (hasExistingVulnerablePackages) { var existingVulnerablePackage = new Package(); var existingRange = new VulnerablePackageVersionRange { Vulnerability = existingVulnerability }; Context.VulnerableRanges.Add(existingRange); existingVulnerability.AffectedRanges.Add(existingRange); existingRange.Packages.Add(existingVulnerablePackage); vulnerability.AffectedRanges.Add(existingRange); UpdateServiceMock .Setup(x => x.UpdatePackagesAsync(new[] { existingVulnerablePackage }, true)) .Returns(Task.CompletedTask) .Verifiable(); } var service = GetService <PackageVulnerabilitiesManagementService>(); // Act await service.UpdateVulnerabilityAsync(vulnerability, true); // Assert Assert.False(Context.Vulnerabilities.AnySafe()); Assert.DoesNotContain(range, Context.VulnerableRanges); UpdateServiceMock.Verify(); VerifyTransaction(); }
private void UpdateRangesOfPackageVulnerability(PackageVulnerability vulnerability, PackageVulnerability existingVulnerability, HashSet <Package> packagesToUpdate) { var rangeComparer = new RangeForSameVulnerabilityEqualityComparer(); // Check for updates in the existing version ranges of this vulnerability. foreach (var existingRange in existingVulnerability.AffectedRanges.ToList()) { var updatedRange = vulnerability.AffectedRanges .SingleOrDefault(r => rangeComparer.Equals(existingRange, r)); if (updatedRange == null) { // Any ranges that are missing from the updated vulnerability need to be removed. _logger.LogInformation( "ID {VulnerablePackageId} and version range {VulnerablePackageVersionRange} is no longer vulnerable to vulnerability with GitHub key {GitHubDatabaseKey}", existingRange.PackageId, existingRange.PackageVersionRange, vulnerability.GitHubDatabaseKey); _entitiesContext.VulnerableRanges.Remove(existingRange); existingVulnerability.AffectedRanges.Remove(existingRange); packagesToUpdate.UnionWith(existingRange.Packages); } else { // Any range that had its first patched version updated needs to be updated. if (existingRange.FirstPatchedPackageVersion != updatedRange.FirstPatchedPackageVersion) { existingRange.FirstPatchedPackageVersion = updatedRange.FirstPatchedPackageVersion; packagesToUpdate.UnionWith(existingRange.Packages); } } } // Any new ranges in the updated vulnerability need to be added to the database. var newRanges = vulnerability.AffectedRanges .Except(existingVulnerability.AffectedRanges, rangeComparer) .ToList(); foreach (var newRange in newRanges) { _logger.LogInformation( "ID {VulnerablePackageId} and version range {VulnerablePackageVersionRange} is now vulnerable to vulnerability with GitHub key {GitHubDatabaseKey}", newRange.PackageId, newRange.PackageVersionRange, vulnerability.GitHubDatabaseKey); newRange.Vulnerability = existingVulnerability; // this needs to happen before we update _entitiesContext, otherwise index uniqueness conflicts occur _entitiesContext.VulnerableRanges.Add(newRange); existingVulnerability.AffectedRanges.Add(newRange); ProcessNewVulnerabilityRange(newRange, packagesToUpdate); } }
public async Task WithNoExistingVulnerability_WithRanges_Adds() { // Arrange var id = "theId"; var versionRange = new VersionRange(NuGetVersion.Parse("1.0.0")).ToNormalizedString(); var vulnerability = new PackageVulnerability(); var range = new VulnerablePackageVersionRange { Vulnerability = vulnerability, PackageId = id, PackageVersionRange = versionRange }; vulnerability.AffectedRanges.Add(range); var registration = new PackageRegistration { Id = id }; Context.PackageRegistrations.Add(registration); var vulnerablePackage = new Package { PackageRegistration = registration, NormalizedVersion = "1.0.1" }; registration.Packages.Add(vulnerablePackage); var notVulnerablePackage = new Package { PackageRegistration = registration, NormalizedVersion = "0.0.0" }; registration.Packages.Add(notVulnerablePackage); UpdateServiceMock .Setup(x => x.UpdatePackagesAsync(new[] { vulnerablePackage }, true)) .Returns(Task.CompletedTask) .Verifiable(); // Act await Service.UpdateVulnerabilityAsync(vulnerability, false); // Assert Assert.Single(Context.Vulnerabilities, vulnerability); Assert.Single(Context.VulnerableRanges, range); UpdateServiceMock.Verify(); VerifyTransaction(); }
public async Task WithNoExistingVulnerability_NoRanges_DoesNotAdd() { // Arrange var vulnerability = new PackageVulnerability(); // Act await Service.UpdateVulnerabilityAsync(vulnerability, true); // Assert Assert.False(Context.Vulnerabilities.AnySafe()); Assert.False(Context.VulnerableRanges.AnySafe()); UpdateServiceMock.Verify( x => x.UpdatePackagesAsync(It.IsAny <IReadOnlyList <Package> >(), It.IsAny <bool>()), Times.Never); VerifyTransaction(); }
private Tuple <PackageVulnerability, bool> FromAdvisory(SecurityAdvisory advisory) { var vulnerability = new PackageVulnerability { GitHubDatabaseKey = advisory.DatabaseId, Severity = (PackageVulnerabilitySeverity)Enum.Parse(typeof(PackageVulnerabilitySeverity), advisory.Severity, ignoreCase: true), AdvisoryUrl = advisory.Permalink }; foreach (var securityVulnerability in advisory.Vulnerabilities?.Edges?.Select(e => e.Node) ?? Enumerable.Empty <SecurityVulnerability>()) { var packageVulnerability = FromVulnerability(vulnerability, securityVulnerability); vulnerability.AffectedRanges.Add(packageVulnerability); } return(Tuple.Create(vulnerability, advisory.WithdrawnAt != null)); }
public async Task WithExistingVulnerability_NotWithdrawn_NoRanges_RemovesAndUnmarks(bool hasExistingVulnerablePackages) { // Arrange var key = 1; var vulnerability = new PackageVulnerability { GitHubDatabaseKey = key }; var existingVulnerability = new PackageVulnerability { GitHubDatabaseKey = key, Severity = PackageVulnerabilitySeverity.Moderate }; Context.Vulnerabilities.Add(existingVulnerability); if (hasExistingVulnerablePackages) { var existingVulnerablePackage = new Package(); var existingRange = new VulnerablePackageVersionRange { Vulnerability = existingVulnerability }; Context.VulnerableRanges.Add(existingRange); existingVulnerability.AffectedRanges.Add(existingRange); existingRange.Packages.Add(existingVulnerablePackage); UpdateServiceMock .Setup(x => x.UpdatePackagesAsync(new[] { existingVulnerablePackage }, true)) .Returns(Task.CompletedTask) .Verifiable(); } var service = GetService <PackageVulnerabilityService>(); // Act await service.UpdateVulnerabilityAsync(vulnerability, false); // Assert Assert.False(Context.Vulnerabilities.AnySafe()); Assert.False(Context.VulnerableRanges.AnySafe()); UpdateServiceMock.Verify(); VerifyTransaction(); }
/// <returns> /// <c>True</c> when the metadata of the existing vulnerability was changed; otherwise <c>false</c>. /// </returns> private bool UpdatePackageVulnerabilityMetadata(PackageVulnerability vulnerability, PackageVulnerability existingVulnerability) { var wasUpdated = false; if (vulnerability.Severity != existingVulnerability.Severity) { existingVulnerability.Severity = vulnerability.Severity; wasUpdated = true; } if (vulnerability.ReferenceUrl != existingVulnerability.ReferenceUrl) { existingVulnerability.ReferenceUrl = vulnerability.ReferenceUrl; wasUpdated = true; } return(wasUpdated); }
private Task VerifyVulnerabilityInMetadataAsync(PackageVulnerability gitHubAdvisory) { Console.WriteLine($"[Metadata] Verifying vulnerability {gitHubAdvisory.GitHubDatabaseKey}."); if (gitHubAdvisory.AffectedRanges == null || !gitHubAdvisory.AffectedRanges.Any()) { return(Task.CompletedTask); } // Group ranges by id -- this makes testing metadata collections cleaner var rangesById = new Dictionary <string, IList <string> >(); foreach (var range in gitHubAdvisory.AffectedRanges) { var id = range.PackageId.Trim(' '); // some incoming data needs cleaning if (rangesById.TryGetValue(id, out var packageVersionRangeForId)) { packageVersionRangeForId.Add(range.PackageVersionRange); } else { rangesById[id] = new List <string> { range.PackageVersionRange }; } } var verificationJobsForAdvisory = new List <Task>(); foreach (var rangeById in rangesById) { verificationJobsForAdvisory.Add( VerifyVulnerabilityForRangeAsync( rangeById.Key, ranges: rangeById.Value, gitHubAdvisory.AdvisoryUrl, gitHubAdvisory.GitHubDatabaseKey, gitHubAdvisory.Severity) ); } return(Task.WhenAll(verificationJobsForAdvisory)); }
public async Task UpdateVulnerabilityAsync(PackageVulnerability vulnerability, bool withdrawn) { if (vulnerability == null) { throw new ArgumentNullException(nameof(vulnerability)); } using (var strategy = new SuspendDbExecutionStrategy()) using (var transaction = _entitiesContext.GetDatabase().BeginTransaction()) { var packagesToUpdate = UpdateVulnerabilityInternal(vulnerability, withdrawn); await _entitiesContext.SaveChangesAsync(); if (packagesToUpdate.Any()) { await _packageUpdateService.UpdatePackagesAsync(packagesToUpdate.ToList()); } transaction.Commit(); } }
private void UpdateRangesOfPackageVulnerability(PackageVulnerability vulnerability, PackageVulnerability existingVulnerability, HashSet <Package> packagesToUpdate) { // Any new ranges in the updated vulnerability need to be added to the database. var rangeComparer = new RangeForSameVulnerabilityEqualityComparer(); var newRanges = vulnerability.AffectedRanges .Except(existingVulnerability.AffectedRanges, rangeComparer) .ToList(); _entitiesContext.VulnerableRanges.AddRange(newRanges); foreach (var newRange in newRanges) { _logger.LogInformation( "ID {VulnerablePackageId} and version range {VulnerablePackageVersionRange} is now vulnerable to vulnerability with GitHub key {GitHubDatabaseKey}", newRange.PackageId, newRange.PackageVersionRange, vulnerability.GitHubDatabaseKey); newRange.Vulnerability = existingVulnerability; existingVulnerability.AffectedRanges.Add(newRange); ProcessNewVulnerabilityRange(newRange, packagesToUpdate); } // Any ranges that are missing from the updated vulnerability need to be removed. var missingRanges = existingVulnerability.AffectedRanges .Except(vulnerability.AffectedRanges, rangeComparer) .ToList(); _entitiesContext.VulnerableRanges.RemoveRange(missingRanges); foreach (var missingRange in missingRanges) { _logger.LogInformation( "ID {VulnerablePackageId} and version range {VulnerablePackageVersionRange} is no longer vulnerable to vulnerability with GitHub key {GitHubDatabaseKey}", missingRange.PackageId, missingRange.PackageVersionRange, vulnerability.GitHubDatabaseKey); existingVulnerability.AffectedRanges.Remove(missingRange); packagesToUpdate.UnionWith(missingRange.Packages); } }
public Task UpdateVulnerabilityAsync(PackageVulnerability vulnerability, bool withdrawn) { if (vulnerability == null) { Console.Error.WriteLine("Null vulnerability passed to verifier! Continuing..."); return(Task.CompletedTask); } if (_configuration.VerifyDatabase) { VerifyVulnerabilityInDatabase(vulnerability, withdrawn); } // Note: testing a withdrawn advisory isn't practical in registration metadata. We can only download // metadata for a package, and would need to download all package/version blobs to determine an advisory // is no longer present. Covering withdrawn advisory processing in the database will be adequate. if (_configuration.VerifyRegistrationMetadata && !withdrawn) { return(VerifyVulnerabilityInMetadataAsync(vulnerability)); } return(Task.CompletedTask); }
private void SetUp() { _registrationVulnerable = new PackageRegistration { Id = "Vulnerable" }; _vulnerabilityCritical = new PackageVulnerability { AdvisoryUrl = "http://theurl/1234", GitHubDatabaseKey = 1234, Severity = PackageVulnerabilitySeverity.Critical }; _vulnerabilityModerate = new PackageVulnerability { AdvisoryUrl = "http://theurl/5678", GitHubDatabaseKey = 5678, Severity = PackageVulnerabilitySeverity.Moderate }; _versionRangeCritical = new VulnerablePackageVersionRange { Vulnerability = _vulnerabilityCritical, PackageVersionRange = "1.1.1", FirstPatchedPackageVersion = "1.1.2" }; _versionRangeModerate = new VulnerablePackageVersionRange { Vulnerability = _vulnerabilityModerate, PackageVersionRange = "<=1.1.1", FirstPatchedPackageVersion = "1.1.2" }; _packageVulnerable100 = new Package { Key = 0, PackageRegistration = _registrationVulnerable, Version = "1.0.0", VulnerablePackageRanges = new List <VulnerablePackageVersionRange> { _versionRangeModerate } }; _packageVulnerable110 = new Package { Key = 1, PackageRegistration = _registrationVulnerable, Version = "1.1.0", VulnerablePackageRanges = new List <VulnerablePackageVersionRange> { _versionRangeModerate } }; _packageVulnerable111 = new Package { Key = 3, // simulate a different order in db - create a non-contiguous range of rows, even if the range is contiguous PackageRegistration = _registrationVulnerable, Version = "1.1.1", VulnerablePackageRanges = new List <VulnerablePackageVersionRange> { _versionRangeModerate, _versionRangeCritical } }; _packageVulnerable112 = new Package { Key = 2, // simulate a different order in db - create a non-contiguous range of rows, even if the range is contiguous PackageRegistration = _registrationVulnerable, Version = "1.1.2", VulnerablePackageRanges = new List <VulnerablePackageVersionRange>() }; _packageNotVulnerable = new Package { Key = 4, PackageRegistration = new PackageRegistration { Id = "NotVulnerable" }, VulnerablePackageRanges = new List <VulnerablePackageVersionRange>() }; }
public async Task WithExistingVulnerability_NotWithdrawn_UpdatesPackages( bool hasExistingVulnerablePackages, bool wasUpdated) { // Arrange var key = 1; var severity = PackageVulnerabilitySeverity.Low; var newSeverity = PackageVulnerabilitySeverity.Critical; var url = "http://hi"; var newUrl = "https://howdy"; var vulnerability = new PackageVulnerability { GitHubDatabaseKey = key, Severity = wasUpdated ? newSeverity : severity, AdvisoryUrl = wasUpdated ? newUrl : url }; var existingVulnerability = new PackageVulnerability { GitHubDatabaseKey = key, Severity = severity, AdvisoryUrl = url }; Context.Vulnerabilities.Add(existingVulnerability); var id = "theId"; var versionRange = new VersionRange(NuGetVersion.Parse("1.0.0")).ToNormalizedString(); var range = new VulnerablePackageVersionRange { Vulnerability = vulnerability, PackageId = id, PackageVersionRange = versionRange }; vulnerability.AffectedRanges.Add(range); var registration = new PackageRegistration { Id = id }; var expectedPackagesToUpdate = new List <Package>(); var expectedVulnerablePackages = new List <Package>(); if (hasExistingVulnerablePackages) { // Any packages that are already vulnerable to this vulnerability but not associated with this package vulnerability should be updated if the vulnerability is updated. var existingVulnerablePackage = new Package { PackageRegistration = registration, NormalizedVersion = "0.0.5" }; var existingVulnerablePackageVersion = NuGetVersion.Parse(existingVulnerablePackage.NormalizedVersion); var existingRange = new VulnerablePackageVersionRange { Vulnerability = existingVulnerability, PackageId = id, PackageVersionRange = new VersionRange(existingVulnerablePackageVersion, true, existingVulnerablePackageVersion, true).ToNormalizedString() }; Context.VulnerableRanges.Add(existingRange); existingRange.Packages.Add(existingVulnerablePackage); existingVulnerability.AffectedRanges.Add(existingRange); vulnerability.AffectedRanges.Add(existingRange); expectedVulnerablePackages.Add(existingVulnerablePackage); if (wasUpdated) { expectedPackagesToUpdate.Add(existingVulnerablePackage); } // If an existing vulnerable range is updated, its packages should be updated. var existingVulnerablePackageWithUpdatedRange = new Package { PackageRegistration = registration, NormalizedVersion = "0.0.9" }; var existingVulnerablePackageVersionWithUpdatedRange = NuGetVersion.Parse(existingVulnerablePackageWithUpdatedRange.NormalizedVersion); var existingRangeWithUpdatedRange = new VulnerablePackageVersionRange { Vulnerability = existingVulnerability, PackageId = id, PackageVersionRange = new VersionRange(existingVulnerablePackageVersionWithUpdatedRange, true, existingVulnerablePackageVersionWithUpdatedRange, true).ToNormalizedString() }; Context.VulnerableRanges.Add(existingRangeWithUpdatedRange); existingRangeWithUpdatedRange.Packages.Add(existingVulnerablePackageWithUpdatedRange); existingVulnerability.AffectedRanges.Add(existingRangeWithUpdatedRange); var updatedExistingRange = new VulnerablePackageVersionRange { Vulnerability = existingRangeWithUpdatedRange.Vulnerability, PackageId = existingRangeWithUpdatedRange.PackageId, PackageVersionRange = existingRangeWithUpdatedRange.PackageVersionRange, FirstPatchedPackageVersion = "1.0.0" }; vulnerability.AffectedRanges.Add(updatedExistingRange); expectedVulnerablePackages.Add(existingVulnerablePackageWithUpdatedRange); expectedPackagesToUpdate.Add(existingVulnerablePackageWithUpdatedRange); // If a package vulnerability is missing from the new vulnerability, it should be removed. var existingMissingVulnerablePackage = new Package { PackageRegistration = registration, NormalizedVersion = "0.0.6" }; var existingMissingVulnerablePackageVersion = NuGetVersion.Parse(existingMissingVulnerablePackage.NormalizedVersion); var existingMissingRange = new VulnerablePackageVersionRange { Vulnerability = existingVulnerability, PackageId = id, PackageVersionRange = new VersionRange(existingMissingVulnerablePackageVersion, true, existingMissingVulnerablePackageVersion, true).ToNormalizedString() }; Context.VulnerableRanges.Add(existingMissingRange); existingMissingRange.Packages.Add(existingMissingVulnerablePackage); existingVulnerability.AffectedRanges.Add(existingMissingRange); expectedPackagesToUpdate.Add(existingMissingVulnerablePackage); } Context.PackageRegistrations.Add(registration); // A package that fits in the version range but is not vulnerable yet should be vulnerable and updated. var newlyVulnerablePackage = new Package { PackageRegistration = registration, NormalizedVersion = "1.0.2" }; registration.Packages.Add(newlyVulnerablePackage); expectedVulnerablePackages.Add(newlyVulnerablePackage); expectedPackagesToUpdate.Add(newlyVulnerablePackage); // A package that is not vulnerable and does not fit in the version range should not be touched. var neverVulnerablePackage = new Package { PackageRegistration = registration, NormalizedVersion = "0.0.1" }; registration.Packages.Add(neverVulnerablePackage); if (expectedPackagesToUpdate.Any()) { UpdateServiceMock .Setup(x => x.UpdatePackagesAsync( It.Is <IReadOnlyList <Package> >( p => p .OrderBy(v => v.NormalizedVersion) .SequenceEqual( expectedPackagesToUpdate.OrderBy(v => v.NormalizedVersion))), true)) .Returns(Task.CompletedTask) .Verifiable(); } var service = GetService <PackageVulnerabilitiesManagementService>(); // Act await service.UpdateVulnerabilityAsync(vulnerability, false); // Assert Assert.Contains(existingVulnerability, Context.Vulnerabilities); Assert.Contains(range, Context.VulnerableRanges); Assert.Equal(existingVulnerability, range.Vulnerability); Assert.Equal(wasUpdated ? newSeverity : severity, existingVulnerability.Severity); Assert.Equal(wasUpdated ? newUrl : url, existingVulnerability.AdvisoryUrl); Assert.Equal( expectedVulnerablePackages.OrderBy(p => p.NormalizedVersion), Context.VulnerableRanges.SelectMany(pv => pv.Packages).OrderBy(p => p.NormalizedVersion)); UpdateServiceMock.Verify(); VerifyTransaction(); }
private void VerifyVulnerabilityInDatabase(PackageVulnerability vulnerability, bool withdrawn) { Console.WriteLine($"[Database] Verifying vulnerability {vulnerability.GitHubDatabaseKey}."); var existingVulnerability = _entitiesContext.Vulnerabilities .Include(v => v.AffectedRanges) .SingleOrDefault(v => v.GitHubDatabaseKey == vulnerability.GitHubDatabaseKey); if (withdrawn || !vulnerability.AffectedRanges.Any()) { if (existingVulnerability != null) { Console.Error.WriteLine(withdrawn ? $@"[Database] Vulnerability advisory {vulnerability.GitHubDatabaseKey} was withdrawn and should not be in DB!" : $@"[Database] Vulnerability advisory {vulnerability.GitHubDatabaseKey} affects no packages and should not be in DB!"); HasErrors = true; } return; } if (existingVulnerability == null) { Console.Error.WriteLine($"[Database] Cannot find vulnerability {vulnerability.GitHubDatabaseKey} in DB!"); HasErrors = true; return; } if (existingVulnerability.Severity != vulnerability.Severity) { Console.Error.WriteLine( $@"[Database] Vulnerability advisory {vulnerability.GitHubDatabaseKey }, severity does not match! GitHub: {vulnerability.Severity}, DB: {existingVulnerability.Severity}"); HasErrors = true; } if (existingVulnerability.AdvisoryUrl != vulnerability.AdvisoryUrl) { Console.Error.WriteLine( $@"[Database] Vulnerability advisory {vulnerability.GitHubDatabaseKey }, advisory URL does not match! GitHub: {vulnerability.AdvisoryUrl}, DB: { existingVulnerability.AdvisoryUrl}"); HasErrors = true; } foreach (var range in vulnerability.AffectedRanges) { Console.WriteLine($"[Database] Verifying range affecting {range.PackageId} {range.PackageVersionRange}."); var existingRange = existingVulnerability.AffectedRanges .SingleOrDefault(r => r.PackageId == range.PackageId && r.PackageVersionRange == range.PackageVersionRange); if (existingRange == null) { Console.Error.WriteLine( $@"[Database] Vulnerability advisory {vulnerability.GitHubDatabaseKey }, cannot find range {range.PackageId} {range.PackageVersionRange} in DB!"); HasErrors = true; continue; } if (existingRange.FirstPatchedPackageVersion != range.FirstPatchedPackageVersion) { Console.Error.WriteLine( $@"[Database] Vulnerability advisory {vulnerability.GitHubDatabaseKey }, range {range.PackageId} {range.PackageVersionRange}, first patched version does not match! GitHub: { range.FirstPatchedPackageVersion}, DB: {range.FirstPatchedPackageVersion}"); HasErrors = true; } var packages = _entitiesContext.Packages .Where(p => p.PackageRegistration.Id == range.PackageId) .Include(p => p.VulnerablePackageRanges) .ToList(); var versionRange = VersionRange.Parse(range.PackageVersionRange); foreach (var package in packages) { var version = NuGetVersion.Parse(package.NormalizedVersion); if (versionRange.Satisfies(version) != package.VulnerablePackageRanges.Contains(existingRange)) { Console.Error.WriteLine( $@"[Database] Vulnerability advisory {vulnerability.GitHubDatabaseKey }, range {range.PackageId} {range.PackageVersionRange}, package {package.NormalizedVersion } is not properly marked vulnerable to vulnerability!"); HasErrors = true; } } } }