From 47e9de676ba0f87aa2b66411b2d30ecc0db8c998 Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Thu, 13 Jun 2024 11:10:48 -0700 Subject: [PATCH 01/15] (build) Make Chocolatey Package depend on Nuspec Prepare-Chocolatey-Package previously would fail if not run as part of a target that also included copying the nuspec files. This commit makes the task dependency explicit so you can directly run Prepare-Chocolatey-Package. --- recipe.cake | 1 + 1 file changed, 1 insertion(+) diff --git a/recipe.cake b/recipe.cake index feca7ac13..49dada8d5 100644 --- a/recipe.cake +++ b/recipe.cake @@ -167,6 +167,7 @@ Task("Prepare-Chocolatey-Packages") .IsDependeeOf("Create-Chocolatey-Packages") .IsDependeeOf("Verify-PowerShellScripts") .IsDependeeOf("Sign-Assemblies") + .IsDependentOn("Copy-Nuspec-Folders") .WithCriteria(() => BuildParameters.BuildAgentOperatingSystem == PlatformFamily.Windows, "Skipping because not running on Windows") .WithCriteria(() => BuildParameters.ShouldRunChocolatey, "Skipping because execution of Chocolatey has been disabled") .Does(() => From aac92133e34cc141404491de033ff189a424996c Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Thu, 13 Jun 2024 11:13:34 -0700 Subject: [PATCH 02/15] (#3465) Don't build MSI unless asked Changed the logic for skipping an MSI build to skip it by default, and no longer depend on a tagged build. This will allow developers to generate an MSI by adding `--shouldBuildMsi=true`. --- recipe.cake | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/recipe.cake b/recipe.cake index 49dada8d5..4e52b636a 100644 --- a/recipe.cake +++ b/recipe.cake @@ -364,7 +364,6 @@ Task("Prepare-NuGet-Packages") Task("Prepare-MSI") .WithCriteria(() => BuildParameters.ShouldBuildMsi, "Skipping because creation of MSI has been disabled") - .WithCriteria(() => BuildParameters.IsTagged, "Skipping because build is not tagged") .IsDependeeOf("Build-MSI") .Does(() => { @@ -379,9 +378,6 @@ Task("Prepare-MSI") } }); -BuildParameters.Tasks.BuildMsiTask - .WithCriteria(() => BuildParameters.IsTagged, "Skipping because build is not tagged"); - Task("Create-TarGz-Packages") .IsDependentOn("Build") .IsDependeeOf("Package") @@ -439,7 +435,7 @@ BuildParameters.SetParameters(context: Context, getMsisToSign: getMsisToSign, getILMergeConfigs: getILMergeConfigs, preferDotNetGlobalToolUsage: !IsRunningOnWindows(), - shouldBuildMsi: true, + shouldBuildMsi: false, msiUsedWithinNupkg: false, shouldAuthenticodeSignMsis: true, shouldRunNuGet: IsRunningOnWindows(), From 4fc90b002a22bfb7516f3db0e96c285d2fbb0979 Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Thu, 13 Jun 2024 11:15:46 -0700 Subject: [PATCH 03/15] (#3465) Build MSI on CI Builds When running the builds in CI, we should generate the MSI as well. This will allow us to get an MSI for any build to test with if needed. --- .teamcity/settings.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index 8f52dbb63..5f1e28d8a 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -65,7 +65,7 @@ object Chocolatey : BuildType({ script { name = "Call Cake" scriptContent = """ - build.official.bat --verbosity=diagnostic --target=CI --testExecutionType=unit --shouldRunOpenCover=false + build.official.bat --verbosity=diagnostic --target=CI --testExecutionType=unit --shouldRunOpenCover=false --shouldBuildMsi=true """.trimIndent() } } From 3c832dce8690cee72809282732ebe444ca0a27a5 Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Thu, 13 Jun 2024 11:17:08 -0700 Subject: [PATCH 04/15] (#3465) Build MSI on GHA As with the previous commit, this adds the configuration for GitHub Actions. Thus allowing us to ensure the MSI is able to be built in GitHub Actions as well. When running the builds in CI, we should generate the MSI as well. This will allow us to get an MSI for any build to test with if needed. --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 094d07b87..cfee50a12 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: key: ${{ runner.os }}-tools-${{ hashFiles('recipe.cake') }} - name: Build with .Net Framework shell: powershell - run: ./build.ps1 --verbosity=diagnostic --target=CI --testExecutionType=unit --shouldRunOpenCover=false + run: ./build.ps1 --verbosity=diagnostic --target=CI --testExecutionType=unit --shouldRunOpenCover=false --shouldBuildMsi=true - name: Upload Windows build results uses: actions/upload-artifact@v3 # Always upload build results @@ -67,6 +67,7 @@ jobs: code_drop\Packages\NuGet\*.nupkg code_drop\Packages\Chocolatey\*.nupkg code_drop\MsBuild.log + code_drop\MSIs\en-US\chocolatey-*.msi # - uses: coverallsapp/github-action@master # with: # github-token: ${{ secrets.GITHUB_TOKEN }} @@ -140,4 +141,4 @@ jobs: # uses: coverallsapp/github-action@master # with: # github-token: ${{ secrets.GITHUB_TOKEN }} - # parallel-finished: true \ No newline at end of file + # parallel-finished: true From 3c712f779fd561c510dd696e569bce1b85fbcb36 Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Tue, 19 Nov 2024 09:04:47 -0800 Subject: [PATCH 05/15] (tests) Update Arguments decryption test When the tests are run against the download command, the second run was failing due to already having downloaded the package. Remove the download directory to ensure the tests function the same. --- tests/pester-tests/features/ArgumentsDecryption.Tests.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/pester-tests/features/ArgumentsDecryption.Tests.ps1 b/tests/pester-tests/features/ArgumentsDecryption.Tests.ps1 index d4fcca865..95d7c8ed7 100644 --- a/tests/pester-tests/features/ArgumentsDecryption.Tests.ps1 +++ b/tests/pester-tests/features/ArgumentsDecryption.Tests.ps1 @@ -97,6 +97,8 @@ Invoke-Choco install upgradepackage --version 1.0.0 $argumentsFile = Join-Path $env:ChocolateyInstall ".chocolatey/upgradepackage.1.0.0/.arguments" $FileContents | Set-Content -Path $argumentsFile -Encoding utf8 -Force + # Remove the `download` directory so the download command doesn't fail the second test. + Remove-Item -Path $PWD/download -Recurse -Force -ErrorAction SilentlyContinue $Output = Invoke-Choco $Command @Parameters --debug } From 4f1da2b10f3c92a82ba021a8ad1bd7dad56a3edc Mon Sep 17 00:00:00 2001 From: AdmiringWorm Date: Tue, 19 Nov 2024 15:36:34 +0100 Subject: [PATCH 06/15] (#3396) Add specific version check on v3 searches This updates the handling of searching for packages while at the same time specifying the version that is wanted of the packages. This ensures that packages will be looked up to verify it has the version the user wants, before returning any results. --- .../chocolatey.tests.integration.csproj | 1 + .../nuget/NugetListSpecs.cs | 1053 +++++++++++++++++ .../infrastructure.app/nuget/NugetList.cs | 21 + 3 files changed, 1075 insertions(+) create mode 100644 src/chocolatey.tests.integration/infrastructure.app/nuget/NugetListSpecs.cs diff --git a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj index d5f8a0615..b25c37cdd 100644 --- a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj +++ b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj @@ -485,6 +485,7 @@ + diff --git a/src/chocolatey.tests.integration/infrastructure.app/nuget/NugetListSpecs.cs b/src/chocolatey.tests.integration/infrastructure.app/nuget/NugetListSpecs.cs new file mode 100644 index 000000000..bd6eb5e59 --- /dev/null +++ b/src/chocolatey.tests.integration/infrastructure.app/nuget/NugetListSpecs.cs @@ -0,0 +1,1053 @@ +// Copyright © 2017 - 2024 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// 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. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using chocolatey.infrastructure.app.configuration; +using chocolatey.infrastructure.app.nuget; +using chocolatey.infrastructure.filesystem; +using FluentAssertions; +using Moq; +using NuGet.Common; +using NuGet.Protocol.Core.Types; +using WireMock.FluentAssertions; +using WireMock.Matchers; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; + +namespace chocolatey.tests.integration.infrastructure.app.services +{ + public class NugetListSpecs + { + public abstract class NugetListSpecsBase : TinySpec + { + protected ChocolateyConfiguration Configuration = new ChocolateyConfiguration(); + protected readonly ILogger Logger = new ChocolateyNugetLogger(); + protected readonly Mock FileSystem = new Mock(); + protected Lazy MockServer; + protected string HttpCacheLocation = Path.Combine( + Environment.CurrentDirectory, + Guid.NewGuid().ToString()); + + public override void Context() + { + Configuration.CacheExpirationInMinutes = 0; + Configuration.CacheLocation = HttpCacheLocation; + MockServer = new Lazy(() => WireMockServer.Start()); + } + + public override void AfterObservations() + { + if (MockServer.IsValueCreated) + { + MockServer.Value.Stop(); + MockServer.Value.Dispose(); + } + + if (Directory.Exists(HttpCacheLocation)) + { + Directory.Delete(HttpCacheLocation, recursive: true); + } + + base.AfterObservations(); + } + + protected void AddMockServerV2ApiUrl(ChocolateyConfiguration config) + { + config.Sources = $"{config.Sources};${MockServer.Value.Url}/api/v2/".TrimStart(';'); + } + + protected void AddMockServerV3ApiUrl(ChocolateyConfiguration config) + { + config.Sources = $"{config.Sources};${MockServer.Value.Url}/v3/index.json".TrimStart(';'); + } + + protected void AddSimpleResponse(string path, string body, string destination = BodyDestinationFormat.Json, Encoding encoding = null) + { + if (encoding is null) + { + encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + } + + var matcher = path.Contains('*') + ? (IStringMatcher)new WildcardMatcher(path) + : new ExactMatcher(path); + + MockServer.Value.Given(Request.Create().WithPath(matcher).UsingGet()) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithBody(body, destination, encoding)); + } + + protected void AddSimpleResponse(string path, object body, Encoding encoding = null) + { + if (encoding is null) + { + encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + } + + var matcher = path.Contains('*') + ? (IStringMatcher)new WildcardMatcher(path) + : new ExactMatcher(path); + + MockServer.Value.Given(Request.Create().WithPath(matcher).UsingGet()) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithBodyAsJson(body, encoding, indented: false)); + } + + protected void AddV3OnlyIndexResponse(string path, string host = null) + { + if (string.IsNullOrEmpty(host)) + { + host = MockServer.Value.Url; + } + + AddSimpleResponse(path, @"{ + ""version"": ""3.0.0"", + ""resources"": [ + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/search"", + ""@type"": ""SearchQueryService"", + ""comment"": ""Query endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/search"", + ""@type"": ""SearchQueryService/3.0.0-rc"", + ""comment"": ""Query endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/search"", + ""@type"": ""SearchQueryService/3.0.0-beta"", + ""comment"": ""Query endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/autocomplete"", + ""@type"": ""SearchAutocompleteService"", + ""comment"": ""Autocomplete endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/autocomplete"", + ""@type"": ""SearchAutocompleteService/3.0.0-rc"", + ""comment"": ""Autocomplete endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/autocomplete"", + ""@type"": ""SearchAutocompleteService/3.0.0-beta"", + ""comment"": ""Autocomplete endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/"", + ""@type"": ""RegistrationsBaseUrl"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/"", + ""@type"": ""RegistrationsBaseUrl/3.0.0-rc"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/"", + ""@type"": ""RegistrationsBaseUrl/3.0.0-beta"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations-gz/"", + ""@type"": ""RegistrationsBaseUrl/3.4.0"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations-gz/"", + ""@type"": ""RegistrationsBaseUrl/3.6.0"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/flatcontainer"", + ""@type"": ""PackageBaseAddress/3.0.0"", + ""comment"": ""Base URL of where NuGet packages are stored, in the format https://api.nuget.org/v3-flatcontainer/{id-lower}/{version-lower}/{id-lower}.{version-lower}.nupkg"" + }, + { + ""@id"": """ + host + @"/feeds/internal-choco/{id}/{version}"", + ""@type"": ""PackageDetailsUriTemplate/5.1.0"", + ""comment"": ""URI template used by NuGet Client to construct details URL for packages"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/{id-lower}/index.json"", + ""@type"": ""PackageDisplayMetadataUriTemplate/3.0.0-rc"", + ""comment"": ""URI template used by NuGet Client to construct display metadata for Packages using ID"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/{id-lower}/{version-lower}.json"", + ""@type"": ""PackageVersionDisplayMetadataUriTemplate/3.0.0-rc"", + ""comment"": ""URI template used by NuGet Client to construct display metadata for Packages using ID, Version"" + } + ] + }"); + } + + protected void AddV3IndexResponse(string path, string host = null) + { + if (string.IsNullOrEmpty(host)) + { + host = MockServer.Value.Url; + } + + AddSimpleResponse(path, @"{ + ""version"": ""3.0.0"", + ""resources"": [ + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/search"", + ""@type"": ""SearchQueryService"", + ""comment"": ""Query endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/search"", + ""@type"": ""SearchQueryService/3.0.0-rc"", + ""comment"": ""Query endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/search"", + ""@type"": ""SearchQueryService/3.0.0-beta"", + ""comment"": ""Query endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/autocomplete"", + ""@type"": ""SearchAutocompleteService"", + ""comment"": ""Autocomplete endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/autocomplete"", + ""@type"": ""SearchAutocompleteService/3.0.0-rc"", + ""comment"": ""Autocomplete endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/autocomplete"", + ""@type"": ""SearchAutocompleteService/3.0.0-beta"", + ""comment"": ""Autocomplete endpoint of NuGet Search service"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/"", + ""@type"": ""RegistrationsBaseUrl"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/"", + ""@type"": ""RegistrationsBaseUrl/3.0.0-rc"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/"", + ""@type"": ""RegistrationsBaseUrl/3.0.0-beta"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations-gz/"", + ""@type"": ""RegistrationsBaseUrl/3.4.0"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations-gz/"", + ""@type"": ""RegistrationsBaseUrl/3.6.0"", + ""comment"": ""Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages."" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/flatcontainer"", + ""@type"": ""PackageBaseAddress/3.0.0"", + ""comment"": ""Base URL of where NuGet packages are stored, in the format https://api.nuget.org/v3-flatcontainer/{id-lower}/{version-lower}/{id-lower}.{version-lower}.nupkg"" + }, + { + ""@id"": """ + host + @"/feeds/internal-choco/{id}/{version}"", + ""@type"": ""PackageDetailsUriTemplate/5.1.0"", + ""comment"": ""URI template used by NuGet Client to construct details URL for packages"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/{id-lower}/index.json"", + ""@type"": ""PackageDisplayMetadataUriTemplate/3.0.0-rc"", + ""comment"": ""URI template used by NuGet Client to construct display metadata for Packages using ID"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/v3/registrations/{id-lower}/{version-lower}.json"", + ""@type"": ""PackageVersionDisplayMetadataUriTemplate/3.0.0-rc"", + ""comment"": ""URI template used by NuGet Client to construct display metadata for Packages using ID, Version"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/"", + ""@type"": ""LegacyGallery"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/"", + ""@type"": ""LegacyGallery/2.0.0"" + }, + { + ""@id"": """ + host + @"/nuget/internal-choco/package"", + ""@type"": ""PackagePublish/2.0.0"" + } + ] +} +"); + + AddV2MetadataResponse("/nuget/internal-choco/"); + + } + + protected void AddV2MetadataResponse(string path) + { + AddSimpleResponse(path, @" + + + Default + + + Packages + + + +", destination: BodyDestinationFormat.SameAsSource); + + AddSimpleResponse(path + "$metadata", @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +", destination: BodyDestinationFormat.SameAsSource); + } + } + + public class When_Searching_For_A_Package_With_Version_On_A_V3_Only_Feed : NugetListSpecsBase + { + private List _result; + + public override void Context() + { + base.Context(); + + AddV3OnlyIndexResponse("/endpoints/test-jsons/content/index.json"); + MockServer.Value.Given(Request.Create() + .WithPath(new ExactMatcher("/nuget/internal-choco/v3/search")) + .UsingMethod("GET") + .WithParam("q", new ExactMatcher("7zip")) + .WithParam("skip", new ExactMatcher("0")) + .WithParam("take", new ExactMatcher("30")) + .WithParam("semVerLevel", new ExactMatcher("2.0.0"))) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithBody(@"{ + ""totalHits"": 2, + ""data"": [ + { + ""id"": ""7zip"", + ""version"": ""23.1.0"", + ""description"": ""7-Zip is a file archiver with a high compression ratio.\n\n## Features\n- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression\n- Supported formats:\n- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM\n- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z.\n- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip\n- Strong AES-256 encryption in 7z and ZIP formats\n- Self-extracting capability for 7z format\n- Integration with Windows Shell\n- Powerful File Manager\n- Powerful command line version\n- Plugin for FAR Manager\n- Localizations for 87 languages\n\n## Notes\n- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it.\n- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.**"", + ""versions"": [ + { + ""version"": ""22.1"", + ""downloads"": 1 + }, + { + ""version"": ""23.1.0"", + ""downloads"": 0 + } + ], + ""authors"": ""Igor Pavlov"", + ""packageTypes"": [ + { + ""name"": ""Dependency"" + } + ], + ""iconUrl"": ""https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg"", + ""licenseUrl"": ""http://www.7-zip.org/license.txt"", + ""owners"": [ + ""chocolatey-community"", + ""Rob Reynolds"" + ], + ""projectUrl"": ""http://www.7-zip.org/"", + ""registration"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations/7zip/index.json"", + ""summary"": ""7-Zip is a file archiver with a high compression ratio."", + ""tags"": [ + ""7zip"", + ""zip"", + ""archiver"", + ""admin"", + ""foss"" + ], + ""title"": ""7-Zip"", + ""totalDownloads"": 1 + }, + { + ""id"": ""7zip.install"", + ""version"": ""23.1.0"", + ""description"": ""7-Zip is a file archiver with a high compression ratio.\n\n## Features\n\n- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression\n- Supported formats:\n- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM\n- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z.\n- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip\n- Strong AES-256 encryption in 7z and ZIP formats\n- Self-extracting capability for 7z format\n- Integration with Windows Shell\n- Powerful File Manager\n- Powerful command line version\n- Plugin for FAR Manager\n- Localizations for 87 languages\n\n## Notes\n- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it.\n- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.**"", + ""versions"": [ + { + ""version"": ""22.1.0"", + ""downloads"": 1 + }, + { + ""version"": ""23.1.0"", + ""downloads"": 0 + } + ], + ""authors"": ""Igor Pavlov"", + ""packageTypes"": [ + { + ""name"": ""Dependency"" + } + ], + ""iconUrl"": ""https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg"", + ""licenseUrl"": ""http://www.7-zip.org/license.txt"", + ""owners"": [ + ""chocolatey-community"", + ""Rob Reynolds"" + ], + ""projectUrl"": ""http://www.7-zip.org/"", + ""registration"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations/7zip.install/index.json"", + ""summary"": ""7-Zip is a file archiver with a high compression ratio."", + ""tags"": [ + ""7zip"", + ""zip"", + ""archiver"", + ""admin"", + ""cross-platform"", + ""cli"", + ""foss"" + ], + ""title"": ""7-Zip (Install)"", + ""totalDownloads"": 1 + } + ] + }", destination: BodyDestinationFormat.Json)); + AddSimpleResponse("/nuget/internal-choco/v3/registrations-gz/7zip/index.json", @"{ + ""count"": 1, + ""items"": [ + { + ""count"": 2, + ""items"": [ + { + ""@id"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip/22.1.json"", + ""@type"": ""Package"", + ""catalogEntry"": { + ""@id"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/catalog/7zip/22.1.json"", + ""@type"": ""PackageDetails"", + ""authors"": ""Igor Pavlov"", + ""dependencyGroups"": [ + { + ""dependencies"": [ + { + ""id"": ""7zip.install"", + ""range"": ""[22.1]"" + } + ] + } + ], + ""description"": ""7-Zip is a file archiver with a high compression ratio.\n\n## Features\n- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression\n- Supported formats:\n- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM\n- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z.\n- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip\n- Strong AES-256 encryption in 7z and ZIP formats\n- Self-extracting capability for 7z format\n- Integration with Windows Shell\n- Powerful File Manager\n- Powerful command line version\n- Plugin for FAR Manager\n- Localizations for 87 languages\n\n## Notes\n- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it.\n- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.**"", + ""iconUrl"": ""https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg"", + ""id"": ""7zip"", + ""licenseUrl"": ""http://www.7-zip.org/license.txt"", + ""projectUrl"": ""http://www.7-zip.org/"", + ""published"": ""2023-05-08T15:25:36.157Z"", + ""summary"": ""7-Zip is a file archiver with a high compression ratio."", + ""tags"": [ + ""7zip"", + ""zip"", + ""archiver"", + ""admin"", + ""foss"" + ], + ""title"": ""7-Zip"", + ""version"": ""22.1"" + }, + ""packageContent"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/flatcontainer/7zip/22.1/7zip.22.1.nupkg"", + ""registration"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip/index.json"" + }, + { + ""@id"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip/23.1.0.json"", + ""@type"": ""Package"", + ""catalogEntry"": { + ""@id"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/catalog/7zip/23.1.0.json"", + ""@type"": ""PackageDetails"", + ""authors"": ""Igor Pavlov"", + ""dependencyGroups"": [ + { + ""dependencies"": [ + { + ""id"": ""7zip.install"", + ""range"": ""[23.1.0]"" + } + ] + } + ], + ""description"": ""7-Zip is a file archiver with a high compression ratio.\n\n## Features\n- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression\n- Supported formats:\n- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM\n- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z.\n- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip\n- Strong AES-256 encryption in 7z and ZIP formats\n- Self-extracting capability for 7z format\n- Integration with Windows Shell\n- Powerful File Manager\n- Powerful command line version\n- Plugin for FAR Manager\n- Localizations for 87 languages\n\n## Notes\n- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it.\n- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.**"", + ""iconUrl"": ""https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg"", + ""id"": ""7zip"", + ""licenseUrl"": ""http://www.7-zip.org/license.txt"", + ""projectUrl"": ""http://www.7-zip.org/"", + ""published"": ""2024-02-06T14:48:14.26Z"", + ""summary"": ""7-Zip is a file archiver with a high compression ratio."", + ""tags"": [ + ""7zip"", + ""zip"", + ""archiver"", + ""admin"", + ""foss"" + ], + ""title"": ""7-Zip"", + ""version"": ""23.1.0"" + }, + ""packageContent"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/flatcontainer/7zip/23.1.0/7zip.23.1.0.nupkg"", + ""registration"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip/index.json"" + } + ], + ""parent"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip/index.json"", + ""lower"": ""22.1"", + ""upper"": ""23.1.0"" + } + ] + }"); + AddSimpleResponse("/nuget/internal-choco/v3/registrations-gz/7zip.install/index.json", @"{ + ""count"": 1, + ""items"": [ + { + ""count"": 2, + ""items"": [ + { + ""@id"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip.install/22.1.0.json"", + ""@type"": ""Package"", + ""catalogEntry"": { + ""@id"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/catalog/7zip.install/22.1.0.json"", + ""@type"": ""PackageDetails"", + ""authors"": ""Igor Pavlov"", + ""dependencyGroups"": [ + { + ""dependencies"": [ + { + ""id"": ""chocolatey-core.extension"", + ""range"": ""1.3.3"" + } + ] + } + ], + ""description"": ""7-Zip is a file archiver with a high compression ratio.\n\n## Features\n\n- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression\n- Supported formats:\n- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM\n- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z.\n- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip\n- Strong AES-256 encryption in 7z and ZIP formats\n- Self-extracting capability for 7z format\n- Integration with Windows Shell\n- Powerful File Manager\n- Powerful command line version\n- Plugin for FAR Manager\n- Localizations for 87 languages\n\n## Notes\n- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it.\n- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.**"", + ""iconUrl"": ""https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg"", + ""id"": ""7zip.install"", + ""licenseUrl"": ""http://www.7-zip.org/license.txt"", + ""projectUrl"": ""http://www.7-zip.org/"", + ""published"": ""2023-05-08T15:25:39.903Z"", + ""summary"": ""7-Zip is a file archiver with a high compression ratio."", + ""tags"": [ + ""7zip"", + ""zip"", + ""archiver"", + ""admin"", + ""cross-platform"", + ""cli"", + ""foss"" + ], + ""title"": ""7-Zip (Install)"", + ""version"": ""22.1.0"" + }, + ""packageContent"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/flatcontainer/7zip.install/22.1.0/7zip.install.22.1.0.nupkg"", + ""registration"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip.install/index.json"" + }, + { + ""@id"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip.install/23.1.0.json"", + ""@type"": ""Package"", + ""catalogEntry"": { + ""@id"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/catalog/7zip.install/23.1.0.json"", + ""@type"": ""PackageDetails"", + ""authors"": ""Igor Pavlov"", + ""dependencyGroups"": [ + { + ""dependencies"": [ + { + ""id"": ""chocolatey-core.extension"", + ""range"": ""1.3.3"" + } + ] + } + ], + ""description"": ""7-Zip is a file archiver with a high compression ratio.\n\n## Features\n\n- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression\n- Supported formats:\n- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM\n- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z.\n- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip\n- Strong AES-256 encryption in 7z and ZIP formats\n- Self-extracting capability for 7z format\n- Integration with Windows Shell\n- Powerful File Manager\n- Powerful command line version\n- Plugin for FAR Manager\n- Localizations for 87 languages\n\n## Notes\n- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it.\n- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.**"", + ""iconUrl"": ""https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg"", + ""id"": ""7zip.install"", + ""licenseUrl"": ""http://www.7-zip.org/license.txt"", + ""projectUrl"": ""http://www.7-zip.org/"", + ""published"": ""2024-02-06T14:48:19.44Z"", + ""summary"": ""7-Zip is a file archiver with a high compression ratio."", + ""tags"": [ + ""7zip"", + ""zip"", + ""archiver"", + ""admin"", + ""cross-platform"", + ""cli"", + ""foss"" + ], + ""title"": ""7-Zip (Install)"", + ""version"": ""23.1.0"" + }, + ""packageContent"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/flatcontainer/7zip.install/23.1.0/7zip.install.23.1.0.nupkg"", + ""registration"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip.install/index.json"" + } + ], + ""parent"": """ + MockServer.Value.Url + @"/nuget/internal-choco/v3/registrations-gz/7zip.install/index.json"", + ""lower"": ""22.1.0"", + ""upper"": ""23.1.0"" + } + ] + }"); + + Configuration.Sources = $"{MockServer.Value.Url}/endpoints/test-jsons/content/index.json"; + Configuration.Input = "7zip"; + Configuration.Version = "22.1.0"; + Configuration.SourceCommand.Username = "kim"; + Configuration.SourceCommand.Password = "P@ssword123"; + } + + public override void Because() + { + _result = NugetList.GetPackages(Configuration, Logger, FileSystem.Object).ToList(); + } + + [Fact] + public void Should_Have_Found_Two_Packages() + { + _result.Should().HaveCount(2); + } + + [InlineData("7zip", "22.1.0")] + [InlineData("7zip.install", "22.1.0")] + public void Should_Contain_Expected_Package(string id, string version) + { + _result.Should() + .ContainSingle(c => c.Identity.Id == id && c.Identity.Version.ToNormalizedString() == version); + } + + [InlineData("/endpoints/test-jsons/content/index.json")] + [InlineData("/nuget/internal-choco/v3/search?q=7zip&skip=0&take=30&prerelease=false&semVerLevel=2.0.0")] + [InlineData("/nuget/internal-choco/v3/registrations-gz/7zip/index.json")] + [InlineData("/nuget/internal-choco/v3/registrations-gz/7zip.install/index.json")] + public void Should_Have_Called_Expected_Paths(string path) + { + MockServer.Value.Should() + .HaveReceivedACall() + .AtUrl(MockServer.Value.Url + path) + .And.UsingMethod("GET"); + } + } + + public class When_Searching_For_A_Package_With_Version_On_A_Combined_Feed : NugetListSpecsBase + { + private List _result; + + public override void Context() + { + base.Context(); + + AddV3IndexResponse("/nuget/internal-choco/v3/index.json"); + MockServer.Value.Given(Request.Create() + .WithPath(new ExactMatcher("/nuget/internal-choco/Search()")) + .UsingGet() + .WithParam("$orderby", + new ExactMatcher("Id"), + new ExactMatcher("Version desc")) + .WithParam("searchTerm", new ExactMatcher("'7zip'")) + .WithParam("$skip", new ExactMatcher("0")) + .WithParam("$top", new ExactMatcher("30")) + .WithParam("semVerLevel", new ExactMatcher("2.0.0"))) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithBody(@" + + Packages + " + MockServer.Value.Url + @"/nuget/internal-choco/Search()/ + 2024-11-19T13:38:35Z + + + " + MockServer.Value.Url + @"/nuget/internal-choco/Packages(Id='7zip',Version='23.1.0') + 7zip + 7-Zip is a file archiver with a high compression ratio. + 2024-02-06T14:48:14Z + + Igor Pavlov + + + + + + + 23.1.0 + 7-Zip + false + 7-Zip is a file archiver with a high compression ratio. + +## Features +- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression +- Supported formats: +- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM +- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z. +- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip +- Strong AES-256 encryption in 7z and ZIP formats +- Self-extracting capability for 7z format +- Integration with Windows Shell +- Powerful File Manager +- Powerful command line version +- Plugin for FAR Manager +- Localizations for 87 languages + +## Notes +- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it. +- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.** + http://www.7-zip.org/history.txt + 7-Zip is a file archiver with a high compression ratio. + http://www.7-zip.org/ + + https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg + + http://www.7-zip.org/license.txt + + 7zip zip archiver admin foss + 7zip.install:[23.1.0] + true + 2024-02-06T14:48:14.2600000Z + 2024-02-06T14:48:14.2600000Z + 3608 + hsYlJkAOzwVQ8+/hVjxaVkV6obzPflj9p3GJVX1B5KOGfKCOMZf0r/GuLYCFeNFXdQG0Og3zXAv6Sl5K+S54HQ== + true + true + true + false + false + 23.1.0 + true + SHA512 + false + false + 0 + 0 + + + + " + MockServer.Value.Url + @"/nuget/internal-choco/Packages(Id='7zip',Version='22.1') + 7zip + 7-Zip is a file archiver with a high compression ratio. + 2023-05-08T15:25:36Z + + Igor Pavlov + + + + + + + 22.1 + 7-Zip + false + 7-Zip is a file archiver with a high compression ratio. + +## Features +- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression +- Supported formats: +- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM +- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z. +- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip +- Strong AES-256 encryption in 7z and ZIP formats +- Self-extracting capability for 7z format +- Integration with Windows Shell +- Powerful File Manager +- Powerful command line version +- Plugin for FAR Manager +- Localizations for 87 languages + +## Notes +- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it. +- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.** + http://www.7-zip.org/history.txt + 7-Zip is a file archiver with a high compression ratio. + http://www.7-zip.org/ + + https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg + + http://www.7-zip.org/license.txt + + 7zip zip archiver admin foss + 7zip.install:[22.1] + true + 2023-05-08T15:25:36.1570000Z + 2023-05-08T15:25:36.1570000Z + 5112 + NX/wvBxlO66YVGIAnop8TkSxcIptt7on/33AfkudbP+u9cjgrsxV+YAZxK5yMD0hx8O0BJjjU3MVbv+70EO6lw== + false + false + true + false + false + 22.1 + true + SHA512 + false + false + 1 + 1 + + + + " + MockServer.Value.Url + @"/nuget/internal-choco/Packages(Id='7zip.install',Version='23.1.0') + 7zip.install + 7-Zip is a file archiver with a high compression ratio. + 2024-02-06T14:48:19Z + + Igor Pavlov + + + + + + + 23.1.0 + 7-Zip (Install) + false + 7-Zip is a file archiver with a high compression ratio. + +## Features + +- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression +- Supported formats: +- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM +- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z. +- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip +- Strong AES-256 encryption in 7z and ZIP formats +- Self-extracting capability for 7z format +- Integration with Windows Shell +- Powerful File Manager +- Powerful command line version +- Plugin for FAR Manager +- Localizations for 87 languages + +## Notes +- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it. +- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.** + [Software Changelog](http://www.7-zip.org/history.txt) +[Package Changelog](https://github.com/chocolatey-community/chocolatey-coreteampackages/blob/master/automatic/7zip.install/Changelog.md) + 7-Zip is a file archiver with a high compression ratio. + http://www.7-zip.org/ + + https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg + + http://www.7-zip.org/license.txt + + 7zip zip archiver admin cross-platform cli foss + chocolatey-core.extension:1.3.3 + true + 2024-02-06T14:48:19.4400000Z + 2024-02-06T14:48:19.4400000Z + 2867604 + fYqj9nY0NqGqYuyuov5AbdZ0JHC0hBwnNiBOPRNBOo1sFCOibU3ZYlBc1aleh6I3T0gCagUti9wQLl0bfAFyJg== + true + true + true + false + false + 23.1.0 + true + SHA512 + false + false + 0 + 0 + + + + " + MockServer.Value.Url + @"/nuget/internal-choco/Packages(Id='7zip.install',Version='22.1.0') + 7zip.install + 7-Zip is a file archiver with a high compression ratio. + 2023-05-08T15:25:39Z + + Igor Pavlov + + + + + + + 22.1.0 + 7-Zip (Install) + false + 7-Zip is a file archiver with a high compression ratio. + +## Features + +- High compression ratio in [7z format](http://www.7-zip.org/7z.html) with **LZMA** and **LZMA2** compression +- Supported formats: +- Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM +- Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z. +- For ZIP and GZIP formats, **7-Zip** provides a compression ratio that is 2-10 % better than the ratio provided by PKZip and WinZip +- Strong AES-256 encryption in 7z and ZIP formats +- Self-extracting capability for 7z format +- Integration with Windows Shell +- Powerful File Manager +- Powerful command line version +- Plugin for FAR Manager +- Localizations for 87 languages + +## Notes +- The installer for 7-Zip is known to close the Explorer process. This means you may lose current work. If it doesn't automatically restart explorer, type `explorer` on the command shell to restart it. +- **If the package is out of date please check [Version History](#versionhistory) for the latest submitted version. If you have a question, please ask it in [Chocolatey Community Package Discussions](https://github.com/chocolatey-community/chocolatey-packages/discussions) or raise an issue on the [Chocolatey Community Packages Repository](https://github.com/chocolatey-community/chocolatey-packages/issues) if you have problems with the package. Disqus comments will generally not be responded to.** + [Software Changelog](http://www.7-zip.org/history.txt) +[Package Changelog](https://github.com/chocolatey-community/chocolatey-coreteampackages/blob/master/automatic/7zip.install/Changelog.md) + 7-Zip is a file archiver with a high compression ratio. + http://www.7-zip.org/ + + https://cdn.jsdelivr.net/gh/chocolatey-community/chocolatey-packages@68b91a851cee97e55c748521aa6da6211dd37c98/icons/7zip.svg + + http://www.7-zip.org/license.txt + + 7zip zip archiver admin cross-platform cli foss + chocolatey-core.extension:1.3.3 + true + 2023-05-08T15:25:39.9030000Z + 2023-05-08T15:25:39.9030000Z + 2842805 + KMGAfQdcR3Vmk8fun0XoOfH2ySi5Et18mhgvEHVpWKFVBERnxDyBvqmdl2zCdiScjgvrWD+0KWZdUZGxGGuEqg== + false + false + true + false + false + 22.1.0 + true + SHA512 + false + false + 1 + 1 + + +", destination: BodyDestinationFormat.SameAsSource)); + + Configuration.Sources = $"{MockServer.Value.Url}/nuget/internal-choco/v3/index.json"; + Configuration.Input = "7zip"; + Configuration.Version = "22.1.0"; + Configuration.SourceCommand.Username = "kim"; + Configuration.SourceCommand.Password = "P@ssword123"; + } + + public override void Because() + { + _result = NugetList.GetPackages(Configuration, Logger, FileSystem.Object).ToList(); + } + + [Fact] + public void Should_Have_Found_Two_Packages() + { + _result.Should().HaveCount(2); + } + + [InlineData("7zip", "22.1.0")] + [InlineData("7zip.install", "22.1.0")] + public void Should_Contain_Expected_Package(string id, string version) + { + _result.Should() + .ContainSingle(c => c.Identity.Id == id && c.Identity.Version.ToNormalizedString() == version); + } + + [InlineData("/nuget/internal-choco/v3/index.json")] + [InlineData("/nuget/internal-choco/")] + [InlineData("/nuget/internal-choco/$metadata")] + [InlineData("/nuget/internal-choco/Search()?$orderby=Id,Version desc&searchTerm='7zip'&targetFramework=''&includePrerelease=false&$skip=0&$top=30&semVerLevel=2.0.0")] + public void Should_Have_Called_Expected_Paths(string path) + { + MockServer.Value.Should() + .HaveReceivedACall() + .AtUrl(MockServer.Value.Url + path) + .And.UsingMethod("GET"); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/nuget/NugetList.cs b/src/chocolatey/infrastructure.app/nuget/NugetList.cs index 595c40312..52d5c2d13 100644 --- a/src/chocolatey/infrastructure.app/nuget/NugetList.cs +++ b/src/chocolatey/infrastructure.app/nuget/NugetList.cs @@ -203,6 +203,27 @@ private async static Task> SearchPackagesAsyn } } } + else if (version != null) + { + // We need to look up any packages that do not have a matching version number. + + foreach (var package in latestResults) + { + if (package.Identity.Version != version) + { + var result = FindPackage(package.Identity.Id, configuration, nugetLogger, (SourceCacheContext)cacheContext, new[] { repositoryResources }, version); + + if (result != null) + { + results.Add(result); + } + } + else + { + results.Add(package); + } + } + } else { results.AddRange(latestResults); From f307edd57a170e56ab8d2c2b1091472df63db434 Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Fri, 15 Nov 2024 17:25:17 -0500 Subject: [PATCH 07/15] (#3565) Rework NugetCredentialProvider Previously we looked up any available sources in the config by the hostname, before falling back to trying an exact match if we had collisions. This still allowed credentials to be reused in situations where we don't actually know if they're applicable; many repository servers will support different credentials for individual repositories, so we cannot and should not assume that credentials for one repository will actually match another repository, nor that users want the credentials to be shared for both. It also led to the possibility of users storing one repository first, and then later specifying a different repository on the same server, and choco would try to use the stored credentials for the first repository for the explicitly-entered URL which is nowhere in config. Instead, we should only match the whole URL (which can be done with Uri. Equals to ensure that we match hostnames case-insensitively, but routes case-sensitively), and expect users to provide credentials if they provide a URL that is not explicitly in the sources. Additionally, we try to ensure that if a user has named a specific source, rather than themselves providing a URL at the command line, we prioritise finding that in the already-configured sources and use that source if the URL matches the current URL that NuGet requires a credential for. --- .../commands/ChocolateyInfoCommand.cs | 5 +- .../commands/ChocolateyInstallCommand.cs | 6 +- .../commands/ChocolateyListCommand.cs | 2 +- .../commands/ChocolateyOutdatedCommand.cs | 6 +- .../commands/ChocolateySearchCommand.cs | 6 +- .../commands/ChocolateyUpgradeCommand.cs | 6 +- .../configuration/ChocolateyConfiguration.cs | 6 ++ .../ChocolateyNugetCredentialProvider.cs | 77 +++++++++---------- 8 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs index ea4b44c90..04e4c8a2d 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs @@ -38,7 +38,10 @@ public override void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConf .Add( "s=|source=", "Source - Source location for install. Can use special 'windowsfeatures', 'ruby', 'cygwin', or 'python' sources. Defaults to configured sources.", - option => configuration.Sources = option.UnquoteSafe()) + option => { + configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); + configuration.ListCommand.ExplicitSource = true; + }) .Add( "l|lo|localonly|local-only", "LocalOnly - Only search against local machine items.", diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs index 45bf7cf20..eae87ebd9 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs @@ -53,7 +53,11 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi optionSet .Add("s=|source=", "Source - The source to find the package(s) to install. Special sources include: ruby, cygwin, windowsfeatures, and python. To specify more than one source, pass it with a semi-colon separating the values (e.g. \"'source1;source2'\"). Defaults to default feeds.", - option => configuration.Sources = option.UnquoteSafe()) + option => + { + configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); + configuration.ListCommand.ExplicitSource = true; + }) .Add("version=", "Version - A specific version to install. Defaults to unspecified.", option => configuration.Version = option.UnquoteSafe()) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs index ff65fa62b..5fc100df5 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs @@ -85,7 +85,7 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi "Source - Name of alternative source to use, for example 'windowsfeatures', 'ruby', 'cygwin', or 'python'.", option => { - configuration.Sources = option.UnquoteSafe(); + configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); configuration.ListCommand.ExplicitSource = true; }) .Add("idonly|id-only", diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs index aff0f2561..b4f8f4ebe 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs @@ -40,7 +40,11 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi optionSet .Add("s=|source=", "Source - The source to find the package(s) to install. Special sources include: ruby, cygwin, windowsfeatures, and python. To specify more than one source, pass it with a semi-colon separating the values (e.g. \"'source1;source2'\"). Defaults to default feeds.", - option => configuration.Sources = option.UnquoteSafe()) + option => + { + configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); + configuration.ListCommand.ExplicitSource = true; + }) .Add("u=|user=", "User - used with authenticated feeds. Defaults to empty.", option => configuration.SourceCommand.Username = option.UnquoteSafe()) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateySearchCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateySearchCommand.cs index 2bf9d8595..ee80059d0 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateySearchCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateySearchCommand.cs @@ -43,7 +43,11 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi optionSet .Add("s=|source=", "Source - Source location for install. Can use special 'windowsfeatures', 'ruby', 'cygwin', or 'python' sources. Defaults to sources.", - option => configuration.Sources = option.UnquoteSafe()) + option => + { + configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); + configuration.ListCommand.ExplicitSource = true; + }) .Add("idonly|id-only", "Id Only - Only return Package Ids in the list results.", option => configuration.ListCommand.IdOnly = option != null) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs index 03569df16..f83500a27 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs @@ -53,7 +53,11 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi optionSet .Add("s=|source=", "Source - The source to find the package(s) to install. Special sources include: ruby, cygwin, windowsfeatures, and python. To specify more than one source, pass it with a semi-colon separating the values (e.g. \"'source1;source2'\"). Defaults to default feeds.", - option => configuration.Sources = option.UnquoteSafe()) + option => + { + configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); + configuration.ListCommand.ExplicitSource = true; + }) .Add("version=", "Version - A specific version to install. Defaults to unspecified.", option => configuration.Version = option.UnquoteSafe()) diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 1c4ea87af..b823cf6d3 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -262,6 +262,12 @@ private void AppendOutput(StringBuilder propertyValues, string append) /// public string Sources { get; set; } + /// + /// One or more source locations set by comamnd line only. Semi-colon delimited. + /// Do not set this anywhere other than parsing CLI arguments for commands. + /// + public string ExplicitSources { get; set; } + public string SourceType { get; set; } public bool IncludeConfiguredSources { get; set; } diff --git a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs index 2c7e4ee69..3e52b3c8d 100644 --- a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs +++ b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs @@ -47,7 +47,7 @@ public ChocolateyNugetCredentialProvider(ChocolateyConfiguration config) public Task GetAsync(Uri uri, IWebProxy proxy, CredentialRequestType credentialType, string message, bool isRetry, bool nonInteractive, CancellationToken cancellationToken) { - if (uri == null) + if (uri is null) { throw new ArgumentNullException("uri"); } @@ -86,59 +86,55 @@ public Task GetAsync(Uri uri, IWebProxy proxy, CredentialReq } // credentials were not explicit - // discover based on closest match in sources - var candidateSources = _config.MachineSources.Where( - s => - { - var sourceUrl = s.Key.TrimEnd('/'); - - try - { - var sourceUri = new Uri(sourceUrl); - return sourceUri.Host.IsEqualTo(uri.Host) - && !string.IsNullOrWhiteSpace(s.Username) - && !string.IsNullOrWhiteSpace(s.EncryptedPassword); - } - catch (Exception) - { - this.Log().Error("Source '{0}' is not a valid Uri".FormatWith(sourceUrl)); - } - - return false; - }).ToList(); - + // find matching source(s) in sources list + var trimmedTargetUri = new Uri(uri.AbsoluteUri.TrimEnd('/')); MachineSourceConfiguration source = null; - - if (candidateSources.Count == 1) + // If the user has specified --source with a *named* source and not a URL, try to find the matching one + // with the correct URL for this credential request. + var namedExplicitSources = _config.ExplicitSources?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + .Where(s => !Uri.IsWellFormedUriString(s, UriKind.Absolute)) + .ToList(); + if (namedExplicitSources?.Count > 0) { - // only one match, use it - source = candidateSources.FirstOrDefault(); + // Uri.Equals() and == operator compare hostnames case-insensitively and the remainder of the url case-sensitively + // while ignoring #fragments on the URLs, but does care about trailing slashes, which we do not here. + source = _config.MachineSources + .Where(s => namedExplicitSources.Contains(s.Name) && new Uri(s.Key.TrimEnd('/')) == trimmedTargetUri) + .FirstOrDefault(); } - else if (candidateSources.Count > 1) + + if (source is null) { - // find the source that is the closest match - foreach (var candidateSource in candidateSources.OrEmpty()) + // Could not find a valid source by name, or the source(s) specified were all URLs. + // Try to look up the target URL in the saved machine sources to attempt to match credentials. + // + // Note: This behaviour remains as removing it would be a breaking change, but we may want + // to remove this in a future version, as specifying an explicit URL should potentially + // not go looking in the configuration file for saved credentials anyway. + var candidateSources = _config.MachineSources + .Where(s => !string.IsNullOrWhiteSpace(s.Username) + && !string.IsNullOrWhiteSpace(s.EncryptedPassword) + && Uri.TryCreate(s.Key.TrimEnd('/'), UriKind.Absolute, out var trimmedSourceUri) + && trimmedSourceUri == trimmedTargetUri) + .ToList(); + + if (candidateSources.Count == 1) { - var candidateRegEx = new Regex(Regex.Escape(candidateSource.Key.TrimEnd('/')), RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - if (candidateRegEx.IsMatch(uri.OriginalString.TrimEnd('/'))) - { - this.Log().Debug("Source selected will be '{0}'".FormatWith(candidateSource.Key.TrimEnd('/'))); - source = candidateSource; - break; - } + // only one match, use it + source = candidateSources.First(); } - - if (source == null && !isRetry) + else if (candidateSources.Count > 1 && !isRetry) { - // use the first source. If it fails, fall back to grabbing credentials from the user + // Use the credentials from the first found source, unless it's a retry (creds already tried and failed) + // use the first source. If it fails, fall back to grabbing credentials from the user. var candidateSource = candidateSources.First(); this.Log().Debug("Evaluated {0} candidate sources but was unable to find a match, using {1}".FormatWith(candidateSources.Count, candidateSource.Key.TrimEnd('/'))); source = candidateSource; } } - if (source == null) + if (source is null) { ICredentials credential = CredentialCache.DefaultNetworkCredentials; @@ -146,7 +142,6 @@ public Task GetAsync(Uri uri, IWebProxy proxy, CredentialReq { this.Log().Debug("This is a retry attempt. Asking user for credentials for '{0}'".FormatWith(uri.OriginalString)); credential = GetUserCredentials(uri, proxy, credentialType); - } return Task.FromResult(new CredentialResponse(credential)); From d2ac771bde53c9854f86fe1f4b4c944df81f4e9a Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Mon, 18 Nov 2024 17:54:34 -0500 Subject: [PATCH 08/15] (#3565) Add unit tests for ChocolateyNugetCredentialProvider These tests ensure that the use cases we expect to handle in the credential provider are appropriately handled according to our expectations, based on the user-provided input and the transformed input that is left in configuration.Sources once the credential provider typically gets queried. --- src/chocolatey.tests/chocolatey.tests.csproj | 1 + .../ChocolateyNugetCredentialProviderSpecs.cs | 320 ++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index c07571964..97e4b7d4f 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -186,6 +186,7 @@ + diff --git a/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs b/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs new file mode 100644 index 000000000..efab38954 --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs @@ -0,0 +1,320 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using chocolatey.infrastructure.app.configuration; +using chocolatey.infrastructure.app.nuget; +using FluentAssertions; +using Moq; +using NuGet.Configuration; +using NUnit.Framework; + +namespace chocolatey.tests.infrastructure.app.nuget +{ + public class ChocolateyNugetCredentialProviderSpecs + { + public abstract class ChocolateyNugetCredentialProviderSpecsBase : TinySpec + { + protected ChocolateyConfiguration Configuration; + protected ChocolateyNugetCredentialProvider Provider; + + protected const string Username = "user"; + protected const string Password = "totally_secure_password!!!"; + protected const string TargetSourceName = "testsource"; + protected const string TargetSourceUrl = "https://testserver.org/repository/test-repository"; + protected Uri TargetSourceUri = new Uri(TargetSourceUrl); + + protected NetworkCredential Result; + + private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); + protected CancellationToken CancellationToken + { + get + { + return _tokenSource.Token; + } + } + + public override void Context() + { + Configuration = new ChocolateyConfiguration(); + Provider = new ChocolateyNugetCredentialProvider(Configuration); + + Configuration.Information.IsInteractive = false; + Configuration.MachineSources = new List + { + new MachineSourceConfiguration + { + AllowSelfService = true, + VisibleToAdminsOnly = false, + EncryptedPassword = NugetEncryptionUtility.EncryptString("otherPassword"), + Username = "otherUserName", + Key = "https://someotherplace.com/repository/things/", + Name = "not-this-one", + Priority = 1, + }, + new MachineSourceConfiguration + { + AllowSelfService = true, + VisibleToAdminsOnly = false, + EncryptedPassword = NugetEncryptionUtility.EncryptString(Password), + Username = Username, + Key = TargetSourceUrl, + Name = TargetSourceName, + Priority = 1, + }, + }; + } + + [OneTimeSetUp] + public async Task OneTimeSetup() + { + await With(); + } + + public virtual async Task With() + { + var result = await Provider.GetAsync(TargetSourceUri, proxy: null, CredentialRequestType.Unauthorized, message: string.Empty, isRetry: false, nonInteractive: true, CancellationToken); + Result = result.Credentials as NetworkCredential; + } + } + + public class When_using_explicit_credentials_and_source_param : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Because() + { + Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; + Configuration.SourceCommand.Username = "user"; + Configuration.SourceCommand.Password = "totally_secure_password!!!"; + } + + [Fact] + public void Creates_a_valid_credential() + { + Result.Should().NotBeNull(); + } + + [Fact] + public void Provides_the_correct_username() + { + Result.UserName.Should().Be("user"); + } + + [Fact] + public void Provides_the_correct_password() + { + Result.Password.Should().Be("totally_secure_password!!!"); + } + } + + public class When_a_source_name_is_provided : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Because() + { + Configuration.Sources = TargetSourceUrl; + Configuration.ExplicitSources = TargetSourceName; + } + + [Fact] + public void Finds_the_saved_source_and_returns_the_credential() + { + Result.Should().NotBeNull(); + } + + [Fact] + public void Provides_the_correct_username() + { + Result.UserName.Should().Be(Username); + } + + [Fact] + public void Provides_the_correct_password() + { + Result.Password.Should().Be(Password); + } + } + + public class When_a_source_url_matching_a_saved_source_is_provided : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Because() + { + Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; + } + + [Fact] + public void Finds_the_saved_source_and_returns_the_credential() + { + Result.Should().NotBeNull(); + } + + [Fact] + public void Provides_the_correct_username() + { + Result.UserName.Should().Be(Username); + } + + [Fact] + public void Provides_the_correct_password() + { + Result.Password.Should().Be(Password); + } + } + + public class Looks_up_source_url_when_name_and_credentials_is_provided : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Because() + { + Configuration.Sources = TargetSourceUrl; + Configuration.ExplicitSources = TargetSourceName; + + Configuration.SourceCommand.Username = "user"; + Configuration.SourceCommand.Password = "totally_secure_password!!!"; + } + + [Fact] + public void Creates_and_returns_the_credential() + { + Result.Should().NotBeNull(); + } + + [Fact] + public void Provides_the_correct_username() + { + Result.UserName.Should().Be(Username); + } + + [Fact] + public void Provides_the_correct_password() + { + Result.Password.Should().Be(Password); + } + } + + public class When_no_matching_source_is_found_by_url : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Because() + { + Configuration.Sources = Configuration.ExplicitSources = "https://unknownurl.com/api/v2/"; + } + + public override async Task With() + { + var result = await Provider.GetAsync(new Uri("https://unknownurl.com/api/v2/"), proxy: null, CredentialRequestType.Unauthorized, message: string.Empty, isRetry: false, nonInteractive: true, CancellationToken); + Result = result.Credentials as NetworkCredential; + } + + [Fact] + public void Returns_the_default_network_credential() + { + Result.Should().Be(CredentialCache.DefaultNetworkCredentials); + } + } + + public class When_multiple_named_sources_are_provided : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Because() + { + Configuration.Sources = Configuration.MachineSources.Select(s => s.Key).Join(";"); + Configuration.ExplicitSources = Configuration.MachineSources.Select(s => s.Name).Join(";"); + } + + [Fact] + public void Finds_the_correct_saved_source_for_the_target_uri_and_returns_the_credential() + { + Result.Should().NotBeNull(); + } + + [Fact] + public void Provides_the_correct_username() + { + Result.UserName.Should().Be(Username); + } + + [Fact] + public void Provides_the_correct_password() + { + Result.Password.Should().Be(Password); + } + } + + public class When_multiple_source_urls_are_provided : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Because() + { + Configuration.Sources = Configuration.ExplicitSources = $"https://unknownurl.com/api/v2/;{TargetSourceUrl}"; + } + + [Fact] + public void Finds_the_saved_source_and_returns_the_credential() + { + Result.Should().NotBeNull(); + } + + [Fact] + public void Provides_the_correct_username() + { + Result.UserName.Should().Be(Username); + } + + [Fact] + public void Provides_the_correct_password() + { + Result.Password.Should().Be(Password); + } + } + + public class When_a_mix_of_named_and_url_sources_are_provided : ChocolateyNugetCredentialProviderSpecsBase + { + public override void Because() + { + Configuration.Sources = $"https://unknownurl.com/api/v2/;{TargetSourceUrl}"; + Configuration.ExplicitSources = $"https://unknownurl.com/api/v2/;{TargetSourceName}"; + } + + [Fact] + public void Finds_the_saved_source_and_returns_the_credential() + { + Result.Should().NotBeNull(); + } + + [Fact] + public void Provides_the_correct_username() + { + Result.UserName.Should().Be(Username); + } + + [Fact] + public void Provides_the_correct_password() + { + Result.Password.Should().Be(Password); + } + } + + // This is a regression test for issue #3565 + public class When_a_url_matching_the_hostname_only_of_a_saved_source_is_provided : ChocolateyNugetCredentialProviderSpecsBase + { + private Uri _otherRepoUri; + public override void Because() + { + _otherRepoUri = new Uri(TargetSourceUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped) + "/other_path/repository/"); + Configuration.Sources = Configuration.ExplicitSources = _otherRepoUri.AbsoluteUri; + } + + public override async Task With() + { + var result = await Provider.GetAsync(new Uri("https://unknownurl.com/api/v2/"), proxy: null, CredentialRequestType.Unauthorized, message: string.Empty, isRetry: false, nonInteractive: true, CancellationToken); + Result = result.Credentials as NetworkCredential; + } + + [Fact] + public void Returns_the_default_network_credential() + { + Result.Should().Be(CredentialCache.DefaultNetworkCredentials); + } + } + } +} From 7c41c31a925d63b1bb7957b7de012d2fe2598713 Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Wed, 20 Nov 2024 11:06:09 -0800 Subject: [PATCH 09/15] (#3565) Add Pester tests for Credential Provider Add Pester tests to ensure we don't inadvertently bleed configured credentials into scenarios where they should not be used. --- .../Chocolatey/Disable-ChocolateySource.ps1 | 6 +- .../Chocolatey/Enable-ChocolateySource.ps1 | 4 +- .../Chocolatey/Get-ChocolateySource.ps1 | 11 ++++ .../features/CredentialProvider.Tests.ps1 | 63 +++++++++++++++++++ 4 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 tests/helpers/common/Chocolatey/Get-ChocolateySource.ps1 create mode 100644 tests/pester-tests/features/CredentialProvider.Tests.ps1 diff --git a/tests/helpers/common/Chocolatey/Disable-ChocolateySource.ps1 b/tests/helpers/common/Chocolatey/Disable-ChocolateySource.ps1 index 523839f5c..58cb264f5 100644 --- a/tests/helpers/common/Chocolatey/Disable-ChocolateySource.ps1 +++ b/tests/helpers/common/Chocolatey/Disable-ChocolateySource.ps1 @@ -8,10 +8,8 @@ function Disable-ChocolateySource { [Parameter()] [switch]$All ) - # Significantly weird behaviour with piping this source list by property name. - $CurrentSources = (Invoke-Choco source list -r).Lines | ConvertFrom-ChocolateyOutput -Command SourceList | Where-Object { - $_.Name -like $Name - } + + $CurrentSources = Get-ChocolateySource -Name $Name foreach ($Source in $CurrentSources) { $null = Invoke-Choco source disable --name $Source.Name } diff --git a/tests/helpers/common/Chocolatey/Enable-ChocolateySource.ps1 b/tests/helpers/common/Chocolatey/Enable-ChocolateySource.ps1 index 6a44cd184..6f6a2f1ab 100644 --- a/tests/helpers/common/Chocolatey/Enable-ChocolateySource.ps1 +++ b/tests/helpers/common/Chocolatey/Enable-ChocolateySource.ps1 @@ -9,9 +9,7 @@ function Enable-ChocolateySource { [switch]$All ) # Significantly weird behaviour with piping this source list by property name. - $CurrentSources = (Invoke-Choco source list -r).Lines | ConvertFrom-ChocolateyOutput -Command SourceList | Where-Object { - $_.Name -like $Name - } + $CurrentSources = Get-ChocolateySource -Name $Name foreach ($Source in $CurrentSources) { $null = Invoke-Choco source enable --name $Source.Name } diff --git a/tests/helpers/common/Chocolatey/Get-ChocolateySource.ps1 b/tests/helpers/common/Chocolatey/Get-ChocolateySource.ps1 new file mode 100644 index 000000000..7b097500a --- /dev/null +++ b/tests/helpers/common/Chocolatey/Get-ChocolateySource.ps1 @@ -0,0 +1,11 @@ +function Get-ChocolateySource { + [CmdletBinding()] + param( + [Parameter()] + [string]$Name = "*" + ) + # Significantly weird behaviour with piping this source list by property name. + (Invoke-Choco source list -r).Lines | ConvertFrom-ChocolateyOutput -Command SourceList | Where-Object { + $_.Name -like $Name + } +} diff --git a/tests/pester-tests/features/CredentialProvider.Tests.ps1 b/tests/pester-tests/features/CredentialProvider.Tests.ps1 new file mode 100644 index 000000000..005f330bc --- /dev/null +++ b/tests/pester-tests/features/CredentialProvider.Tests.ps1 @@ -0,0 +1,63 @@ +Describe 'Ensuring credentials do not bleed from configured sources' -Tag CredentialProvider -ForEach @( + @{ + Command = 'info' + ExitCode = 0 +} + @{ + Command = 'install' + ExitCode = 1 +} + @{ + Command = 'outdated' + ExitCode = 0 +} + @{ + Command = 'search' + ExitCode = 1 +} + @{ + Command = 'upgrade' + ExitCode = 1 +} + @{ + Command = 'download' + ExitCode = 1 +} +) { + BeforeDiscovery { + $HasLicensedExtension = Test-PackageIsEqualOrHigher -PackageName 'chocolatey.extension' -Version '6.0.0' + } + + BeforeAll { + Initialize-ChocolateyTestInstall + Disable-ChocolateySource -All + Enable-ChocolateySource -Name 'hermes' + $SetupSource = Get-ChocolateySource -Name 'hermes-setup' + Remove-Item download -force -recurse + } + + # Skip the download command if chocolatey.extension is not installed. + Context 'Command ()' -Skip:($Command -eq 'download' -and -not $HasLicensedExtension) { + BeforeAll { + # The package used ultimately doesn't matter as we don't expect to find it. + # Picking a package that should be found if the behaviour changes. + $PackageUnderTest = 'chocolatey-compatibility.extension' + Restore-ChocolateyInstallSnapshot + # Chocolatey will prompt for credentials, we need to force something in there, and this will do that. + $Output = 'n' | Invoke-Choco $Command $PackageUnderTest --confirm --source="'$($SetupSource.Url)'" + } + + AfterAll { + Remove-ChocolateyInstallSnapshot + } + + It 'Exits Correctly ()' { + $Output.ExitCode | Should -Be $ExitCode -Because $Output.String + } + + It 'Outputs error message' { + $FilteredOutput = $Output.Lines -match "Failed to fetch results from V2 feed at '$($SetupSource.Url.Trim('/'))" + $FilteredOutput.Count | Should -BeGreaterOrEqual 1 -Because $Output.String + } + } +} From 0f9e5ad21abd1f19e2ed09819797a22cb4c42c1a Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Thu, 21 Nov 2024 15:19:28 -0800 Subject: [PATCH 10/15] (#3565) Address review comments --- .../ChocolateyNugetCredentialProviderSpecs.cs | 98 ++++++++++--------- .../ChocolateyNugetCredentialProvider.cs | 1 + 2 files changed, 54 insertions(+), 45 deletions(-) diff --git a/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs b/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs index efab38954..7805c03cf 100644 --- a/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/nuget/ChocolateyNugetCredentialProviderSpecs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -70,10 +70,9 @@ public override void Context() }; } - [OneTimeSetUp] - public async Task OneTimeSetup() + public override void Because() { - await With(); + With().Wait(); } public virtual async Task With() @@ -83,91 +82,95 @@ public virtual async Task With() } } - public class When_using_explicit_credentials_and_source_param : ChocolateyNugetCredentialProviderSpecsBase + public class When_Using_Explicit_Credentials_And_Source_Param : ChocolateyNugetCredentialProviderSpecsBase { - public override void Because() + public override void Context() { + base.Context(); Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; Configuration.SourceCommand.Username = "user"; Configuration.SourceCommand.Password = "totally_secure_password!!!"; } [Fact] - public void Creates_a_valid_credential() + public void Should_Create_Credentials() { Result.Should().NotBeNull(); } [Fact] - public void Provides_the_correct_username() + public void Should_Provide_The_Correct_Username() { Result.UserName.Should().Be("user"); } [Fact] - public void Provides_the_correct_password() + public void Should_Provide_The_Correct_Password() { Result.Password.Should().Be("totally_secure_password!!!"); } } - public class When_a_source_name_is_provided : ChocolateyNugetCredentialProviderSpecsBase + public class When_A_Source_Name_Is_Provided : ChocolateyNugetCredentialProviderSpecsBase { - public override void Because() + public override void Context() { + base.Context(); Configuration.Sources = TargetSourceUrl; Configuration.ExplicitSources = TargetSourceName; } [Fact] - public void Finds_the_saved_source_and_returns_the_credential() + public void Should_Find_The_Saved_Source_And_Returns_The_Credential() { Result.Should().NotBeNull(); } [Fact] - public void Provides_the_correct_username() + public void Should_Provide_The_Correct_Username() { Result.UserName.Should().Be(Username); } [Fact] - public void Provides_the_correct_password() + public void Should_Provide_The_Correct_Password() { Result.Password.Should().Be(Password); } } - public class When_a_source_url_matching_a_saved_source_is_provided : ChocolateyNugetCredentialProviderSpecsBase + public class When_A_Source_Url_Matching_A_Saved_Source_Is_Provided : ChocolateyNugetCredentialProviderSpecsBase { - public override void Because() + public override void Context() { + base.Context(); Configuration.Sources = Configuration.ExplicitSources = TargetSourceUrl; } [Fact] - public void Finds_the_saved_source_and_returns_the_credential() + public void Should_Find_The_Saved_Source_And_Return_The_Credential() { Result.Should().NotBeNull(); } [Fact] - public void Provides_the_correct_username() + public void Should_Provide_The_Correct_Username() { Result.UserName.Should().Be(Username); } [Fact] - public void Provides_the_correct_password() + public void Should_Provide_The_Correct_Password() { Result.Password.Should().Be(Password); } } - public class Looks_up_source_url_when_name_and_credentials_is_provided : ChocolateyNugetCredentialProviderSpecsBase + public class Looks_Up_Source_Url_When_Name_And_Credentials_Is_Provided : ChocolateyNugetCredentialProviderSpecsBase { - public override void Because() + public override void Context() { + base.Context(); Configuration.Sources = TargetSourceUrl; Configuration.ExplicitSources = TargetSourceName; @@ -176,28 +179,29 @@ public override void Because() } [Fact] - public void Creates_and_returns_the_credential() + public void Should_Create_And_Return_The_Credential() { Result.Should().NotBeNull(); } [Fact] - public void Provides_the_correct_username() + public void Should_Provide_The_Correct_Username() { Result.UserName.Should().Be(Username); } [Fact] - public void Provides_the_correct_password() + public void Should_Provide_The_Correct_Password() { Result.Password.Should().Be(Password); } } - public class When_no_matching_source_is_found_by_url : ChocolateyNugetCredentialProviderSpecsBase + public class When_No_Matching_Source_Is_Found_By_Url : ChocolateyNugetCredentialProviderSpecsBase { - public override void Because() + public override void Context() { + base.Context(); Configuration.Sources = Configuration.ExplicitSources = "https://unknownurl.com/api/v2/"; } @@ -208,98 +212,102 @@ public override async Task With() } [Fact] - public void Returns_the_default_network_credential() + public void Should_Return_The_Default_Network_Credential() { Result.Should().Be(CredentialCache.DefaultNetworkCredentials); } } - public class When_multiple_named_sources_are_provided : ChocolateyNugetCredentialProviderSpecsBase + public class When_Multiple_Named_Sources_Are_Provided : ChocolateyNugetCredentialProviderSpecsBase { - public override void Because() + public override void Context() { + base.Context(); Configuration.Sources = Configuration.MachineSources.Select(s => s.Key).Join(";"); Configuration.ExplicitSources = Configuration.MachineSources.Select(s => s.Name).Join(";"); } [Fact] - public void Finds_the_correct_saved_source_for_the_target_uri_and_returns_the_credential() + public void Should_Find_The_Correct_Saved_Source_For_The_Target_Uri_And_Return_The_Credential() { Result.Should().NotBeNull(); } [Fact] - public void Provides_the_correct_username() + public void Should_Provide_The_Correct_Username() { Result.UserName.Should().Be(Username); } [Fact] - public void Provides_the_correct_password() + public void Should_Provide_The_Correct_Password() { Result.Password.Should().Be(Password); } } - public class When_multiple_source_urls_are_provided : ChocolateyNugetCredentialProviderSpecsBase + public class When_Multiple_Source_Urls_Are_Provided : ChocolateyNugetCredentialProviderSpecsBase { - public override void Because() + public override void Context() { + base.Context(); Configuration.Sources = Configuration.ExplicitSources = $"https://unknownurl.com/api/v2/;{TargetSourceUrl}"; } [Fact] - public void Finds_the_saved_source_and_returns_the_credential() + public void Should_Find_The_Saved_Source_And_Return_The_Credential() { Result.Should().NotBeNull(); } [Fact] - public void Provides_the_correct_username() + public void Should_Provide_The_Correct_Username() { Result.UserName.Should().Be(Username); } [Fact] - public void Provides_the_correct_password() + public void Should_Provide_The_Correct_Password() { Result.Password.Should().Be(Password); } } - public class When_a_mix_of_named_and_url_sources_are_provided : ChocolateyNugetCredentialProviderSpecsBase + public class When_A_Mix_Of_Named_And_Url_Sources_Are_Provided : ChocolateyNugetCredentialProviderSpecsBase { - public override void Because() + public override void Context() { + base.Context(); Configuration.Sources = $"https://unknownurl.com/api/v2/;{TargetSourceUrl}"; Configuration.ExplicitSources = $"https://unknownurl.com/api/v2/;{TargetSourceName}"; } [Fact] - public void Finds_the_saved_source_and_returns_the_credential() + public void Should_Find_The_Saved_Source_And_Return_The_Credential() { Result.Should().NotBeNull(); } [Fact] - public void Provides_the_correct_username() + public void Should_Provide_The_Correct_Username() { Result.UserName.Should().Be(Username); } [Fact] - public void Provides_the_correct_password() + public void Should_Provide_The_Correct_Password() { Result.Password.Should().Be(Password); } } // This is a regression test for issue #3565 - public class When_a_url_matching_the_hostname_only_of_a_saved_source_is_provided : ChocolateyNugetCredentialProviderSpecsBase + public class When_A_Url_Matching_The_Hostname_Only_Of_A_Saved_Source_Is_Provided : ChocolateyNugetCredentialProviderSpecsBase { private Uri _otherRepoUri; - public override void Because() + public override void Context() { + base.Context(); _otherRepoUri = new Uri(TargetSourceUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped) + "/other_path/repository/"); Configuration.Sources = Configuration.ExplicitSources = _otherRepoUri.AbsoluteUri; } @@ -311,7 +319,7 @@ public override async Task With() } [Fact] - public void Returns_the_default_network_credential() + public void Should_Return_The_Default_Network_Credential() { Result.Should().Be(CredentialCache.DefaultNetworkCredentials); } diff --git a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs index 3e52b3c8d..a140839df 100644 --- a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs +++ b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs @@ -95,6 +95,7 @@ public Task GetAsync(Uri uri, IWebProxy proxy, CredentialReq var namedExplicitSources = _config.ExplicitSources?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .Where(s => !Uri.IsWellFormedUriString(s, UriKind.Absolute)) .ToList(); + if (namedExplicitSources?.Count > 0) { // Uri.Equals() and == operator compare hostnames case-insensitively and the remainder of the url case-sensitively From 667fbc70c2c50be8f99b251492a4cd75e52ec53c Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Fri, 22 Nov 2024 10:24:29 -0800 Subject: [PATCH 11/15] (#3565) Deprecate ListCommand.ExplicitSource Since we've added an ExplicitSources property to the top-level configuration object, we do not need the ListCommand.ExplicitSource property. It is being deprecated here to be removed in version 3. To determine if an explicit source was provided, we can look at if ExplicitSources is set. --- .../infrastructure.app/commands/ChocolateyInfoCommand.cs | 5 +---- .../infrastructure.app/commands/ChocolateyInstallCommand.cs | 6 +----- .../infrastructure.app/commands/ChocolateyListCommand.cs | 2 +- .../commands/ChocolateyOutdatedCommand.cs | 6 +----- .../infrastructure.app/commands/ChocolateySearchCommand.cs | 6 +----- .../infrastructure.app/commands/ChocolateyUpgradeCommand.cs | 6 +----- .../configuration/ChocolateyConfiguration.cs | 1 + 7 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs index 04e4c8a2d..d7ee4e97e 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs @@ -38,10 +38,7 @@ public override void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConf .Add( "s=|source=", "Source - Source location for install. Can use special 'windowsfeatures', 'ruby', 'cygwin', or 'python' sources. Defaults to configured sources.", - option => { - configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); - configuration.ListCommand.ExplicitSource = true; - }) + option => configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe()) .Add( "l|lo|localonly|local-only", "LocalOnly - Only search against local machine items.", diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs index eae87ebd9..2ca1c0c5a 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs @@ -53,11 +53,7 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi optionSet .Add("s=|source=", "Source - The source to find the package(s) to install. Special sources include: ruby, cygwin, windowsfeatures, and python. To specify more than one source, pass it with a semi-colon separating the values (e.g. \"'source1;source2'\"). Defaults to default feeds.", - option => - { - configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); - configuration.ListCommand.ExplicitSource = true; - }) + option => configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe()) .Add("version=", "Version - A specific version to install. Defaults to unspecified.", option => configuration.Version = option.UnquoteSafe()) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs index 5fc100df5..a7cd59468 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs @@ -252,7 +252,7 @@ public virtual void Run(ChocolateyConfiguration config) { // Would have liked to have done this in the Validate method, but can't, since the SourceType // hasn't yet been set, since the sources have not yet been parsed. - if (config.ListCommand.ExplicitSource && config.SourceType == SourceTypes.Normal) + if (!string.IsNullOrWhiteSpace(config.ExplicitSources) && config.SourceType == SourceTypes.Normal) { throw new ApplicationException("When using the '--source' option with the 'choco list' command, only a named alternative source can be provided."); } diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs index b4f8f4ebe..6c6b46f42 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs @@ -40,11 +40,7 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi optionSet .Add("s=|source=", "Source - The source to find the package(s) to install. Special sources include: ruby, cygwin, windowsfeatures, and python. To specify more than one source, pass it with a semi-colon separating the values (e.g. \"'source1;source2'\"). Defaults to default feeds.", - option => - { - configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); - configuration.ListCommand.ExplicitSource = true; - }) + option => configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe()) .Add("u=|user=", "User - used with authenticated feeds. Defaults to empty.", option => configuration.SourceCommand.Username = option.UnquoteSafe()) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateySearchCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateySearchCommand.cs index ee80059d0..34acb867e 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateySearchCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateySearchCommand.cs @@ -43,11 +43,7 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi optionSet .Add("s=|source=", "Source - Source location for install. Can use special 'windowsfeatures', 'ruby', 'cygwin', or 'python' sources. Defaults to sources.", - option => - { - configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); - configuration.ListCommand.ExplicitSource = true; - }) + option => configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe()) .Add("idonly|id-only", "Id Only - Only return Package Ids in the list results.", option => configuration.ListCommand.IdOnly = option != null) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs index f83500a27..04206f592 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs @@ -53,11 +53,7 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi optionSet .Add("s=|source=", "Source - The source to find the package(s) to install. Special sources include: ruby, cygwin, windowsfeatures, and python. To specify more than one source, pass it with a semi-colon separating the values (e.g. \"'source1;source2'\"). Defaults to default feeds.", - option => - { - configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe(); - configuration.ListCommand.ExplicitSource = true; - }) + option => configuration.Sources = configuration.ExplicitSources = option.UnquoteSafe()) .Add("version=", "Version - A specific version to install. Defaults to unspecified.", option => configuration.Version = option.UnquoteSafe()) diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index b823cf6d3..9954353c4 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -603,6 +603,7 @@ public ListCommandConfiguration() public bool NotBroken { get; set; } public bool IncludeVersionOverrides { get; set; } public bool ExplicitPageSize { get; set; } + [Obsolete("This property is deprecated and will be removed in v3. Check if the top-level ExplicitSources property is set instead.")] public bool ExplicitSource { get; set; } } From 56f27052912b63d780f7ed9ededd8e8433a7056f Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Mon, 25 Nov 2024 06:48:30 -0800 Subject: [PATCH 12/15] (#3565) Do case insensitive comparisons --- .../nuget/ChocolateyNugetCredentialProvider.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs index a140839df..ac17ac30a 100644 --- a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs +++ b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs @@ -92,16 +92,19 @@ public Task GetAsync(Uri uri, IWebProxy proxy, CredentialReq // If the user has specified --source with a *named* source and not a URL, try to find the matching one // with the correct URL for this credential request. - var namedExplicitSources = _config.ExplicitSources?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + // Lower case all of the explicitly named sources so that we can use .Contains to compare them. + var namedExplicitSources = _config.ExplicitSources?.ToLower().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .Where(s => !Uri.IsWellFormedUriString(s, UriKind.Absolute)) .ToList(); if (namedExplicitSources?.Count > 0) { - // Uri.Equals() and == operator compare hostnames case-insensitively and the remainder of the url case-sensitively - // while ignoring #fragments on the URLs, but does care about trailing slashes, which we do not here. + // Instead of using Uri.Equals(), we're using Uri.Compare() on the HttpRequestUrl components as this allows + // us to ignore the case of everything. source = _config.MachineSources - .Where(s => namedExplicitSources.Contains(s.Name) && new Uri(s.Key.TrimEnd('/')) == trimmedTargetUri) + .Where(s => namedExplicitSources.Contains(s.Name.ToLower()) + && Uri.TryCreate(s.Key.TrimEnd('/'), UriKind.Absolute, out var trimmedSourceUri) + && Uri.Compare(trimmedSourceUri, trimmedTargetUri, UriComponents.HttpRequestUrl, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0) .FirstOrDefault(); } @@ -113,11 +116,12 @@ public Task GetAsync(Uri uri, IWebProxy proxy, CredentialReq // Note: This behaviour remains as removing it would be a breaking change, but we may want // to remove this in a future version, as specifying an explicit URL should potentially // not go looking in the configuration file for saved credentials anyway. + // See GitHub Issue: https://github.com/chocolatey/choco/issues/3573 var candidateSources = _config.MachineSources .Where(s => !string.IsNullOrWhiteSpace(s.Username) && !string.IsNullOrWhiteSpace(s.EncryptedPassword) && Uri.TryCreate(s.Key.TrimEnd('/'), UriKind.Absolute, out var trimmedSourceUri) - && trimmedSourceUri == trimmedTargetUri) + && Uri.Compare(trimmedSourceUri, trimmedTargetUri, UriComponents.HttpRequestUrl, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0) .ToList(); if (candidateSources.Count == 1) From 30479ce9a0be092dd51e583a028e25534813ea7b Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Tue, 26 Nov 2024 08:04:58 -0800 Subject: [PATCH 13/15] (#3565) Add comments around Pester tests Add some comments to the Pester Tests to better describe the purpose of the test and why some commands are expected to exit 0 and others not. --- .../features/CredentialProvider.Tests.ps1 | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/pester-tests/features/CredentialProvider.Tests.ps1 b/tests/pester-tests/features/CredentialProvider.Tests.ps1 index 005f330bc..5ab3887fc 100644 --- a/tests/pester-tests/features/CredentialProvider.Tests.ps1 +++ b/tests/pester-tests/features/CredentialProvider.Tests.ps1 @@ -1,28 +1,35 @@ -Describe 'Ensuring credentials do not bleed from configured sources' -Tag CredentialProvider -ForEach @( +# These tests are to ensure that credentials from one configured and enabled source are not +# picked up and used when a URL is matching based on the hostname. These tests use an authenticated +# source without explicitly providing a username/password. It is expected that Chocolatey will prompt for +# the username and password. +Describe 'Ensuring credentials do not bleed from configured sources' -Tag CredentialProvider -ForEach @( + # Info and outdated are returning 0 in all test cases we've thrown at them. + # Suspect the only way either of these commands actually return non-zero is in a scenario where + # something goes catastrophically wrong outside of the actual command calls. @{ Command = 'info' ExitCode = 0 -} - @{ - Command = 'install' - ExitCode = 1 -} + } @{ Command = 'outdated' ExitCode = 0 -} + } + @{ + Command = 'install' + ExitCode = 1 + } @{ Command = 'search' ExitCode = 1 -} + } @{ Command = 'upgrade' ExitCode = 1 -} + } @{ Command = 'download' ExitCode = 1 -} + } ) { BeforeDiscovery { $HasLicensedExtension = Test-PackageIsEqualOrHigher -PackageName 'chocolatey.extension' -Version '6.0.0' From 7387944c4754c19b1fbab67bc9a8e58533ef516a Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Wed, 27 Nov 2024 11:55:25 -0800 Subject: [PATCH 14/15] (#3565) Add authenticated source for Pester tests Add the push source to configuration so that we are able to push to it successfully. When anonymous access is disabled, Chocolatey will now only use credentials it has configured by the exact source URL, and not just one that matches the hostname. As such, this test started failing and needs to be updated to ensure the credentials can be used. See https://github.com/chocolatey/choco/issues/2026 for more details. --- tests/pester-tests/commands/choco-push.Tests.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/pester-tests/commands/choco-push.Tests.ps1 b/tests/pester-tests/commands/choco-push.Tests.ps1 index 63a2a002f..8525bd970 100644 --- a/tests/pester-tests/commands/choco-push.Tests.ps1 +++ b/tests/pester-tests/commands/choco-push.Tests.ps1 @@ -152,6 +152,9 @@ Describe 'choco push nuget <_> repository' -Tag Chocolatey, PushCommand -Skip:($ if ($UseConfig) { $null = Invoke-Choco apikey add --source $RepositoryToUse$RepositoryEndpoint --api-key $ApiKey + # Add the Nuget source so that the push doesn't prompt for credentials. + # See https://github.com/chocolatey/choco/issues/2026#issuecomment-2423828013 + $null = Invoke-Choco source add --name temporary-nuget --source $RepositoryToUse$RepositoryEndpoint --user $env:NUGET_SOURCE_USERNAME --password $env:NUGET_SOURCE_PASSWORD # Ensure the key is null (should always be, but scoping can be wonky) $KeyParameter = $null } else { From e852aff67a7d57c9dc2dca678a94c7fec6a86e2d Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Wed, 27 Nov 2024 14:00:15 -0800 Subject: [PATCH 15/15] (tests) Fixup user agent regex for Extension Correct the Licensed version capture so that the regex matches when Licensed Extension with User Agent update is installed. Also adds a validation that the LicensedVersion is present when Licensed Extension is installed. --- tests/pester-tests/features/UserAgent.Tests.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/pester-tests/features/UserAgent.Tests.ps1 b/tests/pester-tests/features/UserAgent.Tests.ps1 index 1b3f43374..543f188aa 100644 --- a/tests/pester-tests/features/UserAgent.Tests.ps1 +++ b/tests/pester-tests/features/UserAgent.Tests.ps1 @@ -72,10 +72,17 @@ Describe "Chocolatey User Agent" -Tag Chocolatey, UserAgent { $userAgent = $matches['UserAgent'] - $userAgent -match 'Chocolatey Command Line/(?[a-z0-9.-]+) ([a-z ]+/([a-z0-9.-]+) )?\((?[^,)]+)(?:, (?[^)]+))?\) via NuGet Client' | + $userAgent -match 'Chocolatey Command Line/(?[a-z0-9.-]+) ([a-z ]+/(?[a-z0-9.-]+) )?\((?[^,)]+)(?:, (?[^)]+))?\) via NuGet Client' | Should -BeTrue -Because "the user agent string should contain the choco.exe version, the licensed extension version if any, and any parent processes. $logLine" $matches['Version'] | Should -Be $ChocolateyVersion -Because "the user agent string should contain the currently running Chocolatey version. $logLine" + + if (Test-PackageIsEqualOrHigher -PackageName 'chocolatey.extension' -Version '6.3.0-alpha') { + # We are not asserting the Licensed Extension version here as the Chocolatey package version often + # mismatches the assembly version. + $matches['LicensedVersion'] | Should -Not -BeNullOrEmpty -Because "Chocolatey Licensed Extension is installed and should be in the user agent. $logLine" + } + $filteredProcesses = @($Processes | Where-Object { $_ -notin $ExcludedProcesses }) if ($filteredProcesses.Count -gt 1) {