Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for setting source trust level #4216

Merged
merged 26 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ srs
startswith
STARTUPINFOW
STDMETHODCALLTYPE
storeorigin
STRRET
stylecop
subdir
Expand Down
8 changes: 8 additions & 0 deletions src/AppInstallerCLICore/Argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ namespace AppInstaller::CLI
return { type, "arg"_liv, 'a' };
case Execution::Args::Type::ForceSourceReset:
return { type, "force"_liv };
case Execution::Args::Type::SourceExplicit:
return { type, "explicit"_liv };
case Execution::Args::Type::SourceTrustLevel:
return { type, "trust-level"_liv };

//Hash Command
case Execution::Args::Type::HashFile:
Expand Down Expand Up @@ -340,6 +344,10 @@ namespace AppInstaller::CLI
return Argument{ type, Resource::String::SourceArgArgumentDescription, ArgumentType::Positional, true };
case Args::Type::SourceType:
return Argument{ type, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional };
case Args::Type::SourceExplicit:
return Argument{ type, Resource::String::SourceExplicitArgumentDescription, ArgumentType::Flag };
case Args::Type::SourceTrustLevel:
return Argument{ type, Resource::String::SourceTrustLevelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help };
case Args::Type::ValidateManifest:
return Argument{ type, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true };
case Args::Type::IgnoreWarnings:
Expand Down
25 changes: 25 additions & 0 deletions src/AppInstallerCLICore/Commands/SourceCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ namespace AppInstaller::CLI
Argument::ForType(Args::Type::SourceName).SetRequired(true),
Argument::ForType(Args::Type::SourceArg),
Argument::ForType(Args::Type::SourceType),
Argument::ForType(Args::Type::SourceTrustLevel),
Argument::ForType(Args::Type::CustomHeader),
Argument::ForType(Args::Type::AcceptSourceAgreements),
Argument::ForType(Args::Type::SourceExplicit),
};
}

Expand All @@ -72,6 +74,29 @@ namespace AppInstaller::CLI
return s_SourceCommand_HelpLink;
}

void SourceAddCommand::ValidateArgumentsInternal(Args& execArgs) const
{
if (execArgs.Contains(Execution::Args::Type::SourceTrustLevel))
{
try
{
std::string trustLevelArg = std::string{ execArgs.GetArg(Execution::Args::Type::SourceTrustLevel) };

for (auto trustLevel : Utility::Split(trustLevelArg, '|', true))
{
Repository::ConvertToSourceTrustLevelEnum(trustLevel);
}
}
catch (...)
{
auto validOptions = std::vector<Utility::LocIndString>{
Utility::LocIndString{ Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::None) },
Utility::LocIndString{ Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::Trusted) } };
throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::SourceTrustLevel).Name, Utility::Join(","_liv, validOptions)));
}
}
}

void SourceAddCommand::ExecuteInternal(Context& context) const
{
// Note: Group Policy for allowed sources is enforced at the RepositoryCore level
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Commands/SourceCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace AppInstaller::CLI
Utility::LocIndView HelpLink() const override;

protected:
void ValidateArgumentsInternal(Execution::Args& execArgs) const override;
void ExecuteInternal(Execution::Context& context) const override;
};

Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/ExecutionArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ namespace AppInstaller::CLI::Execution
SourceType,
SourceArg,
ForceSourceReset,
SourceExplicit,
SourceTrustLevel,

