Skip to content

Commit

Permalink
Added NugetConfigFileService, parsing nuget.config for package sources
Browse files Browse the repository at this point in the history
Allow package lookup from a list of repositories
  • Loading branch information
Georg Rottensteiner committed Jan 16, 2025
1 parent ef81800 commit c7af6ec
Show file tree
Hide file tree
Showing 15 changed files with 338 additions and 27 deletions.
5 changes: 3 additions & 2 deletions CycloneDX.Tests/FunctionalTests/FunctionalTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ private static INugetServiceFactory CreateMockNugetServiceFactory()
It.IsAny<RunOptions>(),
It.IsAny<IFileSystem>(),
It.IsAny<IGithubService>(),
It.IsAny<List<string>>()))
It.IsAny<List<string>>(),
It.IsAny<HashSet<NugetInputModel>>()))
.Returns(nugetService);

return mockNugetServiceFactory.Object;
Expand Down Expand Up @@ -79,7 +80,7 @@ public static async Task<Bom> Test(RunOptions options, INugetServiceFactory nuge
options.SolutionOrProjectFile ??= MockUnixSupport.Path("c:/ProjectPath/Project.csproj");
options.disablePackageRestore = true;

Runner runner = new Runner(mockFileSystem, null, null, null, null, null, null, nugetService);
Runner runner = new Runner(mockFileSystem, null, null, null, null, null, null, null, nugetService);
int exitCode = await runner.HandleCommandAsync(options);

Assert.Equal((int)ExitCode.OK, exitCode);
Expand Down
3 changes: 2 additions & 1 deletion CycloneDX.Tests/FunctionalTests/Issue758.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public Issue758()
It.IsAny<RunOptions>(),
It.IsAny<IFileSystem>(),
It.IsAny<IGithubService>(),
It.IsAny<List<string>>()))
It.IsAny<List<string>>(),
It.IsAny<HashSet<NugetInputModel>>()))
.Returns(nugetService);

nugetServiceFactory = mockNugetServiceFactory.Object;
Expand Down
12 changes: 12 additions & 0 deletions CycloneDX.Tests/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ public static MockFileData GetPackagesFileWithPackageReferences(IEnumerable<Dotn

}

