Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal sealed partial class FeedManager : IDisposable
/// <summary>
/// Gets whether the reachability of the NuGet feeds should be checked before using them for restore.
/// </summary>
public bool CheckNugetFeedResponsiveness { get; } = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness);
public bool CheckNugetFeedResponsiveness { get; init; } = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness);

private readonly Lazy<ImmutableHashSet<string>> lazyExplicitFeeds;

Expand Down Expand Up @@ -246,6 +246,30 @@ public List<string> MakeRestoreFeeds(string path)
return RestoreFeeds(feedsToUse);
}

/// <summary>
/// Constructs the NuGet CLI restore sources, excluding private registries that require the registry proxy.
/// An empty list leaves source selection to NuGet only when no filtering or default feeds are needed.
/// </summary>
public List<string> MakeNugetExeRestoreFeeds(string path)
{
var feedsToUse = FeedsToUse(path).ToList();
var defaultFeeds = CheckNugetFeedResponsiveness ? ReachableDefaultFeeds : DefaultFeeds;
var useDefaultFeeds = feedsToUse.Count == 0 && defaultFeeds.Count > 0;

if (!CheckNugetFeedResponsiveness && !HasPrivateRegistryFeeds && !useDefaultFeeds)
{
return [];
}

if (useDefaultFeeds)
{
feedsToUse.AddRange(defaultFeeds);
}

// Filter after selecting defaults so private base feeds cannot be reintroduced.
return RestoreFeeds(feedsToUse.Where(feed => !privateRegistryFeeds.Contains(feed)));
}

private (int initialTimeout, int tryCount) GetFeedRequestSettings(bool isFallback)
{
int timeoutMilliSeconds = isFallback && int.TryParse(Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetFeedResponsivenessInitialTimeoutForFallback), out timeoutMilliSeconds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,24 +163,8 @@ private bool TryRestoreNugetPackage(string packagesConfig)
{
logger.LogInfo($"Restoring file \"{packagesConfig}\"...");

List<string> sourcesArgument = [];
var feedsToUse = feedManager.FeedsToUse(packagesConfig).ToList();
var defaultFeeds = feedManager.CheckNugetFeedResponsiveness
? feedManager.ReachableDefaultFeeds
: feedManager.DefaultFeeds;
var useDefaultFeeds = feedsToUse.Count == 0 && defaultFeeds.Count > 0;

// Explicitly construct the sources to be used for the restore command when checking feed
// responsiveness, using private registries, or falling back to default feeds.
if (feedManager.CheckNugetFeedResponsiveness || feedManager.HasPrivateRegistryFeeds || useDefaultFeeds)
{
if (useDefaultFeeds)
{
feedsToUse.AddRange(defaultFeeds);
}
var restoreFeeds = feedManager.RestoreFeeds(feedsToUse);
sourcesArgument = restoreFeeds.SelectMany<string, string>(feed => ["-Source", feed]).ToList();
}
var sourcesArgument = feedManager.MakeNugetExeRestoreFeeds(packagesConfig)
.SelectMany<string, string>(feed => ["-Source", feed]).ToList();

/* Use nuget.exe to install a package.
* Note that there is a clutch of NuGet assemblies which could be used to
Expand Down
93 changes: 93 additions & 0 deletions csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,99 @@ public void TestFeedsToUse()
], feedsToUse);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void TestNugetExeRestoreFeedsExcludePrivateRegistries(bool checkResponsiveness)
{
var logger = new LoggerStub();
var dotnet = new DotNetStub([], [], [],
["E https://example.com/registry2", "E https://feed.from/folder1", "E https://feed.from/folder2"]);
using var feedManager = new FeedManager(logger, dotnet, new RegistryProxyStub(),
new FileProviderStub(), new FeedManagerIOStub(["https://feed.from/folder2"]))
{
CheckNugetFeedResponsiveness = checkResponsiveness
};

var feeds = feedManager.MakeNugetExeRestoreFeeds("/path/to/packages.config").ToHashSet();
Assert.True(feeds.SetEquals(checkResponsiveness
? ["https://feed.from/folder1"]
: ["https://feed.from/folder1", "https://feed.from/folder2"]));
Assert.Contains("https://example.com/registry2", feedManager.MakeRestoreFeeds("/path/to/project.csproj"));
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void TestNugetExeRestoreFeedsExcludePrivateBaseRegistries(bool checkResponsiveness)
{
var logger = new LoggerStub();
var dotnet = new DotNetStub([], [], [],
["E https://api.nuget.org/v3/index.json", "E https://feed.from/folder1"]);
using var feedManager = new FeedManager(logger, dotnet, new RegistryProxyStubWithBaseUrls(),
new FileProviderStub(), new FeedManagerIOStub([]))
{
CheckNugetFeedResponsiveness = checkResponsiveness
};

Assert.Equal(["https://feed.from/folder1"], feedManager.MakeNugetExeRestoreFeeds("/path/to/packages.config"));
}

[Theory]
[InlineData(true, false)]
[InlineData(false, false)]
[InlineData(true, true)]
[InlineData(false, true)]
public void TestNugetExeRestoreFeedsWithOnlyPrivateRegistries(bool checkResponsiveness, bool unreachable)
{
var logger = new LoggerStub();
var registryProxy = new RegistryProxyStubWithBaseUrls();
var dotnet = new DotNetStub([], [], [], ["E https://api.nuget.org/v3/index.json"]);
using var feedManager = new FeedManager(logger, dotnet, registryProxy,
new FileProviderStub(), new FeedManagerIOStub(unreachable ? registryProxy.RegistryURLs.ToList() : []))
{
CheckNugetFeedResponsiveness = checkResponsiveness
};

var source = Assert.Single(feedManager.MakeNugetExeRestoreFeeds("/path/to/packages.config"));
Assert.True(Path.IsPathFullyQualified(source));
Assert.True(Directory.Exists(source));
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void TestNugetExeRestoreFeedsWithoutPrivateRegistries(bool checkResponsiveness)
{
var logger = new LoggerStub();
var dotnet = new DotNetStub([], [], [], ["E https://feed.from/folder1"]);
using var feedManager = new FeedManager(logger, dotnet, null,
new FileProviderStub(), new FeedManagerIOStub([]))
{
CheckNugetFeedResponsiveness = checkResponsiveness
};

Assert.Equal(checkResponsiveness ? ["https://feed.from/folder1"] : [],
feedManager.MakeNugetExeRestoreFeeds("/path/to/packages.config"));
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void TestNugetExeRestoreFeedsUseDefaults(bool checkResponsiveness)
{
var logger = new LoggerStub();
var dotnet = new DotNetStub([], [], [], []);
using var feedManager = new FeedManager(logger, dotnet, null,
new FileProviderStub(), new FeedManagerIOStub([]))
{
CheckNugetFeedResponsiveness = checkResponsiveness
};

Assert.Equal(["https://api.nuget.org/v3/index.json"],
feedManager.MakeNugetExeRestoreFeeds("/path/to/packages.config"));
}

/// <summary>
/// Verify that `FeedManager` correctly computes the default feeds and reachable default feeds
/// when no private registries are configured.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
category: minorAnalysis
---

- The C# extractor continues to use the NuGet CLI to restore dependencies from `packages.config` files when private NuGet registries are configured, but excludes those private registries from the CLI's sources. Other applicable feeds remain available, while private registries continue to be used by `dotnet restore`.