//Hash Command
HashFile,
Expand Down
4 changes: 4 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(SourceListName);
WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoneFound);
WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoSources);
WINGET_DEFINE_RESOURCE_STRINGID(SourceListExplicit);
WINGET_DEFINE_RESOURCE_STRINGID(SourceListTrustLevel);
WINGET_DEFINE_RESOURCE_STRINGID(SourceListType);
WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdated);
WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdatedNever);
Expand All @@ -558,12 +560,14 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveOne);
WINGET_DEFINE_RESOURCE_STRINGID(SourceRequiresAuthentication);
WINGET_DEFINE_RESOURCE_STRINGID(SourceExplicitArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(SourceResetAll);
WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(SourceResetForceArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(SourceResetListAndOverridePreamble);
WINGET_DEFINE_RESOURCE_STRINGID(SourceResetOne);
WINGET_DEFINE_RESOURCE_STRINGID(SourceTrustLevelArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(SourceTypeArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateAll);
WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandLongDescription);
Expand Down
21 changes: 18 additions & 3 deletions src/AppInstallerCLICore/Workflows/SourceFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,16 @@ namespace AppInstaller::CLI::Workflow
std::string_view name = context.Args.GetArg(Args::Type::SourceName);
std::string_view arg = context.Args.GetArg(Args::Type::SourceArg);
std::string_view type = context.Args.GetArg(Args::Type::SourceType);
bool isExplicit = context.Args.Contains(Args::Type::SourceExplicit);

Repository::Source sourceToAdd{ name, arg, type };
Repository::SourceTrustLevel trustLevel = Repository::SourceTrustLevel::None;
if (context.Args.Contains(Execution::Args::Type::SourceTrustLevel))
{
std::vector<std::string> trustLevelArgs = Utility::Split(std::string{ context.Args.GetArg(Execution::Args::Type::SourceTrustLevel) }, '|', true);
trustLevel = Repository::ConvertToSourceTrustLevelFlag(trustLevelArgs);
}

Repository::Source sourceToAdd{ name, arg, type, trustLevel, isExplicit};

if (context.Args.Contains(Execution::Args::Type::CustomHeader))
{
Expand Down Expand Up @@ -156,6 +164,8 @@ namespace AppInstaller::CLI::Workflow
table.OutputLine({ Resource::LocString(Resource::String::SourceListArg), source.Arg });
table.OutputLine({ Resource::LocString(Resource::String::SourceListData), source.Data });
table.OutputLine({ Resource::LocString(Resource::String::SourceListIdentifier), source.Identifier });
table.OutputLine({ Resource::LocString(Resource::String::SourceListTrustLevel), Repository::GetSourceTrustLevelForDisplay(source.TrustLevel)});
table.OutputLine({ Resource::LocString(Resource::String::SourceListExplicit), std::string{ Utility::ConvertBoolToString(source.Explicit) }});

if (source.LastUpdateTime == Utility::ConvertUnixEpochToSystemClock(0))
{
Expand All @@ -181,10 +191,10 @@ namespace AppInstaller::CLI::Workflow
}
else
{
Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListName, Resource::String::SourceListArg });
Execution::TableOutput<3> table(context.Reporter, { Resource::String::SourceListName, Resource::String::SourceListArg, Resource::String::SourceListExplicit });
for (const auto& source : sources)
{
table.OutputLine({ source.Name, source.Arg });
table.OutputLine({ source.Name, source.Arg, std::string{ Utility::ConvertBoolToString(source.Explicit) }});
}
table.Complete();
}
Expand All @@ -199,6 +209,7 @@ namespace AppInstaller::CLI::Workflow
}

const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>();

for (const auto& sd : sources)
{
Repository::Source source{ sd.Name };
Expand Down Expand Up @@ -303,6 +314,10 @@ namespace AppInstaller::CLI::Workflow
s.Arg = source.Arg;
s.Data = source.Data;
s.Identifier = source.Identifier;

std::vector<std::string_view> sourceTrustLevels = Repository::SourceTrustLevelFlagToList(source.TrustLevel);
s.TrustLevel = std::vector<std::string>(sourceTrustLevels.begin(), sourceTrustLevels.end());
s.Explicit = source.Explicit;
context.Reporter.Info() << s.ToJsonString() << std::endl;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLIE2ETests/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="Constants.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand Down
28 changes: 27 additions & 1 deletion src/AppInstallerCLIE2ETests/GroupPolicy.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="GroupPolicy.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand Down Expand Up @@ -180,6 +180,32 @@ public void EnableAdditionalSources()
Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
}

/// <summary>
/// Test additional sources with trust levels and explicit are enabled by policy.
/// </summary>
[Test]
public void EnableAdditionalSources_TrustLevel_Explicit()
{
// Remove the test source, then add it with policy.
TestCommon.RunAICLICommand("source remove", "TestSource");
var result = TestCommon.RunAICLICommand("source list", "TestSource");
Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode);

GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[]
{
"{\"Arg\":\"https://localhost:5001/TestKit\",\"Data\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Identifier\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Name\":\"TestSource\",\"Type\":\"Microsoft.PreIndexed.Package\",\"TrustLevel\":[\"Trusted\"],\"Explicit\":true}",
});

result = TestCommon.RunAICLICommand("source list", "TestSource");
Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
Assert.True(result.StdOut.Contains("Trust Level"));
Assert.True(result.StdOut.Contains("Trusted"));

var searchResult = TestCommon.RunAICLICommand("search", "TestExampleInstaller");
Assert.AreEqual(Constants.ErrorCode.ERROR_NO_SOURCES_DEFINED, searchResult.ExitCode);
Assert.True(searchResult.StdOut.Contains("No sources defined; add one with 'source add' or reset to defaults with 'source reset'"));
}

