Ejemplo n.º 1
0
        /// <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);
        }
Ejemplo n.º 2
0
        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);
            }
        }
Ejemplo n.º 3
0
        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();
            }
Ejemplo n.º 5
0
 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();
            }
Ejemplo n.º 7
0
        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));
        }
Ejemplo n.º 11
0
            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();
            }
Ejemplo n.º 12
0
        /// <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));
        }
Ejemplo n.º 14
0
        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();
                }
        }
Ejemplo n.º 15
0
        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;
                    }
                }
            }
        }