diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependabotProxy.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependabotProxy.cs
index 08b142219a97..3bf843d3fa2c 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependabotProxy.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependabotProxy.cs
@@ -18,84 +18,54 @@ public class DependabotProxy : IDependabotProxy
/// The URL of the package registry.
public record class RegistryConfig(string Type, string URL);
- private readonly string host;
- private readonly string port;
-
public string Address { get; }
- public HashSet RegistryURLs { get; }
+ public HashSet RegistryURLs { get; } = [];
public string? CertificatePath { get; private set; }
public X509Certificate2? Certificate { get; private set; }
- internal static IDependabotProxy? GetDependabotProxy(
- ILogger logger, IDiagnosticsWriter diagnosticsWriter, TemporaryDirectory tempWorkingDirectory)
+ private DependabotProxy(IDependabotProxyConfiguration config, ILogger logger, TemporaryDirectory tempWorkingDirectory)
{
- // Setting HTTP(S)_PROXY and SSL_CERT_FILE have no effect on Windows or macOS,
- // but we would still end up using the Dependabot proxy to check for feed reachability.
- // This would result in us discovering that the feeds are reachable, but `dotnet` would
- // fail to connect to them. To prevent this from happening, we do not initialise an
- // instance of `DependabotProxy` on those platforms.
- if (SystemBuildActions.Instance.IsWindows() || SystemBuildActions.Instance.IsMacOs()) return null;
-
- // Obtain and store the address of the Dependabot proxy, if available.
- var host = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyHost);
- var port = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyPort);
-
- if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(port))
- {
- logger.LogInfo("No Dependabot proxy credentials are configured.");
- return null;
- }
-
- var result = new DependabotProxy(host, port);
- logger.LogInfo($"Dependabot proxy configured at {result.Address}");
+ Address = $"http://{config.Host}:{config.Port}";
- // Obtain and store the proxy's certificate, if available.
- var cert = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyCertificate);
-
- if (!string.IsNullOrWhiteSpace(cert))
+ if (!string.IsNullOrWhiteSpace(config.Certificate))
{
var certDirPath = new DirectoryInfo(Path.Join(tempWorkingDirectory.DirInfo.FullName, ".dependabot-proxy"));
Directory.CreateDirectory(certDirPath.FullName);
- result.CertificatePath = Path.Join(certDirPath.FullName, "proxy.crt");
- var certFile = new FileInfo(result.CertificatePath);
+ CertificatePath = Path.Join(certDirPath.FullName, "proxy.crt");
+ var certFile = new FileInfo(CertificatePath);
using var writer = certFile.CreateText();
- writer.Write(cert);
+ writer.Write(config.Certificate);
writer.Close();
- logger.LogInfo($"Stored Dependabot proxy certificate at {result.CertificatePath}");
+ logger.LogInfo($"Stored Dependabot proxy certificate at {CertificatePath}");
- result.Certificate = X509Certificate2.CreateFromPem(cert);
+ Certificate = X509Certificate2.CreateFromPem(config.Certificate);
}
- // Try to obtain the list of private registry URLs.
- var registryURLs = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyURLs);
-
- if (!string.IsNullOrWhiteSpace(registryURLs))
+ if (!string.IsNullOrWhiteSpace(config.RegistryURLs))
{
try
{
- // The value of the environment variable should be a JSON array of objects, such as:
- // [ { "type": "nuget_feed", "url": "https://nuget.pkg.github.com/org/index.json" } ]
- var array = JsonConvert.DeserializeObject>(registryURLs);
+ var array = JsonConvert.DeserializeObject>(config.RegistryURLs);
if (array is not null)
{
- foreach (RegistryConfig config in array)
+ foreach (RegistryConfig registry in array)
{
// The array contains all configured private registries, not just ones for C#.
// We ignore the non-C# ones here.
- if (!config.Type.Equals("nuget_feed"))
+ if (!registry.Type.Equals("nuget_feed"))
{
- logger.LogDebug($"Ignoring registry at '{config.URL}' since it is not of type 'nuget_feed'.");
+ logger.LogDebug($"Ignoring registry at '{registry.URL}' since it is not of type 'nuget_feed'.");
continue;
}
- logger.LogInfo($"Found private registry at '{config.URL}'");
- result.RegistryURLs.Add(config.URL);
+ logger.LogInfo($"Found private registry at '{registry.URL}'");
+ RegistryURLs.Add(registry.URL);
}
}
}
@@ -104,6 +74,39 @@ public record class RegistryConfig(string Type, string URL);
logger.LogError($"Unable to parse '{EnvironmentVariableNames.ProxyURLs}': {ex.Message}");
}
}
+ }
+
+ internal static IDependabotProxy? Make(ILogger logger, IDiagnosticsWriter diagnosticsWriter, TemporaryDirectory tempWorkingDirectory)
+ {
+ // Setting HTTP(S)_PROXY and SSL_CERT_FILE have no effect on Windows or macOS,
+ // but we would still end up using the Dependabot proxy to check for feed reachability.
+ // This would result in us discovering that the feeds are reachable, but `dotnet` would
+ // fail to connect to them. To prevent this from happening, we do not initialise an
+ // instance of `DependabotProxy` on those platforms.
+ if (SystemBuildActions.Instance.IsWindows() || SystemBuildActions.Instance.IsMacOs())
+ {
+ return null;
+ }
+
+ return Make(new DependabotProxyConfiguration(), logger, diagnosticsWriter, tempWorkingDirectory);
+ }
+
+ ///
+ /// Creates an instance of the Dependabot proxy using the specified configuration.
+ /// Returns null if the proxy cannot be created.
+ /// This overload is exposed primarily to enable platform-independent unit testing.
+ ///
+ internal static IDependabotProxy? Make(
+ IDependabotProxyConfiguration proxyConfig, ILogger logger, IDiagnosticsWriter diagnosticsWriter, TemporaryDirectory tempWorkingDirectory)
+ {
+ if (string.IsNullOrWhiteSpace(proxyConfig.Host) || string.IsNullOrWhiteSpace(proxyConfig.Port))
+ {
+ logger.LogDebug("No Dependabot proxy credentials are configured.");
+ return null;
+ }
+
+ var result = new DependabotProxy(proxyConfig, logger, tempWorkingDirectory);
+ logger.LogInfo($"Dependabot proxy configured at {result.Address}");
// Emit a diagnostic for the discovered private registries, so that it is easy
// for users to see that they were picked up.
@@ -125,17 +128,9 @@ public record class RegistryConfig(string Type, string URL);
return result;
}
- private DependabotProxy(string host, string port)
- {
- this.host = host;
- this.port = port;
- this.Address = $"http://{this.host}:{this.port}";
- this.RegistryURLs = new HashSet();
- }
-
public void Dispose()
{
- this.Certificate?.Dispose();
+ Certificate?.Dispose();
}
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependabotProxyConfiguration.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependabotProxyConfiguration.cs
new file mode 100644
index 000000000000..2d81f94aea2c
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependabotProxyConfiguration.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Semmle.Extraction.CSharp.DependencyFetching
+{
+ public class DependabotProxyConfiguration : IDependabotProxyConfiguration
+ {
+ public string? Host { get; } = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyHost);
+
+ public string? Port { get; } = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyPort);
+
+ public string? Certificate { get; } = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyCertificate);
+
+ public string? RegistryURLs { get; } = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyURLs);
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs
index 707fabbf83fb..a985947c0c12 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs
@@ -106,7 +106,7 @@ void exitCallback(int ret, string msg, bool silent)
return BuildScript.Success;
}).Run(SystemBuildActions.Instance, startCallback, exitCallback);
- dependabotProxy = DependabotProxy.GetDependabotProxy(logger, diagnosticsWriter, tempWorkingDirectory);
+ dependabotProxy = DependabotProxy.Make(logger, diagnosticsWriter, tempWorkingDirectory);
try
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDependabotProxyConfiguration.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDependabotProxyConfiguration.cs
new file mode 100644
index 000000000000..c67ee4fc39df
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDependabotProxyConfiguration.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Semmle.Extraction.CSharp.DependencyFetching
+{
+ public interface IDependabotProxyConfiguration
+ {
+ // The host of the Dependabot proxy, if available.
+ string? Host { get; }
+
+ // The port of the Dependabot proxy, if available.
+ string? Port { get; }
+
+ // The certificate of the Dependabot proxy, if available.
+ string? Certificate { get; }
+
+ // The list of package registries that are configured for the proxy, if any.
+ // The value of the environment variable should be a JSON array of objects, such as:
+ // [ { "type": "nuget_feed", "url": "https://nuget.pkg.github.com/org/index.json" } ]
+ string? RegistryURLs { get; }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.Tests/DependabotProxy.cs b/csharp/extractor/Semmle.Extraction.Tests/DependabotProxy.cs
new file mode 100644
index 000000000000..9c8c762f5989
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.Tests/DependabotProxy.cs
@@ -0,0 +1,185 @@
+using Xunit;
+using System;
+using System.IO;
+using Semmle.Extraction.CSharp.DependencyFetching;
+using Semmle.Util;
+
+namespace Semmle.Extraction.Tests
+{
+ public class DependabotConfigurationStub : IDependabotProxyConfiguration
+ {
+ public string? Host { get; set; }
+ public string? Port { get; set; }
+ public string? Certificate { get; set; }
+ public string? RegistryURLs { get; set; }
+ }
+
+ public class DiagnosticsWriterStub : IDiagnosticsWriter
+ {
+ public void AddEntry(Semmle.Util.DiagnosticMessage entry) { }
+ public void Dispose() { }
+ }
+
+ public class DependabotProxyTests
+ {
+ private static TemporaryDirectory MakeTemporaryDirectory()
+ {
+ var tmp = Path.Join(Path.GetTempPath(), "DependabotProxyTests", Guid.NewGuid().ToString());
+ return new TemporaryDirectory(tmp, "testing", new LoggerStub());
+ }
+
+ [Fact]
+ public void TestDependabotProxyCreation1()
+ {
+ // Setup
+ var config = new DependabotConfigurationStub
+ {
+ Host = "localhost",
+ Port = "",
+ };
+
+ // Execute
+ using var tempWorkingDirectory = MakeTemporaryDirectory();
+ using var proxy = DependabotProxy.Make(config, new LoggerStub(), new DiagnosticsWriterStub(), tempWorkingDirectory);
+
+ // Verify
+ Assert.Null(proxy);
+ }
+
+ [Fact]
+ public void TestDependabotProxyCreation2()
+ {
+ // Setup
+ var config = new DependabotConfigurationStub
+ {
+ Port = "8080",
+ };
+
+ // Execute
+ using var tempWorkingDirectory = MakeTemporaryDirectory();
+ using var proxy = DependabotProxy.Make(config, new LoggerStub(), new DiagnosticsWriterStub(), tempWorkingDirectory);
+
+ // Verify
+ Assert.Null(proxy);
+ }
+
+ private const string ExampleCertificate = """
+ -----BEGIN CERTIFICATE-----
+ MIIFJTCCAw2gAwIBAgIUDImU6YnuAqJ1QuRp+OpJQPnPu6wwDQYJKoZIhvcNAQEL
+ BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDkwMTEyMjUzMVoXDTI3MDkw
+ MTEyMjUzMVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF
+ AAOCAg8AMIICCgKCAgEAnlp7yQ1VuocMwIZWlCle3bEM86+1ED6BFfPFpIrRhfUT
+ c+5IvPng8TIZPO4mROp5G9YDZfOtXW2bwktyZNUhsBcxqUT1lmXit21vc5W9Gxx5
+ 4G8nyF4/FcjFkmxkZifxiUCdBceDcE7+kx2itq/a7gLPlyTzvz5etu1nHEC3Jg/y
+ TVhAwdwysgAo9WymFCczDa2ga6nOPBOaxwLnoPl9041KSu5oIo9QC0Im+US1R18Q
+ /mXa+wkmjf+bYAkE/pZie8z8Q7h9yppTngGzkoDEebFYyaMr8MXlFdWS8f/eMwSp
+ iMFSsmlCqgUbA672APxzOcuSMMYrblzGkvZp23qbNjwQuQKlgAYBTSGltLv4U8JF
+ ePNcgDCY6RG55rNvF1gk1L2h25jcw1LX6fSvQGCOkzNmP03AhqZBUigO1Zt0zLwi
+ K4m0bH7nPLJFEN6tI3tybyZeC2RVyiSHvOkgx35Qj8RQ3XMVkImJNBYOMc2MkmMZ
+ ux6XMiHqXCON4zaWuWSovciZeMAQAspCrzVDLH6p2DWEfw/zDfQNU3iLk21sZGei
+ 0GKzs8zrxUcqOU9V4Cnm+7JJ6eqS72f1+wX0ROb3djC6KgCE/NaHqo4apiI3K+CH
+ T0rVRsJIHyT39YO1c1I1vhAKRSH5kQVe3qRfIT/AuaDLQY6WqGPzrOkem78sjtsC
+ AwEAAaNvMG0wHQYDVR0OBBYEFK0DP5MD6mhEcdcm346uwoPL2NFGMB8GA1UdIwQY
+ MBaAFK0DP5MD6mhEcdcm346uwoPL2NFGMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R
+ BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQCc5u8qNHHG
+ kONjfvq7Denq6QaEt4dZZDDODAvgUzZnnBjEhgrp7zfxtbyU/I0+DWnKQMKA9wPM
+ ktiFGd0lldEqoT+E7b0kN124lBGqZ/uYkhsWZ0Nc5dD+UB9oJszwOc5KNuquOnr6
+ SbsfXVm4yLvVLXl67c0jvqvRgGg9/6Q6eMzohW6abMdbYhS28/DsJhCea/dV3+L1
+ oVJ3O/A8e86m174ZCGE8s9UtnVYylBkAryDqaaQLdOBQ2C7uxdRAUNHSIa2JlqUc
+ 5+cod8lFojKb74hbgj6wkXyajsFttqYMh7CeASsnjZXDQ4MC3DqqDVCZuNvJ85Rt
+ ya3Tljp4Ln2AAAoKC3REUeU8PQqpk1vVIj0FSr3RvBTvwzyNfWFVqyBiXTATuV9n
+ 6AemqqXo5MZrHHeRaSTF8A70Jxbt9yx75xQxp3O3tdEL1Mxbl9X7c/hizOfLbeHH
+ IkAgzALQgi87Zbf2tOhRwH5NrB4ijyUUfovRHUwzsZOoTNqlVeNzbDRVbegx9V99
+ /3vwNZgpStGl/JYhN9qY5hJKnC64ltMvuNGpLeJCGyFkrtFS8gKkgR7VKrGo7h3+
+ Zo8rz8TFjP7RmSgQbrmFuPqNOGXzPidu2sMMFacKV7Rn4bEtHzW3MDhqVD4w/pGD
+ L0xpnWjzLYltVjz8mo07yh+zQ10G71Cl1w==
+ -----END CERTIFICATE-----
+ """;
+
+ [Fact]
+ public void TestDependabotProxyCertificate()
+ {
+ // Setup
+ var config = new DependabotConfigurationStub
+ {
+ Port = "8080",
+ Host = "localhost",
+ Certificate = ExampleCertificate
+ };
+
+ // Execute
+ using var tempWorkingDirectory = MakeTemporaryDirectory();
+ using var proxy = DependabotProxy.Make(config, new LoggerStub(), new DiagnosticsWriterStub(), tempWorkingDirectory);
+
+ // Verify
+ Assert.NotNull(proxy);
+ Assert.Equal("http://localhost:8080", proxy.Address);
+ Assert.NotNull(proxy.Certificate);
+ Assert.NotNull(proxy.CertificatePath);
+ }
+
+ [Fact]
+ public void TestDependabotRegistryUrls1()
+ {
+ // Setup
+ var config = new DependabotConfigurationStub
+ {
+ Port = "8080",
+ Host = "localhost",
+ RegistryURLs = "Doesn't parse as a JSON list"
+ };
+
+ // Execute
+ using var tempWorkingDirectory = MakeTemporaryDirectory();
+ using var proxy = DependabotProxy.Make(config, new LoggerStub(), new DiagnosticsWriterStub(), tempWorkingDirectory);
+
+ // Verify
+ Assert.NotNull(proxy);
+ Assert.Equal([], proxy.RegistryURLs);
+ }
+
+ [Fact]
+ public void TestDependabotRegistryUrls2()
+ {
+ // Setup
+ var config = new DependabotConfigurationStub
+ {
+ Port = "8080",
+ Host = "localhost",
+ RegistryURLs = "[ { \"type\": \"nuget_feed\", \"url\": \"https://nuget.pkg.github.com/org/index.json\" } ]"
+ };
+
+ // Execute
+ using var tempWorkingDirectory = MakeTemporaryDirectory();
+ using var proxy = DependabotProxy.Make(config, new LoggerStub(), new DiagnosticsWriterStub(), tempWorkingDirectory);
+
+ // Verify
+ Assert.NotNull(proxy);
+ Assert.Equal([
+ "https://nuget.pkg.github.com/org/index.json"
+ ], proxy.RegistryURLs);
+ }
+
+ [Fact]
+ public void TestDependabotRegistryUrls3()
+ {
+ // Setup
+ var config = new DependabotConfigurationStub
+ {
+ Port = "8080",
+ Host = "localhost",
+ RegistryURLs = "[ { \"type\": \"nuget_feed\", \"url\": \"https://example.com/org/index.json\" }, { \"type\": \"wrong_type\", \"url\": \"https://nuget.pkg.github.com/org/index.json\" } ]"
+ };
+
+ // Execute
+ using var tempWorkingDirectory = MakeTemporaryDirectory();
+ using var proxy = DependabotProxy.Make(config, new LoggerStub(), new DiagnosticsWriterStub(), tempWorkingDirectory);
+
+ // Verify
+ Assert.NotNull(proxy);
+ Assert.Equal([
+ "https://example.com/org/index.json"
+ ], proxy.RegistryURLs);
+ }
+ }
+}