/// <summary>
/// Test enable allowed sources.
/// </summary>
Expand Down
12 changes: 11 additions & 1 deletion src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="GroupPolicyHelper.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand Down Expand Up @@ -385,6 +385,16 @@ public class GroupPolicySource
/// Gets or sets certificate pinning.
/// </summary>
public GroupPolicyCertificatePinning CertificatePinning { get; set; }

/// <summary>
/// Gets or sets the source trust levels.
/// </summary>
public string[] TrustLevel { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the source is explicit.
/// </summary>
public bool Explicit { get; set; }
}

/// <summary>
Expand Down
4 changes: 3 additions & 1 deletion src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="TestCommon.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand Down Expand Up @@ -701,6 +701,8 @@ public static void SetupTestSource(bool useGroupPolicyForTestSource = false)
},
},
},
TrustLevel = new string[] { "None" },
Explicit = false,
},
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLIE2ETests/SearchCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ public void SearchStoreWithBadPin()
},
},
},
TrustLevel = new string[] { "None" },
Explicit = false,
},
});

Expand Down
55 changes: 54 additions & 1 deletion src/AppInstallerCLIE2ETests/SourceCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,58 @@ public void SourceAdd()
TestCommon.RunAICLICommand("source remove", $"-n SourceTest");
}

/// <summary>
/// Test source add with trust level.
/// </summary>
[Test]
public void SourceAddWithTrustLevel()
{
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"));

var listResult = TestCommon.RunAICLICommand("source list", $"-n SourceTest");
Assert.AreEqual(Constants.ErrorCode.S_OK, listResult.ExitCode);
Assert.True(listResult.StdOut.Contains("Trust Level"));
Assert.True(listResult.StdOut.Contains("Trusted"));
TestCommon.RunAICLICommand("source remove", $"-n SourceTest");
}

/// <summary>
/// Test source add with store origin trust level.
/// </summary>
[Test]
public void SourceAddWithStoreOriginTrustLevel()
{
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"));
}

/// <summary>
/// Test source add with explicit flag. Packages should only appear if the source is explicitly declared.
/// </summary>
[Test]
public void SourceAddWithExplicit()
{
// Remove the test source.
TestCommon.RunAICLICommand("source remove", "TestSource");

var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --explicit");
Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
Assert.True(result.StdOut.Contains("Done"));

var searchResult = TestCommon.RunAICLICommand("search", "TestExampleInstaller");
Assert.AreEqual(Constants.ErrorCode.ERROR_NO_SOURCES_DEFINED, searchResult.ExitCode);
Assert.True(searchResult.StdOut.Contains("No sources defined; add one with 'source add' or reset to defaults with 'source reset'"));

var searchResult2 = TestCommon.RunAICLICommand("search", "TestExampleInstaller --source SourceTest");
Assert.AreEqual(Constants.ErrorCode.S_OK, searchResult2.ExitCode);
Assert.True(searchResult2.StdOut.Contains("TestExampleInstaller"));
Assert.True(searchResult2.StdOut.Contains("AppInstallerTest.TestExampleInstaller"));
TestCommon.RunAICLICommand("source remove", $"-n SourceTest");
}

/// <summary>
/// Test source add with duplicate name.
/// </summary>
Expand Down Expand Up @@ -94,6 +146,7 @@ public void SourceListWithName()
Assert.True(result.StdOut.Contains(Constants.TestSourceName));
Assert.True(result.StdOut.Contains(Constants.TestSourceUrl));
Assert.True(result.StdOut.Contains("Microsoft.PreIndexed.Package"));
Assert.True(result.StdOut.Contains("Trust Level"));
Assert.True(result.StdOut.Contains("Updated"));
}

Expand Down Expand Up @@ -184,4 +237,4 @@ public void SourceForceReset()
Assert.False(result.StdOut.Contains(Constants.TestSourceUrl));
}
}
}
}
12 changes: 12 additions & 0 deletions src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
Original file line number Diff line number Diff line change
Expand Up @@ -2861,4 +2861,16 @@ Please specify one of them using the --source option to proceed.</value>
<data name="SettingsSetCommandShortDescription" xml:space="preserve">
<value>Sets the value of an admin setting.</value>
</data>
<data name="SourceRequireExplicitArgumentDescription" xml:space="preserve">
<value>Excludes a source from discovery unless specified</value>
</data>
<data name="SourceListExplicit" xml:space="preserve">
<value>Explicit</value>
</data>
<data name="SourceTrustLevelArgumentDescription" xml:space="preserve">
<value>Trust level of the source (none or trusted)</value>
</data>
<data name="SourceListTrustLevel" xml:space="preserve">
<value>Trust Level</value>
</data>
</root>
Loading
Loading