public static MockFileData GetNugetConfigFileWithSources(IEnumerable<NugetInputModel> sources)
{
var fileData = "<configuration><packageSources>";
foreach (var source in sources)
{
fileData += @"<add key=""" + source.nugetFeedName + @""" value=""" + source.nugetFeedUrl + @""" />";
}
fileData += "</packageSources></configuration>";
return new MockFileData(fileData);

}

public static DotnetCommandResult GetDotnetListPackagesResult(IEnumerable<(string projectName, (string packageName, string version)[] packages)> projects)
{
StringBuilder stdout = new StringBuilder();
Expand Down
86 changes: 86 additions & 0 deletions CycloneDX.Tests/NugetConfigFileServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// This file is part of CycloneDX Tool for .NET
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) OWASP Foundation. All Rights Reserved.

using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
using System.IO.Abstractions.TestingHelpers;
using XFS = System.IO.Abstractions.TestingHelpers.MockUnixSupport;
using CycloneDX.Models;
using CycloneDX.Services;
using System.Linq;

namespace CycloneDX.Tests
{
public class NugetConfigFileServiceTests
{
[Fact]
public async Task GetDotnetDependencys_ReturnsDotnetDependency()
{
var mockFileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ XFS.Path(@"c:\Project\nuget.config"), Helpers.GetNugetConfigFileWithSources(
new List<NugetInputModel> {
new NugetInputModel( "https://www.contoso.com" ) { nugetFeedName = "Contoso" }
})
},
});
var configFileService = new NugetConfigFileService(mockFileSystem);

var sources = await configFileService.GetPackageSourcesAsync(XFS.Path(@"c:\Project\nuget.config")).ConfigureAwait(true);

Assert.Collection(sources,
item => {
Assert.Equal("https://www.contoso.com", item.nugetFeedUrl);
});
Assert.Collection(sources,
item => {
Assert.Equal("Contoso", item.nugetFeedName);
});
}

[Fact]
public async Task GetDotnetDependencys_ReturnsMultipleDotnetDependencys()
{
var mockFileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ XFS.Path(@"c:\Project\nuget.config"), Helpers.GetNugetConfigFileWithSources(
new List<NugetInputModel> {
new NugetInputModel( "https://www.contoso.com" ) { nugetFeedName = "Contoso" },
new NugetInputModel( "https://www.contoso2.com" ) { nugetFeedName = "Contoso2" },
new NugetInputModel( "https://www.contoso3.com" ) { nugetFeedName = "Contoso3" },
})
},
});
var configFileService = new NugetConfigFileService(mockFileSystem);

var sources = await configFileService.GetPackageSourcesAsync(XFS.Path(@"c:\Project\nuget.config")).ConfigureAwait(true);
var sortedPackages = new List<NugetInputModel>(sources);
sortedPackages.OrderBy(nim => nim.nugetFeedName);

Assert.Collection(sortedPackages,
item => Assert.Equal("https://www.contoso.com", item.nugetFeedUrl),
item => Assert.Equal("https://www.contoso2.com", item.nugetFeedUrl),
item => Assert.Equal("https://www.contoso3.com", item.nugetFeedUrl));
Assert.Collection(sortedPackages,
item => Assert.Equal("Contoso", item.nugetFeedName),
item => Assert.Equal("Contoso2", item.nugetFeedName),
item => Assert.Equal("Contoso3", item.nugetFeedName));
}

}
}
6 changes: 3 additions & 3 deletions CycloneDX.Tests/ProgramTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public async Task CallingCycloneDX_CreatesOutputDirectory()
.Setup(s => s.GetSolutionDotnetDependencys(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(new HashSet<DotnetDependency>());

Runner runner = new Runner(fileSystem: mockFileSystem, null, null, null, null, null, solutionFileService: mockSolutionFileService.Object, null);
Runner runner = new Runner(fileSystem: mockFileSystem, null, null, null, null, null, null, solutionFileService: mockSolutionFileService.Object, null);

RunOptions runOptions = new RunOptions
{
Expand All @@ -75,7 +75,7 @@ public async Task CallingCycloneDX_WithOutputFilename_CreatesOutputFilename()
.Setup(s => s.GetSolutionDotnetDependencys(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(new HashSet<DotnetDependency>());

Runner runner = new Runner(fileSystem: mockFileSystem, null, null, null, null, null, solutionFileService: mockSolutionFileService.Object, null);
Runner runner = new Runner(fileSystem: mockFileSystem, null, null, null, null, null, null, solutionFileService: mockSolutionFileService.Object, null);

RunOptions runOptions = new RunOptions
{
Expand Down Expand Up @@ -116,7 +116,7 @@ public async Task CallingCycloneDX_WithSolutionOrProjectFileThatDoesntExistsRetu
.Setup(s => s.GetSolutionDotnetDependencys(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(new HashSet<DotnetDependency>());

Runner runner = new Runner(fileSystem: mockFileSystem, null, null, null, null, null, solutionFileService: mockSolutionFileService.Object, null);
Runner runner = new Runner(fileSystem: mockFileSystem, null, null, null, null, null, null, solutionFileService: mockSolutionFileService.Object, null);

RunOptions runOptions = new RunOptions
{
Expand Down
2 changes: 1 addition & 1 deletion CycloneDX.Tests/ValidationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public async Task Validation(string fileFormat, bool disableGitHubLicenses)
mock.GetProjectDotnetDependencysAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<string>(), It.IsAny<string>())
).ReturnsAsync(packages);

Runner runner = new Runner(fileSystem: mockFileSystem, null, null, null, null, projectFileService: mockProjectFileService.Object, solutionFileService: null, null);
Runner runner = new Runner(fileSystem: mockFileSystem, null, null, null, null, null, projectFileService: mockProjectFileService.Object, solutionFileService: null, null);


RunOptions runOptions = new RunOptions
Expand Down
29 changes: 29 additions & 0 deletions CycloneDX/Interfaces/INugetConfigFileService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// This file is part of CycloneDX Tool for .NET
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) OWASP Foundation. All Rights Reserved.

using System.Collections.Generic;
using System.Threading.Tasks;
using CycloneDX.Models;

namespace CycloneDX.Interfaces
{
public interface INugetConfigFileService
{
Task<HashSet<NugetInputModel>> GetPackageSourcesAsync(string configFilePath);
Task<HashSet<NugetInputModel>> RecursivelyGetPackageSourcesAsync(string directoryPath);
}
}
2 changes: 1 addition & 1 deletion CycloneDX/Interfaces/INugetServiceFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ namespace CycloneDX.Interfaces
{
public interface INugetServiceFactory
{
INugetService Create(RunOptions option, IFileSystem fileSystem, IGithubService githubService, List<string> packageCachePaths);
INugetService Create(RunOptions option, IFileSystem fileSystem, IGithubService githubService, List<string> packageCachePaths, HashSet<NugetInputModel> nugetInputModels);
}
}
8 changes: 5 additions & 3 deletions CycloneDX/Models/NugetInputModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace CycloneDX.Models
public static class NugetInputFactory
{
public static NugetInputModel Create(string baseUrl, string baseUrlUserName, string baseUrlUserPassword,
bool isPasswordClearText)
bool isPasswordClearText, string feedName = "Unnamed Source" )
{
if (string.IsNullOrEmpty(baseUrl))
{
Expand All @@ -29,7 +29,7 @@ public static NugetInputModel Create(string baseUrl, string baseUrlUserName, str

if (!string.IsNullOrEmpty(baseUrlUserName) && !string.IsNullOrEmpty(baseUrlUserPassword))
{
return new NugetInputModel(baseUrl, baseUrlUserName, baseUrlUserPassword, isPasswordClearText);
return new NugetInputModel(baseUrl, baseUrlUserName, baseUrlUserPassword, isPasswordClearText, feedName);
}

return new NugetInputModel(baseUrl);
Expand All @@ -39,6 +39,7 @@ public static NugetInputModel Create(string baseUrl, string baseUrlUserName, str

public class NugetInputModel
{
public string nugetFeedName { get; set; }
public string nugetFeedUrl { get; set; }
public string nugetUsername { get; set; }
public string nugetPassword { get; set; }
Expand All @@ -50,12 +51,13 @@ public NugetInputModel(string baseUrl)
}

public NugetInputModel(string baseUrl, string baseUrlUserName, string baseUrlUserPassword,
bool isPasswordClearText)
bool isPasswordClearText, string feedName)
{
nugetFeedUrl = baseUrl;
nugetUsername = baseUrlUserName;
nugetPassword = baseUrlUserPassword;
IsPasswordClearText = isPasswordClearText;
nugetFeedName = nugetFeedName;
}
}
}
15 changes: 11 additions & 4 deletions CycloneDX/Runner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class Runner
readonly IDotnetCommandService dotnetCommandService;
readonly IDotnetUtilsService dotnetUtilsService;
readonly IPackagesFileService packagesFileService;
readonly INugetConfigFileService configFileService;
readonly IProjectFileService projectFileService;
readonly ISolutionFileService solutionFileService;
readonly INugetServiceFactory nugetServiceFactory;
Expand All @@ -46,6 +47,7 @@ public Runner(IFileSystem fileSystem,
IProjectAssetsFileService projectAssetsFileService,
IDotnetUtilsService dotnetUtilsService,
IPackagesFileService packagesFileService,
INugetConfigFileService configFileService,
IProjectFileService projectFileService,
ISolutionFileService solutionFileService,
INugetServiceFactory nugetServiceFactory)
Expand All @@ -55,11 +57,12 @@ public Runner(IFileSystem fileSystem,
projectAssetsFileService ??= new ProjectAssetsFileService(this.fileSystem, () => new AssetFileReader());
this.dotnetUtilsService = dotnetUtilsService ?? new DotnetUtilsService(this.fileSystem, this.dotnetCommandService);
this.packagesFileService = packagesFileService ?? new PackagesFileService(this.fileSystem);
this.configFileService = configFileService ?? new NugetConfigFileService(this.fileSystem);
this.projectFileService = projectFileService ?? new ProjectFileService(this.fileSystem, this.dotnetUtilsService, this.packagesFileService, projectAssetsFileService);
this.solutionFileService = solutionFileService ?? new SolutionFileService(this.fileSystem, this.projectFileService);
this.nugetServiceFactory = nugetServiceFactory ?? new NugetV3ServiceFactory();
}
public Runner() : this(null, null, null, null, null, null, null, null) { }
public Runner() : this(null, null, null, null, null, null, null, null, null) { }

public async Task<int> HandleCommandAsync(RunOptions options)
{
Expand Down Expand Up @@ -130,9 +133,8 @@ public async Task<int> HandleCommandAsync(RunOptions options)
}
}

var nugetService = nugetServiceFactory.Create(options, fileSystem, githubService, packageCachePathsResult.Result);

var packages = new HashSet<DotnetDependency>();
var sources = new HashSet<NugetInputModel>();

// determine what we are analyzing and do the analysis
var fullSolutionOrProjectFilePath = this.fileSystem.Path.GetFullPath(SolutionOrProjectFile);
Expand Down Expand Up @@ -170,6 +172,7 @@ public async Task<int> HandleCommandAsync(RunOptions options)
return (int)ExitCode.InvalidOptions;
}
packages = await solutionFileService.GetSolutionDotnetDependencys(fullSolutionOrProjectFilePath, baseIntermediateOutputPath, excludetestprojects, framework, runtime).ConfigureAwait(false);
sources = await configFileService.RecursivelyGetPackageSourcesAsync( fileSystem.Path.GetDirectoryName(fullSolutionOrProjectFilePath)).ConfigureAwait(false);
topLevelComponent.Name = fileSystem.Path.GetFileNameWithoutExtension(SolutionOrProjectFile);
}
else if (Utils.IsSupportedProjectType(SolutionOrProjectFile) && scanProjectReferences)
Expand All @@ -180,6 +183,7 @@ public async Task<int> HandleCommandAsync(RunOptions options)
return (int)ExitCode.InvalidOptions;
}
packages = await projectFileService.RecursivelyGetProjectDotnetDependencysAsync(fullSolutionOrProjectFilePath, baseIntermediateOutputPath, excludetestprojects, framework, runtime).ConfigureAwait(false);
sources = await configFileService.RecursivelyGetPackageSourcesAsync(fileSystem.Path.GetDirectoryName(fullSolutionOrProjectFilePath)).ConfigureAwait(false);
topLevelComponent.Name = fileSystem.Path.GetFileNameWithoutExtension(SolutionOrProjectFile);
}
else if (Utils.IsSupportedProjectType(SolutionOrProjectFile))
Expand All @@ -190,6 +194,7 @@ public async Task<int> HandleCommandAsync(RunOptions options)
return (int)ExitCode.InvalidOptions;
}
packages = await projectFileService.GetProjectDotnetDependencysAsync(fullSolutionOrProjectFilePath, baseIntermediateOutputPath, excludetestprojects, framework, runtime).ConfigureAwait(false);
sources = await configFileService.RecursivelyGetPackageSourcesAsync(fileSystem.Path.GetDirectoryName(fullSolutionOrProjectFilePath)).ConfigureAwait(false);
topLevelComponent.Name = fileSystem.Path.GetFileNameWithoutExtension(SolutionOrProjectFile);
}
else if (fileSystem.Path.GetFileName(SolutionOrProjectFile).ToLowerInvariant().Equals("packages.config", StringComparison.OrdinalIgnoreCase))
Expand All @@ -200,11 +205,13 @@ public async Task<int> HandleCommandAsync(RunOptions options)
return (int)ExitCode.InvalidOptions;
}
packages = await packagesFileService.GetDotnetDependencysAsync(fullSolutionOrProjectFilePath).ConfigureAwait(false);
sources = await configFileService.RecursivelyGetPackageSourcesAsync(fileSystem.Path.GetDirectoryName(fullSolutionOrProjectFilePath)).ConfigureAwait(false);
topLevelComponent.Name = fileSystem.Path.GetDirectoryName(fullSolutionOrProjectFilePath);
}
else if (fileSystem.Directory.Exists(fullSolutionOrProjectFilePath))
{
packages = await packagesFileService.RecursivelyGetDotnetDependencysAsync(fullSolutionOrProjectFilePath).ConfigureAwait(false);
sources = await configFileService.RecursivelyGetPackageSourcesAsync(fileSystem.Path.GetDirectoryName(fullSolutionOrProjectFilePath)).ConfigureAwait(false);
topLevelComponent.Name = fileSystem.Path.GetDirectoryName(fullSolutionOrProjectFilePath);
}
else
Expand All @@ -219,7 +226,7 @@ public async Task<int> HandleCommandAsync(RunOptions options)
}

await Console.Out.WriteLineAsync($"Found {packages.Count()} packages");

var nugetService = nugetServiceFactory.Create(options, fileSystem, githubService, packageCachePathsResult.Result, sources);

if (!string.IsNullOrEmpty(setName))
{
Expand Down
10 changes: 10 additions & 0 deletions CycloneDX/Services/FileDiscoveryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,15 @@ public IEnumerable<string> GetPackagesConfigFiles(string directory)
return _fileSystem.Directory.GetFiles(directory, "packages.config", SearchOption.AllDirectories);
}

/// <summary>
/// Recursively searches a directory for nuget.config files.
/// </summary>
/// <param name="directory">Directory path to search</param>
/// <returns>List of full file paths</returns>
public IEnumerable<string> GetNugetConfigFiles(string directory)
{
return _fileSystem.Directory.GetFiles(directory, "nuget.config", SearchOption.AllDirectories);
}

}
}
Loading

0 comments on commit c7af6ec

Please sign in to comment.