From be131879e1385e571ad0f31c26b4f726d6240554 Mon Sep 17 00:00:00 2001
From: Madhusudhan-MSFT <53235553+Madhusudhan-MSFT@users.noreply.github.com>
Date: Mon, 21 Oct 2024 14:42:33 -0700
Subject: [PATCH] Fix for Source Argument Validation in SourceWorkflow for
Default Source Type (#4891)
Fix for Source Argument Validation in SourceWorkflow for Default Source
Type
[Issues:] The current validation in the source flow correctly detects
duplicate source names. However, when the source argument is validated
along with the source type, it allows different source names with the
same arguments for the empty source type. This happens because, during
source type comparison, if the source type is not provided, it defaults
to an empty string, which is compared against the default type
(Microsoft.PreIndexed). This allows multiple different source names with
the same argument to be considered valid sources. For an empty source
type, the default source type is assigned during the source add
operation, not beforehand. Consequently, after the source add operation
is finished, the source will have some source arguments, but only the
name will differ.
[Fix:]
For an empty source type, we should compare against the default source
to prevent different source names with the same arguments for the
default types. During validation, we obtain the default type to replace
the empty source type and use it for comparison to validate argument
duplication.
- Extended source tests to validate the duplicate source argument
scenario for the default source type.
- Fixed additional failing source origin tests with appropriate fixes.
[How Validated:]
- Compiled the latest modifications and deployed the
AppInstallerCLIPackage.
- Executed CLI SourceTests to ensure all tests pass without issues.
**[Manual validation:]**
**Before fix:**
![image](https://github.com/user-attachments/assets/591b3ec2-ae6c-42ac-8258-169596657c4a)
**After fix:**
![image](https://github.com/user-attachments/assets/2078b72a-d746-440c-a44e-2ec05c812e26)
- [x] I have signed the [Contributor License
Agreement](https://cla.opensource.microsoft.com/microsoft/winget-pkgs).
- [ ] This pull request is related to an issue.
-----
###### Microsoft Reviewers: [Open in
CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/4891)
---
.../Workflows/SourceFlow.cpp | 12 ++++++++++++
src/AppInstallerCLIE2ETests/SourceCommand.cs | 18 ++++++++++++++++++
.../Public/winget/RepositorySource.h | 3 +++
.../RepositorySource.cpp | 7 ++++++-
4 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp
index a6a1dee6d4..7d8df61ffd 100644
--- a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp
+++ b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp
@@ -52,6 +52,18 @@ namespace AppInstaller::CLI::Workflow
std::string_view arg = context.Args.GetArg(Args::Type::SourceArg);
std::string_view type = context.Args.GetArg(Args::Type::SourceType);
+ // In the absence of a specified type, the default is Microsoft.PreIndexed.Package for comparison.
+ // The default type assignment to the source takes place during the add operation (Source::Add in Repository.cpp).
+ // This is necessary for the comparison to function correctly; otherwise, it would allow the addition of multiple
+ // sources with different names but the same argument for all default type cases.
+ // For example, the following commands would be allowed, but they acts as different alias to same source:
+ // winget source add "mysource1" "https:\\mysource" --trust - level trusted
+ // winget source add "mysource2" "https:\\mysource" --trust - level trusted
+ if (type.empty())
+ {
+ type = Repository::Source::GetDefaultSourceType();
+ }
+
for (const auto& details : sourceList)
{
if (Utility::ICUCaseInsensitiveEquals(details.Name, name))
diff --git a/src/AppInstallerCLIE2ETests/SourceCommand.cs b/src/AppInstallerCLIE2ETests/SourceCommand.cs
index 27ee083e13..c873a6d72b 100644
--- a/src/AppInstallerCLIE2ETests/SourceCommand.cs
+++ b/src/AppInstallerCLIE2ETests/SourceCommand.cs
@@ -45,6 +45,9 @@ public void SourceAdd()
[Test]
public void SourceAddWithTrustLevel()
{
+ // Remove the test source.
+ TestCommon.RunAICLICommand("source remove", Constants.TestSourceName);
+
var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level trusted");
Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
Assert.True(result.StdOut.Contains("Done"));
@@ -62,6 +65,9 @@ public void SourceAddWithTrustLevel()
[Test]
public void SourceAddWithStoreOriginTrustLevel()
{
+ // Remove the test source.
+ TestCommon.RunAICLICommand("source remove", Constants.TestSourceName);
+
var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level storeOrigin");
Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_DATA_INTEGRITY_FAILURE, result.ExitCode);
Assert.True(result.StdOut.Contains("The source data is corrupted or tampered"));
@@ -103,6 +109,18 @@ public void SourceAddWithDuplicateName()
Assert.True(result.StdOut.Contains("A source with the given name already exists and refers to a different location"));
}
+ ///
+ /// Test source add with duplicate source url.
+ ///
+ [Test]
+ public void SourceAddWithDuplicateSourceUrl()
+ {
+ // Add source with duplicate url should fail
+ var result = TestCommon.RunAICLICommand("source add", $"TestSource2 {Constants.TestSourceUrl}");
+ Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_ARG_ALREADY_EXISTS, result.ExitCode);
+ Assert.True(result.StdOut.Contains("A source with a different name already refers to this location"));
+ }
+
///
/// Test source add with invalid url.
///
diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h
index 3bdcdb2822..c01a1611f9 100644
--- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h
+++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h
@@ -335,6 +335,9 @@ namespace AppInstaller::Repository
// Get a list of all available SourceDetails.
static std::vector GetCurrentSources();
+ // Get a default source type is the source type used when adding a source without specifying a type.
+ static std::string_view GetDefaultSourceType();
+
private:
void InitializeSourceReference(std::string_view name);
diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp
index 5c279f4de0..d178b6890d 100644
--- a/src/AppInstallerRepositoryCore/RepositorySource.cpp
+++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp
@@ -880,7 +880,7 @@ namespace AppInstaller::Repository
// AddSourceForDetails will also check for empty, but we need the actual type before that for validation.
if (sourceDetails.Type.empty())
{
- sourceDetails.Type = ISourceFactory::GetForType("")->TypeName();
+ sourceDetails.Type = GetDefaultSourceType();
}
AICLI_LOG(Repo, Info, << "Adding source: Name[" << sourceDetails.Name << "], Type[" << sourceDetails.Type << "], Arg[" << sourceDetails.Arg << "]");
@@ -1040,6 +1040,11 @@ namespace AppInstaller::Repository
}
}
+ std::string_view Source::GetDefaultSourceType()
+ {
+ return ISourceFactory::GetForType("")->TypeName();
+ }
+
#ifndef AICLI_DISABLE_TEST_HOOKS
void TestHook_SetSourceFactoryOverride(const std::string& type, std::function()>&& factory)
{