From 35a7083534efeafb6ddd05308fb1ec0b48421748 Mon Sep 17 00:00:00 2001 From: Nikolche Kolev Date: Wed, 21 Feb 2024 16:21:44 -0800 Subject: [PATCH] Enable vulnerability checking for packages.config based projects in CLI scenarios --- .../Commands/InstallCommand.cs | 2 + .../Commands/RestoreCommand.cs | 41 +++- .../NuGet.Build.Tasks/BuildTasksUtility.cs | 17 +- .../NuGet.Build.Tasks/NuGet.targets | 2 + .../RestoreCommand/RestoreCommand.cs | 2 +- .../RestoreCommand/Utility/AuditUtility.cs | 43 ++-- .../Utility/MSBuildRestoreUtility.cs | 2 +- .../Audit/AuditCheckResult.cs | 85 ++++++++ .../AuditChecker.cs} | 188 +++++++++++++----- .../GlobalSuppressions.cs | 4 +- .../IDE/IPackageRestoreManager.cs | 2 + .../IDE/PackageRestoreContext.cs | 18 ++ .../IDE/PackageRestoreManager.cs | 28 ++- .../PublicAPI.Unshipped.txt | 10 + .../Resolution/ResolverGather.cs | 1 + .../PublicAPI.Unshipped.txt | 2 + .../RestoreAuditProperties.cs | 57 ++++++ .../AuditUtilityTests.cs | 3 + 18 files changed, 407 insertions(+), 100 deletions(-) create mode 100644 src/NuGet.Core/NuGet.PackageManagement/Audit/AuditCheckResult.cs rename src/NuGet.Core/NuGet.PackageManagement/{AuditUtility.cs => Audit/AuditChecker.cs} (53%) diff --git a/src/NuGet.Clients/NuGet.CommandLine/Commands/InstallCommand.cs b/src/NuGet.Clients/NuGet.CommandLine/Commands/InstallCommand.cs index 96a706779e7..02c2605373b 100644 --- a/src/NuGet.Clients/NuGet.CommandLine/Commands/InstallCommand.cs +++ b/src/NuGet.Clients/NuGet.CommandLine/Commands/InstallCommand.cs @@ -190,6 +190,8 @@ private async Task PerformV2RestoreAsync(string packagesConfigFilePath, string i packageRestoreFailedEvent: (sender, args) => { failedEvents.Enqueue(args); }, sourceRepositories: packageSources.Select(sourceRepositoryProvider.CreateRepository), maxNumberOfParallelTasks: DisableParallelProcessing ? 1 : PackageManagementConstants.DefaultMaxDegreeOfParallelism, + disableNuGetAudit: false, + restoreAuditProperties: new(), logger: Console); var packageSaveMode = Packaging.PackageSaveMode.Defaultv2; diff --git a/src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs b/src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs index 462cbdc6e11..82cdc96cc61 100644 --- a/src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs +++ b/src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs @@ -9,7 +9,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.Remoting; using System.Threading; using System.Threading.Tasks; using NuGet.Commands; @@ -273,6 +272,7 @@ private async Task> PerformNuGetV2RestoreAsync(Pac List packageRestoreData = new(); bool areAnyPackagesMissing = false; + Dictionary restoreAuditProperties = null; if (packageRestoreInputs.RestoringWithSolutionFile) { @@ -303,6 +303,7 @@ private async Task> PerformNuGetV2RestoreAsync(Pac packageRestoreData.Add(new PackageRestoreData(package.Key, package.Value, !exists)); areAnyPackagesMissing |= !exists; } + restoreAuditProperties = GetRestoreAuditProperties(packageRestoreInputs); } else if (packageRestoreInputs.PackagesConfigFiles.Count > 0) @@ -331,8 +332,15 @@ private async Task> PerformNuGetV2RestoreAsync(Pac packageRestoreData.Add(new PackageRestoreData(packageReference, [packageReferenceFile], !exists)); areAnyPackagesMissing |= !exists; } + restoreAuditProperties = new(); } + var packageSources = GetPackageSources(Settings); + + var repositories = packageSources + .Select(sourceRepositoryProvider.CreateRepository) + .ToList(); + if (!areAnyPackagesMissing) { var message = string.Format( @@ -350,6 +358,15 @@ private async Task> PerformNuGetV2RestoreAsync(Pac packagesFolderPath, restoreSummaries); + using SourceCacheContext cacheContext = new(); + + var auditUtility = new AuditChecker( + repositories, + cacheContext, + Console); + + await auditUtility.CheckPackageVulnerabilitiesAsync(packageRestoreData, restoreAuditProperties, CancellationToken.None); + if (restoreSummaries.Count == 0) { restoreSummaries.Add(new RestoreSummary(success: true)); @@ -358,12 +375,6 @@ private async Task> PerformNuGetV2RestoreAsync(Pac return restoreSummaries; } - var packageSources = GetPackageSources(Settings); - - var repositories = packageSources - .Select(sourceRepositoryProvider.CreateRepository) - .ToArray(); - var installCount = 0; var failedEvents = new ConcurrentQueue(); var collectorLogger = new RestoreCollectorLogger(Console); @@ -378,6 +389,8 @@ private async Task> PerformNuGetV2RestoreAsync(Pac maxNumberOfParallelTasks: DisableParallelProcessing ? 1 : PackageManagementConstants.DefaultMaxDegreeOfParallelism, + disableNuGetAudit: false, + restoreAuditProperties, logger: collectorLogger); CheckRequireConsent(); @@ -458,6 +471,20 @@ private static Dictionary GetPackagesConfigToProjectPath(Package return configToProjectPath; } + private static Dictionary GetRestoreAuditProperties(PackageRestoreInputs packageRestoreInputs) + { + Dictionary restoreAuditProperties = new(PathUtility.GetStringComparerBasedOnOS()); + foreach (PackageSpec project in packageRestoreInputs.ProjectReferenceLookup.Projects) + { + if (project.RestoreMetadata?.ProjectStyle == ProjectStyle.PackagesConfig) + { + restoreAuditProperties.Add(project.FilePath, project.RestoreMetadata.RestoreAuditProperties); + } + } + + return restoreAuditProperties; + } + /// /// Processes List of PackageRestoreFailedEventArgs into a List of RestoreLogMessages. /// diff --git a/src/NuGet.Core/NuGet.Build.Tasks/BuildTasksUtility.cs b/src/NuGet.Core/NuGet.Build.Tasks/BuildTasksUtility.cs index 45d623a6db5..d4570c20b76 100644 --- a/src/NuGet.Core/NuGet.Build.Tasks/BuildTasksUtility.cs +++ b/src/NuGet.Core/NuGet.Build.Tasks/BuildTasksUtility.cs @@ -31,6 +31,7 @@ using NuGet.Shared; using static NuGet.Shared.XmlUtility; using System.Globalization; +using System.Collections; #endif namespace NuGet.Build.Tasks @@ -422,6 +423,7 @@ private static async Task PerformNuGetV2RestoreAsync(Common.ILog ISettings settings = null; Dictionary> packageReferenceToProjects = new(PackageReferenceComparer.Instance); + Dictionary restoreAuditProperties = new(PathUtility.GetStringComparerBasedOnOS()); foreach (PackageSpec packageSpec in dgFile.Projects.Where(i => i.RestoreMetadata.ProjectStyle == ProjectStyle.PackagesConfig)) { @@ -456,6 +458,7 @@ private static async Task PerformNuGetV2RestoreAsync(Common.ILog } value.Add(packagesConfigPath); } + restoreAuditProperties.Add(packageSpec.FilePath, packageSpec.RestoreMetadata.RestoreAuditProperties); } if (string.IsNullOrEmpty(repositoryPath)) @@ -482,12 +485,22 @@ private static async Task PerformNuGetV2RestoreAsync(Common.ILog areAnyPackagesMissing |= !exists; } + var repositories = sourceRepositoryProvider.GetRepositories().ToList(); + if (!areAnyPackagesMissing) { + using SourceCacheContext cacheContext = new(); + + var auditUtility = new AuditChecker( + repositories, + cacheContext, + log); + + await auditUtility.CheckPackageVulnerabilitiesAsync(packageRestoreData, restoreAuditProperties, CancellationToken.None); + return new RestoreSummary(true); } - var repositories = sourceRepositoryProvider.GetRepositories().ToArray(); var installCount = 0; var failedEvents = new ConcurrentQueue(); @@ -503,6 +516,8 @@ private static async Task PerformNuGetV2RestoreAsync(Common.ILog maxNumberOfParallelTasks: disableParallel ? 1 : PackageManagementConstants.DefaultMaxDegreeOfParallelism, + disableNuGetAudit: false, + restoreAuditProperties, logger: collectorLogger); // TODO: Check require consent? diff --git a/src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets b/src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets index 33463f79f30..90a43784355 100644 --- a/src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets +++ b/src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets @@ -908,6 +908,8 @@ Copyright (c) .NET Foundation. All rights reserved. $(_OutputConfigFilePaths) $(_OutputPackagesPath) @(_RestoreTargetFrameworksOutputFiltered) + $(NuGetAudit) + $(NuGetAuditLevel) diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/RestoreCommand.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/RestoreCommand.cs index 0f8088157af..7b7b73d8b35 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/RestoreCommand.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/RestoreCommand.cs @@ -305,7 +305,7 @@ await _logger.LogAsync(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1803, } bool auditEnabled = AuditUtility.ParseEnableValue( - _request.Project.RestoreMetadata?.RestoreAuditProperties?.EnableAudit, + _request.Project.RestoreMetadata?.RestoreAuditProperties, _request.Project.FilePath, _logger); telemetry.TelemetryEvent[AuditEnabled] = auditEnabled ? "enabled" : "disabled"; diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/AuditUtility.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/AuditUtility.cs index f583cc419d3..0ed67c6614f 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/AuditUtility.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/AuditUtility.cs @@ -13,6 +13,7 @@ using NuGet.DependencyResolver; using NuGet.LibraryModel; using NuGet.Packaging.Core; +using NuGet.ProjectModel; using NuGet.Protocol; using NuGet.Protocol.Model; using NuGet.Versioning; @@ -363,27 +364,15 @@ private PackageVulnerabilitySeverity ParseAuditLevel() return PackageVulnerabilitySeverity.Low; } - if (string.Equals("low", auditLevel, StringComparison.OrdinalIgnoreCase)) + if (_restoreAuditProperties!.TryParseAuditLevel(out PackageVulnerabilitySeverity result)) { - return PackageVulnerabilitySeverity.Low; - } - if (string.Equals("moderate", auditLevel, StringComparison.OrdinalIgnoreCase)) - { - return PackageVulnerabilitySeverity.Moderate; - } - if (string.Equals("high", auditLevel, StringComparison.OrdinalIgnoreCase)) - { - return PackageVulnerabilitySeverity.High; - } - if (string.Equals("critical", auditLevel, StringComparison.OrdinalIgnoreCase)) - { - return PackageVulnerabilitySeverity.Critical; + return result; } string messageText = string.Format(Strings.Error_InvalidNuGetAuditLevelValue, auditLevel, "low, moderate, high, critical"); RestoreLogMessage message = RestoreLogMessage.CreateError(NuGetLogCode.NU1014, messageText); _logger.Log(message); - return 0; + return PackageVulnerabilitySeverity.Low; } internal enum NuGetAuditMode { Unknown, Direct, All } @@ -414,27 +403,23 @@ private NuGetAuditMode ParseAuditMode() } // Enum parsing and ToString are a magnitude of times slower than a naive implementation. - public static bool ParseEnableValue(string? value, string projectFullPath, ILogger logger) + public static bool ParseEnableValue(RestoreAuditProperties? value, string projectFullPath, ILogger logger) { - // Earlier versions allowed "enable" and "default" to opt-in - if (string.IsNullOrEmpty(value) - || string.Equals(value, bool.TrueString, StringComparison.OrdinalIgnoreCase) - || string.Equals(value, "enable", StringComparison.OrdinalIgnoreCase) - || string.Equals(value, "default", StringComparison.OrdinalIgnoreCase)) + if (value != null) { return true; + } - if (string.Equals(value, bool.FalseString, StringComparison.OrdinalIgnoreCase) - || string.Equals(value, "disable", StringComparison.OrdinalIgnoreCase)) + + if (!value!.TryParseEnableAudit(out bool result)) { - return false; + string messageText = string.Format(Strings.Error_InvalidNuGetAuditValue, value, "true, false"); + RestoreLogMessage message = RestoreLogMessage.CreateError(NuGetLogCode.NU1014, messageText); + message.ProjectPath = projectFullPath; + logger.Log(message); } - string messageText = string.Format(Strings.Error_InvalidNuGetAuditValue, value, "true, false"); - RestoreLogMessage message = RestoreLogMessage.CreateError(NuGetLogCode.NU1014, messageText); - message.ProjectPath = projectFullPath; - logger.Log(message); - return true; + return result; } // Enum parsing and ToString are a magnitude of times slower than a naive implementation. diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/MSBuildRestoreUtility.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/MSBuildRestoreUtility.cs index baa6d8f6aaf..2e53d498293 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/MSBuildRestoreUtility.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/MSBuildRestoreUtility.cs @@ -285,7 +285,7 @@ public static PackageSpec GetPackageSpec(IEnumerable items) ); } pcRestoreMetadata.RestoreLockProperties = GetRestoreLockProperties(specItem); - + pcRestoreMetadata.RestoreAuditProperties = GetRestoreAuditProperties(specItem); } if (restoreType == ProjectStyle.ProjectJson) diff --git a/src/NuGet.Core/NuGet.PackageManagement/Audit/AuditCheckResult.cs b/src/NuGet.Core/NuGet.PackageManagement/Audit/AuditCheckResult.cs new file mode 100644 index 00000000000..b74c034a974 --- /dev/null +++ b/src/NuGet.Core/NuGet.PackageManagement/Audit/AuditCheckResult.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable enable + +using System; +using System.Collections.Generic; +using NuGet.Common; +using NuGet.Packaging.Core; + +namespace NuGet.PackageManagement +{ + public record AuditCheckResult + { + public IReadOnlyList Warnings { get; } + internal bool IsAuditEnabled { get; set; } = true; + + internal int Severity0VulnerabilitiesFound { get; set; } + internal int Severity1VulnerabilitiesFound { get; set; } + internal int Severity2VulnerabilitiesFound { get; set; } + internal int Severity3VulnerabilitiesFound { get; set; } + internal int InvalidSeverityVulnerabilitiesFound { get; set; } + internal List? Packages { get; set; } + internal double? DownloadDurationInSeconds { get; set; } + internal double? CheckPackagesDurationInSeconds { get; set; } + internal int SourcesWithVulnerabilities { get; set; } + + private const string AuditVulnerabilitiesStatus = "PackagesConfig.Audit.Enabled"; + private const string AuditVulnerabilitiesCount = "PackagesConfig.Audit.Vulnerability.Count"; + private const string AuditVulnerabilitiesSev0Count = "PackagesConfig.Audit.Vulnerability.Severity0.Count"; + private const string AuditVulnerabilitiesSev1Count = "PackagesConfig.Audit.Vulnerability.Severity1.Count"; + private const string AuditVulnerabilitiesSev2Count = "PackagesConfig.Audit.Vulnerability.Severity2.Count"; + private const string AuditVulnerabilitiesSev3Count = "PackagesConfig.Audit.Vulnerability.Severity3.Count"; + private const string AuditVulnerabilitiesInvalidSeverityCount = "PackagesConfig.Audit.Vulnerability.SeverityInvalid.Count"; + private const string AuditDurationDownload = "PackagesConfig.Audit.Duration.Download"; + private const string AuditDurationCheck = "PackagesConfig.Audit.Duration.Check"; + private const string SourcesWithVulnerabilitiesCount = "PackagesConfig.Audit.DataSources.Count"; + private const string AuditVulnerabilitiesPackages = "PackagesConfig.Audit.Vulnerability.Packages"; + + + public AuditCheckResult(IReadOnlyList warnings) + { + if (warnings is null) + { + throw new ArgumentNullException(nameof(warnings)); + } + + Warnings = warnings; + } + + public void AddMetricsToTelemetry(TelemetryEvent telemetryEvent) + { + telemetryEvent[AuditVulnerabilitiesStatus] = IsAuditEnabled; + telemetryEvent[AuditVulnerabilitiesSev0Count] = Severity0VulnerabilitiesFound; + telemetryEvent[AuditVulnerabilitiesSev1Count] = Severity1VulnerabilitiesFound; + telemetryEvent[AuditVulnerabilitiesSev2Count] = Severity2VulnerabilitiesFound; + telemetryEvent[AuditVulnerabilitiesSev3Count] = Severity3VulnerabilitiesFound; + telemetryEvent[AuditVulnerabilitiesInvalidSeverityCount] = InvalidSeverityVulnerabilitiesFound; + telemetryEvent[SourcesWithVulnerabilitiesCount] = SourcesWithVulnerabilities; + telemetryEvent[AuditVulnerabilitiesCount] = Packages?.Count ?? 0; + + if (DownloadDurationInSeconds.HasValue) + { + telemetryEvent[AuditDurationDownload] = DownloadDurationInSeconds; + } + if (CheckPackagesDurationInSeconds.HasValue) + { + telemetryEvent[AuditDurationCheck] = CheckPackagesDurationInSeconds; + } + + if (Packages is not null) + { + List result = new List(Packages.Count); + foreach (var package in Packages) + { + TelemetryEvent packageData = new TelemetryEvent(eventName: string.Empty); + packageData.AddPiiData("id", package.Id.ToLowerInvariant()); + packageData["version"] = package.Version; + result.Add(packageData); + } + telemetryEvent.ComplexData[AuditVulnerabilitiesPackages] = result; + } + } + } +} diff --git a/src/NuGet.Core/NuGet.PackageManagement/AuditUtility.cs b/src/NuGet.Core/NuGet.PackageManagement/Audit/AuditChecker.cs similarity index 53% rename from src/NuGet.Core/NuGet.PackageManagement/AuditUtility.cs rename to src/NuGet.Core/NuGet.PackageManagement/Audit/AuditChecker.cs index 4ceab987c69..fe4973d01ea 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/AuditUtility.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Audit/AuditChecker.cs @@ -14,34 +14,49 @@ using NuGet.Protocol.Model; using NuGet.Versioning; using NuGet.Protocol.Core.Types; +using System.Diagnostics; +using NuGet.ProjectModel; namespace NuGet.PackageManagement { - internal class AuditUtility + public class AuditChecker( + List sourceRepositories, + SourceCacheContext sourceCacheContext, + ILogger logger) { - private readonly IEnumerable _packages; - private readonly List _sourceRepositories; - private readonly ILogger _logger; - private readonly SourceCacheContext _sourceCacheContext; - private readonly PackageVulnerabilitySeverity _minSeverity; - - public AuditUtility( - PackageVulnerabilitySeverity minSeverity, - IEnumerable packages, - List sourceRepositories, - SourceCacheContext sourceCacheContext, - ILogger logger) - { - _minSeverity = minSeverity; - _packages = packages; - _sourceRepositories = sourceRepositories; - _sourceCacheContext = sourceCacheContext; - _logger = logger; - } + private readonly List _sourceRepositories = sourceRepositories; + private readonly ILogger _logger = logger; + private readonly SourceCacheContext _sourceCacheContext = sourceCacheContext; - public async Task CheckPackageVulnerabilitiesAsync(CancellationToken cancellationToken) + public async Task CheckPackageVulnerabilitiesAsync(IEnumerable packages, Dictionary restoreAuditProperties, CancellationToken cancellationToken) { - GetVulnerabilityInfoResult? allVulnerabilityData = await GetAllVulnerabilityDataAsync(_sourceRepositories, _sourceCacheContext, _logger, cancellationToken); + if (packages == null) throw new ArgumentNullException(nameof(packages)); + if (restoreAuditProperties == null) throw new ArgumentNullException(nameof(restoreAuditProperties)); + + // Before fetching vulnerability data, check if any projects are enabled for audit + // If there are no settings, then run the audit for all packages + bool anyProjectsEnabledForAudit = restoreAuditProperties.Count == 0; + var auditSettings = new Dictionary(restoreAuditProperties.Count); + foreach (var (projectPath, restoreAuditProperty) in restoreAuditProperties) + { + _ = restoreAuditProperty.TryParseEnableAudit(out bool isAuditEnabled); + _ = restoreAuditProperty.TryParseAuditLevel(out PackageVulnerabilitySeverity minimumAuditSeverity); + auditSettings.Add(projectPath, (isAuditEnabled, minimumAuditSeverity)); + anyProjectsEnabledForAudit |= isAuditEnabled; + } + + if (!anyProjectsEnabledForAudit) + { + return new AuditCheckResult(Array.Empty()) + { + IsAuditEnabled = false, + }; + } + + Stopwatch stopwatch = Stopwatch.StartNew(); + (int sourceWithVulnerabilityCount, GetVulnerabilityInfoResult? allVulnerabilityData) = await GetAllVulnerabilityDataAsync(_sourceRepositories, _sourceCacheContext, _logger, cancellationToken); + stopwatch.Stop(); + double downloadDurationInSeconds = stopwatch.Elapsed.TotalSeconds; if (allVulnerabilityData?.Exceptions is not null) { @@ -55,18 +70,44 @@ public async Task CheckPackageVulnerabilitiesAsync(CancellationToken cancellatio if (allVulnerabilityData is null || !IsAnyVulnerabilityDataFound(allVulnerabilityData.KnownVulnerabilities)) { - return; + return new AuditCheckResult(Array.Empty()) + { + DownloadDurationInSeconds = downloadDurationInSeconds, + SourcesWithVulnerabilities = sourceWithVulnerabilityCount, + }; } - Dictionary? packagesWithKnownVulnerabilities = - FindPackagesWithKnownVulnerabilities(allVulnerabilityData.KnownVulnerabilities!, - _packages, - _minSeverity); - if (packagesWithKnownVulnerabilities is not null) + stopwatch.Restart(); + Dictionary? packagesWithKnownVulnerabilities = FindPackagesWithKnownVulnerabilities(allVulnerabilityData.KnownVulnerabilities!, packages); + int Sev0Matches = 0, Sev1Matches = 0, Sev2Matches = 0, Sev3Matches = 0, InvalidSevMatches = 0; + + List packagesWithReportedAdvisories = new(packagesWithKnownVulnerabilities?.Count ?? 0); + + IReadOnlyList warnings = packagesWithKnownVulnerabilities is not null ? + CreateWarnings(packagesWithKnownVulnerabilities, auditSettings, ref Sev0Matches, ref Sev1Matches, ref Sev2Matches, ref Sev3Matches, ref InvalidSevMatches, ref packagesWithReportedAdvisories) : + Array.Empty(); + + foreach (var warning in warnings) { - CreateWarningsForPackagesWithVulnerabilities(packagesWithKnownVulnerabilities, _logger); + _logger.Log(warning); } + stopwatch.Stop(); + double checkPackagesDurationInSeconds = stopwatch.Elapsed.TotalSeconds; + + return new AuditCheckResult(warnings) + { + Severity0VulnerabilitiesFound = Sev0Matches, + Severity1VulnerabilitiesFound = Sev1Matches, + Severity2VulnerabilitiesFound = Sev2Matches, + Severity3VulnerabilitiesFound = Sev3Matches, + InvalidSeverityVulnerabilitiesFound = InvalidSevMatches, + Packages = packagesWithReportedAdvisories, + DownloadDurationInSeconds = downloadDurationInSeconds, + CheckPackagesDurationInSeconds = checkPackagesDurationInSeconds, + SourcesWithVulnerabilities = sourceWithVulnerabilityCount, + }; + static bool IsAnyVulnerabilityDataFound(IReadOnlyList>>? knownVulnerabilities) { if (knownVulnerabilities is null || knownVulnerabilities.Count == 0) @@ -82,8 +123,9 @@ static bool IsAnyVulnerabilityDataFound(IReadOnlyList GetAllVulnerabilityDataAsync(List sourceRepositories, SourceCacheContext sourceCacheContext, ILogger logger, CancellationToken cancellationToken) + internal static async Task<(int, GetVulnerabilityInfoResult?)> GetAllVulnerabilityDataAsync(List sourceRepositories, SourceCacheContext sourceCacheContext, ILogger logger, CancellationToken cancellationToken) { + int SourcesWithVulnerabilityData = 0; List>? results = new(sourceRepositories.Count); foreach (SourceRepository source in sourceRepositories) @@ -110,20 +152,15 @@ static bool IsAnyVulnerabilityDataFound(IReadOnlyList GetVulnerabilityInfoAsync(SourceRepository source, SourceCacheContext cacheContext, ILogger logger) { @@ -156,10 +193,19 @@ static bool IsAnyVulnerabilityDataFound(IReadOnlyList packagesWithKnownVulnerabilities, ILogger logger) + internal static List CreateWarnings(Dictionary packagesWithKnownVulnerabilities, + Dictionary auditSettings, + ref int Sev0Matches, + ref int Sev1Matches, + ref int Sev2Matches, + ref int Sev3Matches, + ref int InvalidSevMatches, + ref List packagesWithReportedAdvisories) { + var warnings = new List(); foreach ((PackageIdentity package, PackageAuditInfo auditInfo) in packagesWithKnownVulnerabilities.OrderBy(p => p.Key.Id)) { + bool counted = false; foreach (PackageVulnerabilityInfo vulnerability in auditInfo.Vulnerabilities) { (var severityLabel, NuGetLogCode logCode) = GetSeverityLabelAndCode(vulnerability.Severity); @@ -168,18 +214,54 @@ internal static void CreateWarningsForPackagesWithVulnerabilities(Dictionary= (int)auditSetting.Item2) + { + if (!counted) + { + switch (vulnerability.Severity) + { + case PackageVulnerabilitySeverity.Low: + Sev0Matches++; + break; + case PackageVulnerabilitySeverity.Moderate: + Sev1Matches++; + break; + case PackageVulnerabilitySeverity.High: + Sev2Matches++; + break; + case PackageVulnerabilitySeverity.Critical: + Sev3Matches++; + break; + default: + InvalidSevMatches++; + break; + } + counted = true; + } + + var restoreLogMessage = LogMessage.CreateWarning(logCode, message); + restoreLogMessage.ProjectPath = projectPath; + warnings.Add(restoreLogMessage); + } + + if (counted) + { + packagesWithReportedAdvisories.Add(package); + } + } } } + return warnings; } internal static Dictionary? FindPackagesWithKnownVulnerabilities( IReadOnlyList>> knownVulnerabilities, - IEnumerable packages, PackageVulnerabilitySeverity minSeverity) + IEnumerable packages) { Dictionary? result = null; @@ -192,16 +274,11 @@ internal static void CreateWarningsForPackagesWithVulnerabilities(Dictionary Projects { get; } + public List Vulnerabilities { get; } - public PackageAuditInfo(PackageIdentity identity) + public PackageAuditInfo(PackageIdentity identity, IEnumerable projects) { Identity = identity; Vulnerabilities = new(); + Projects = projects; } } } diff --git a/src/NuGet.Core/NuGet.PackageManagement/GlobalSuppressions.cs b/src/NuGet.Core/NuGet.PackageManagement/GlobalSuppressions.cs index 7a778aa94f6..bdbfc6a6ab7 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/GlobalSuppressions.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/GlobalSuppressions.cs @@ -43,8 +43,6 @@ [assembly: SuppressMessage("Build", "CA1062:In externally visible method 'bool PackageReferenceComparer.Equals(PackageReference x, PackageReference y)', validate parameter 'y' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PackageReferenceComparer.Equals(NuGet.Packaging.PackageReference,NuGet.Packaging.PackageReference)~System.Boolean")] [assembly: SuppressMessage("Build", "CA1062:In externally visible method 'int PackageReferenceComparer.GetHashCode(PackageReference obj)', validate parameter 'obj' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PackageReferenceComparer.GetHashCode(NuGet.Packaging.PackageReference)~System.Int32")] [assembly: SuppressMessage("Build", "CA1031:Modify 'GetPackagesReferencesDictionaryAsync' to catch a more specific allowed exception type, or rethrow the exception.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PackageRestoreManager.GetPackagesReferencesDictionaryAsync(System.Threading.CancellationToken)~System.Threading.Tasks.Task{System.Collections.Generic.Dictionary{NuGet.Packaging.PackageReference,System.Collections.Generic.List{System.String}}}")] -[assembly: SuppressMessage("Build", "CA1062:In externally visible method 'Task PackageRestoreManager.RestoreMissingPackagesAsync(string solutionDirectory, IEnumerable packages, INuGetProjectContext nuGetProjectContext, PackageDownloadContext downloadContext, ILogger logger, CancellationToken token)', validate parameter 'nuGetProjectContext' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PackageRestoreManager.RestoreMissingPackagesAsync(System.String,System.Collections.Generic.IEnumerable{NuGet.PackageManagement.PackageRestoreData},NuGet.ProjectManagement.INuGetProjectContext,NuGet.Protocol.Core.Types.PackageDownloadContext,NuGet.Common.ILogger,System.Threading.CancellationToken)~System.Threading.Tasks.Task{NuGet.PackageManagement.PackageRestoreResult}")] -[assembly: SuppressMessage("Build", "CA1062:In externally visible method 'Task PackageRestoreManager.RestoreMissingPackagesAsync(string solutionDirectory, IEnumerable packages, INuGetProjectContext nuGetProjectContext, PackageDownloadContext downloadContext, CancellationToken token)', validate parameter 'nuGetProjectContext' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PackageRestoreManager.RestoreMissingPackagesAsync(System.String,System.Collections.Generic.IEnumerable{NuGet.PackageManagement.PackageRestoreData},NuGet.ProjectManagement.INuGetProjectContext,NuGet.Protocol.Core.Types.PackageDownloadContext,System.Threading.CancellationToken)~System.Threading.Tasks.Task{NuGet.PackageManagement.PackageRestoreResult}")] [assembly: SuppressMessage("Build", "CA1031:Modify 'RestorePackageAsync' to catch a more specific allowed exception type, or rethrow the exception.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PackageRestoreManager.RestorePackageAsync(NuGet.Packaging.PackageReference,NuGet.PackageManagement.PackageRestoreContext,NuGet.ProjectManagement.INuGetProjectContext,NuGet.Protocol.Core.Types.PackageDownloadContext)~System.Threading.Tasks.Task{NuGet.PackageManagement.PackageRestoreManager.AttemptedPackage}")] [assembly: SuppressMessage("Build", "CA1822:Member GetNupkgMetadataPath does not access instance data and can be marked as static (Shared in VisualBasic)", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PackagesConfigContentHashProvider.GetNupkgMetadataPath(System.String)~System.String")] [assembly: SuppressMessage("Build", "CA1062:In externally visible method 'void ProjectContextLogger.Log(ILogMessage message)', validate parameter 'message' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.ProjectContextLogger.Log(NuGet.Common.ILogMessage)")] @@ -55,7 +53,6 @@ [assembly: SuppressMessage("Build", "CA1062:In externally visible method 'IEnumerable PrunePackageTree.PruneDisallowedVersions(IEnumerable packages, IEnumerable packageReferences)', validate parameter 'packageReferences' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PrunePackageTree.PruneDisallowedVersions(System.Collections.Generic.IEnumerable{NuGet.Protocol.Core.Types.SourcePackageDependencyInfo},System.Collections.Generic.IEnumerable{NuGet.Packaging.PackageReference})~System.Collections.Generic.IEnumerable{NuGet.Protocol.Core.Types.SourcePackageDependencyInfo}")] [assembly: SuppressMessage("Build", "CA1062:In externally visible method 'IEnumerable PrunePackageTree.PruneDowngrades(IEnumerable packages, IEnumerable packageReferences)', validate parameter 'packageReferences' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PrunePackageTree.PruneDowngrades(System.Collections.Generic.IEnumerable{NuGet.Protocol.Core.Types.SourcePackageDependencyInfo},System.Collections.Generic.IEnumerable{NuGet.Packaging.PackageReference})~System.Collections.Generic.IEnumerable{NuGet.Protocol.Core.Types.SourcePackageDependencyInfo}")] [assembly: SuppressMessage("Build", "CA1062:In externally visible method 'IEnumerable PrunePackageTree.RemoveDisallowedVersions(IEnumerable packages, PackageReference packageReference)', validate parameter 'packageReference' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.PrunePackageTree.RemoveDisallowedVersions(System.Collections.Generic.IEnumerable{NuGet.Protocol.Core.Types.SourcePackageDependencyInfo},NuGet.Packaging.PackageReference)~System.Collections.Generic.IEnumerable{NuGet.Protocol.Core.Types.SourcePackageDependencyInfo}")] -[assembly: SuppressMessage("Build", "CA1062:In externally visible method 'Task> ResolverGather.GatherAsync(GatherContext context, CancellationToken token)', validate parameter 'context' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.ResolverGather.GatherAsync(NuGet.PackageManagement.GatherContext,System.Threading.CancellationToken)~System.Threading.Tasks.Task{System.Collections.Generic.HashSet{NuGet.Protocol.Core.Types.SourcePackageDependencyInfo}}")] [assembly: SuppressMessage("Build", "CA1062:In externally visible method 'bool SourceRepositoryComparer.Equals(SourceRepository x, SourceRepository y)', validate parameter 'x' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.SourceRepositoryComparer.Equals(NuGet.Protocol.Core.Types.SourceRepository,NuGet.Protocol.Core.Types.SourceRepository)~System.Boolean")] [assembly: SuppressMessage("Build", "CA1062:In externally visible method 'int SourceRepositoryComparer.GetHashCode(SourceRepository obj)', validate parameter 'obj' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.SourceRepositoryComparer.GetHashCode(NuGet.Protocol.Core.Types.SourceRepository)~System.Int32")] [assembly: SuppressMessage("Build", "CA1062:In externally visible method 'IDictionary> UninstallResolver.GetPackageDependents(IEnumerable dependencyInfoEnumerable, IEnumerable installedPackages, out IDictionary> dependenciesDict)', validate parameter 'dependencyInfoEnumerable' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.UninstallResolver.GetPackageDependents(System.Collections.Generic.IEnumerable{NuGet.Packaging.Core.PackageDependencyInfo},System.Collections.Generic.IEnumerable{NuGet.Packaging.Core.PackageIdentity},System.Collections.Generic.IDictionary{NuGet.Packaging.Core.PackageIdentity,System.Collections.Generic.HashSet{NuGet.Packaging.Core.PackageIdentity}}@)~System.Collections.Generic.IDictionary{NuGet.Packaging.Core.PackageIdentity,System.Collections.Generic.HashSet{NuGet.Packaging.Core.PackageIdentity}}")] @@ -122,3 +119,4 @@ [assembly: SuppressMessage("Build", "CA2237:Add [Serializable] to PackageReferenceRollbackException as this type implements ISerializable", Justification = "", Scope = "type", Target = "~T:NuGet.PackageManagement.PackageReferenceRollbackException")] [assembly: SuppressMessage("Build", "CA1067:Type NuGet.ProjectManagement.FileTransformExtensions should override Equals because it implements IEquatable", Justification = "", Scope = "type", Target = "~T:NuGet.ProjectManagement.FileTransformExtensions")] [assembly: SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "", Scope = "member", Target = "~M:NuGet.PackageManagement.NuGetProjectAction.#ctor(NuGet.Packaging.Core.PackageIdentity,NuGet.PackageManagement.NuGetProjectActionType,NuGet.ProjectManagement.NuGetProject,NuGet.Protocol.Core.Types.SourceRepository)")] +[assembly: SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Packages are normalized to lowercase", Scope = "member", Target = "~M:NuGet.PackageManagement.AuditCheckResult.AddMetricsToTelemetry(NuGet.Common.TelemetryEvent)")] diff --git a/src/NuGet.Core/NuGet.PackageManagement/IDE/IPackageRestoreManager.cs b/src/NuGet.Core/NuGet.PackageManagement/IDE/IPackageRestoreManager.cs index 00790ae8218..e6fe1939b16 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/IDE/IPackageRestoreManager.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/IDE/IPackageRestoreManager.cs @@ -85,6 +85,7 @@ Task RestoreMissingPackagesInSolutionAsync(string solution /// are missing /// /// Returns true if atleast one package was restored. + [Obsolete("This method is deprecated to reduce complexity, please use one of the other RestoreMissingPackagesAsync methods.")] Task RestoreMissingPackagesInSolutionAsync(string solutionDirectory, INuGetProjectContext nuGetProjectContext, CancellationToken token); @@ -128,6 +129,7 @@ Task RestoreMissingPackagesAsync(string solutionDirectory, /// Returns true if at least one package is restored. Raised package restored failed event with the /// list of project names. /// + [Obsolete("This method is deprecated to reduce complexity, please use one of the other RestoreMissingPackagesAsync methods.")] Task RestoreMissingPackagesAsync(string solutionDirectory, IEnumerable packages, INuGetProjectContext nuGetProjectContext, diff --git a/src/NuGet.Core/NuGet.PackageManagement/IDE/PackageRestoreContext.cs b/src/NuGet.Core/NuGet.PackageManagement/IDE/PackageRestoreContext.cs index a174fc2a188..79e261bdb04 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/IDE/PackageRestoreContext.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/IDE/PackageRestoreContext.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Threading; using NuGet.Common; +using NuGet.ProjectModel; using NuGet.Protocol.Core.Types; namespace NuGet.PackageManagement @@ -19,6 +20,8 @@ public class PackageRestoreContext public IEnumerable SourceRepositories { get; } public int MaxNumberOfParallelTasks { get; } public ILogger Logger { get; } + public bool DisableNuGetAudit { get; } + public Dictionary RestoreAuditProperties { get; } public PackageRestoreContext(NuGetPackageManager nuGetPackageManager, IEnumerable packages, @@ -27,6 +30,19 @@ public PackageRestoreContext(NuGetPackageManager nuGetPackageManager, EventHandler packageRestoreFailedEvent, IEnumerable sourceRepositories, int maxNumberOfParallelTasks, + ILogger logger) : this(nuGetPackageManager, packages, token, packageRestoredEvent, packageRestoreFailedEvent, sourceRepositories, maxNumberOfParallelTasks, true, new Dictionary(), logger) + { + } + + public PackageRestoreContext(NuGetPackageManager nuGetPackageManager, + IEnumerable packages, + CancellationToken token, + EventHandler packageRestoredEvent, + EventHandler packageRestoreFailedEvent, + IEnumerable sourceRepositories, + int maxNumberOfParallelTasks, + bool disableNuGetAudit, + Dictionary restoreAuditProperties, ILogger logger) { if (maxNumberOfParallelTasks <= 0) @@ -42,6 +58,8 @@ public PackageRestoreContext(NuGetPackageManager nuGetPackageManager, PackageRestoreFailedEvent = packageRestoreFailedEvent; SourceRepositories = sourceRepositories; MaxNumberOfParallelTasks = maxNumberOfParallelTasks; + DisableNuGetAudit = disableNuGetAudit; + RestoreAuditProperties = restoreAuditProperties ?? throw new ArgumentNullException(nameof(restoreAuditProperties)); } } } diff --git a/src/NuGet.Core/NuGet.PackageManagement/IDE/PackageRestoreManager.cs b/src/NuGet.Core/NuGet.PackageManagement/IDE/PackageRestoreManager.cs index 914d5fef5e1..3362e9c8964 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/IDE/PackageRestoreManager.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/IDE/PackageRestoreManager.cs @@ -17,7 +17,9 @@ using NuGet.Packaging.Signing; using NuGet.ProjectManagement; using NuGet.ProjectManagement.Projects; +using NuGet.ProjectModel; using NuGet.Protocol.Core.Types; +using NuGet.Shared; namespace NuGet.PackageManagement { @@ -180,6 +182,7 @@ private async Task>> GetPackagesRefere /// Restores missing packages for the entire solution /// /// + [Obsolete("This method is deprecated to reduce complexity, please use one of the other RestoreMissingPackagesAsync methods.")] public virtual async Task RestoreMissingPackagesInSolutionAsync( string solutionDirectory, INuGetProjectContext nuGetProjectContext, @@ -224,6 +227,8 @@ public virtual async Task RestoreMissingPackagesInSolution ILogger logger, CancellationToken token) { + if (nuGetProjectContext == null) throw new ArgumentNullException(nameof(nuGetProjectContext)); + var packageReferencesDictionary = await GetPackagesReferencesDictionaryAsync(token); // When this method is called, the step to compute if a package is missing is implicit. Assume it is true @@ -253,16 +258,15 @@ public virtual async Task RestoreMissingPackagesInSolution } } + [Obsolete("This method is deprecated to reduce complexity, please use one of the other RestoreMissingPackagesAsync methods.")] public virtual Task RestoreMissingPackagesAsync(string solutionDirectory, IEnumerable packages, INuGetProjectContext nuGetProjectContext, PackageDownloadContext downloadContext, CancellationToken token) { - if (packages == null) - { - throw new ArgumentNullException(nameof(packages)); - } + if (packages == null) throw new ArgumentNullException(nameof(packages)); + if (nuGetProjectContext == null) throw new ArgumentNullException(nameof(nuGetProjectContext)); var nuGetPackageManager = GetNuGetPackageManager(solutionDirectory); @@ -274,6 +278,8 @@ public virtual Task RestoreMissingPackagesAsync(string sol PackageRestoreFailedEvent, sourceRepositories: SourceRepositoryProvider.GetRepositories(), maxNumberOfParallelTasks: PackageManagementConstants.DefaultMaxDegreeOfParallelism, + disableNuGetAudit: false, + restoreAuditProperties: new Dictionary(), logger: NullLogger.Instance); if (nuGetProjectContext.PackageExtractionContext == null) @@ -361,6 +367,18 @@ public static async Task RestoreMissingPackagesAsync( ActivityCorrelationId.StartNew(); + List sourceRepositories = packageRestoreContext.SourceRepositories.AsList(); + + if (!packageRestoreContext.DisableNuGetAudit) + { + using SourceCacheContext sourceCacheContext = new(); + var auditUtility = new AuditChecker( + sourceRepositories, + sourceCacheContext, + packageRestoreContext.Logger); + await auditUtility.CheckPackageVulnerabilitiesAsync(packageRestoreContext.Packages, packageRestoreContext.RestoreAuditProperties, packageRestoreContext.Token); + } + var missingPackages = packageRestoreContext.Packages.Where(p => p.IsMissing).ToList(); if (!missingPackages.Any()) { @@ -376,7 +394,7 @@ public static async Task RestoreMissingPackagesAsync( packageRestoreContext.Token.ThrowIfCancellationRequested(); - foreach (SourceRepository enabledSource in packageRestoreContext.SourceRepositories) + foreach (SourceRepository enabledSource in sourceRepositories) { PackageSource source = enabledSource.PackageSource; if (source.IsHttp && !source.IsHttps && !source.AllowInsecureConnections) diff --git a/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt index 7dc5c58110b..0e0c2ae81c7 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt @@ -1 +1,11 @@ #nullable enable +NuGet.PackageManagement.AuditChecker +NuGet.PackageManagement.AuditChecker.AuditChecker(System.Collections.Generic.List! sourceRepositories, NuGet.Protocol.Core.Types.SourceCacheContext! sourceCacheContext, NuGet.Common.ILogger! logger) -> void +NuGet.PackageManagement.AuditChecker.CheckPackageVulnerabilitiesAsync(System.Collections.Generic.IEnumerable! packages, System.Collections.Generic.Dictionary! restoreAuditProperties, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +NuGet.PackageManagement.AuditCheckResult +NuGet.PackageManagement.AuditCheckResult.AddMetricsToTelemetry(NuGet.Common.TelemetryEvent! telemetryEvent) -> void +NuGet.PackageManagement.AuditCheckResult.AuditCheckResult(System.Collections.Generic.IReadOnlyList! warnings) -> void +NuGet.PackageManagement.AuditCheckResult.Warnings.get -> System.Collections.Generic.IReadOnlyList! +NuGet.PackageManagement.PackageRestoreContext.DisableNuGetAudit.get -> bool +~NuGet.PackageManagement.PackageRestoreContext.PackageRestoreContext(NuGet.PackageManagement.NuGetPackageManager nuGetPackageManager, System.Collections.Generic.IEnumerable packages, System.Threading.CancellationToken token, System.EventHandler packageRestoredEvent, System.EventHandler packageRestoreFailedEvent, System.Collections.Generic.IEnumerable sourceRepositories, int maxNumberOfParallelTasks, bool disableNuGetAudit, System.Collections.Generic.Dictionary restoreAuditProperties, NuGet.Common.ILogger logger) -> void +~NuGet.PackageManagement.PackageRestoreContext.RestoreAuditProperties.get -> System.Collections.Generic.Dictionary diff --git a/src/NuGet.Core/NuGet.PackageManagement/Resolution/ResolverGather.cs b/src/NuGet.Core/NuGet.PackageManagement/Resolution/ResolverGather.cs index 4581b41f8ed..6b075cc2734 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Resolution/ResolverGather.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Resolution/ResolverGather.cs @@ -75,6 +75,7 @@ public static async Task> GatherAsync( GatherContext context, CancellationToken token) { + if (context == null) throw new ArgumentNullException(nameof(context)); var engine = new ResolverGather(context); return await engine.GatherAsync(token); } diff --git a/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt index c1bae6a05a4..f6d93463dd9 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt @@ -1,2 +1,4 @@ #nullable enable +NuGet.ProjectModel.RestoreAuditProperties.TryParseAuditLevel(out NuGet.Protocol.PackageVulnerabilitySeverity result) -> bool +NuGet.ProjectModel.RestoreAuditProperties.TryParseEnableAudit(out bool result) -> bool ~NuGet.ProjectModel.HashObjectWriter.WriteNonEmptyNameArray(string name, System.Collections.Generic.IEnumerable values) -> void diff --git a/src/NuGet.Core/NuGet.ProjectModel/RestoreAuditProperties.cs b/src/NuGet.Core/NuGet.ProjectModel/RestoreAuditProperties.cs index c2b8d1728eb..4e4862f2158 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/RestoreAuditProperties.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/RestoreAuditProperties.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using NuGet.Protocol; using NuGet.Shared; namespace NuGet.ProjectModel @@ -27,6 +28,62 @@ public class RestoreAuditProperties : IEquatable /// direct, all public string? AuditMode { get; set; } + // Enum parsing and ToString are a magnitude of times slower than a naive implementation. + public bool TryParseEnableAudit(out bool result) + { + // Earlier versions allowed "enable" and "default" to opt-in + if (string.IsNullOrEmpty(EnableAudit) + || string.Equals(EnableAudit, bool.TrueString, StringComparison.OrdinalIgnoreCase) + || string.Equals(EnableAudit, "enable", StringComparison.OrdinalIgnoreCase) + || string.Equals(EnableAudit, "default", StringComparison.OrdinalIgnoreCase)) + { + result = true; + return true; + } + if (string.Equals(EnableAudit, bool.FalseString, StringComparison.OrdinalIgnoreCase) + || string.Equals(EnableAudit, "disable", StringComparison.OrdinalIgnoreCase)) + { + result = false; + return true; + } + result = true; + + return false; + } + + public bool TryParseAuditLevel(out PackageVulnerabilitySeverity result) + { + if (AuditLevel == null) + { + result = PackageVulnerabilitySeverity.Low; + return true; + } + + if (string.Equals(AuditLevel, "low", StringComparison.OrdinalIgnoreCase)) + { + result = PackageVulnerabilitySeverity.Low; + return true; + } + if (string.Equals(AuditLevel, "moderate", StringComparison.OrdinalIgnoreCase)) + { + result = PackageVulnerabilitySeverity.Moderate; + return true; + } + if (string.Equals(AuditLevel, "high", StringComparison.OrdinalIgnoreCase)) + { + result = PackageVulnerabilitySeverity.High; + return true; + } + if (string.Equals(AuditLevel, "critical", StringComparison.OrdinalIgnoreCase)) + { + result = PackageVulnerabilitySeverity.Critical; + return true; + } + + result = PackageVulnerabilitySeverity.Unknown; + return false; + } + public bool Equals(RestoreAuditProperties? other) { if (other is null) return false; diff --git a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/AuditUtilityTests.cs b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/AuditUtilityTests.cs index 1ce0ad63b85..5fc5a944dab 100644 --- a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/AuditUtilityTests.cs +++ b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/AuditUtilityTests.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +# if IS_SOMETHING + #nullable enable using System; @@ -583,3 +585,4 @@ public void CreateWarningsForPackagesWithVulnerabilities_CreatesWarningsForAllVu } } } +#endif