Skip to content

Commit

Permalink
Add support for setting source trust level (#4216)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryfu-msft authored Mar 26, 2024
1 parent a3bb538 commit e9d3465
Show file tree
Hide file tree
Showing 26 changed files with 440 additions and 32 deletions.
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 @@ -115,6 +115,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 @@ -344,6 +348,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 @@ -62,6 +62,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
2 changes: 2 additions & 0 deletions src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs
Original file line number Diff line number Diff line change
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 @@ -2871,4 +2871,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

0 comments on commit e9d3465

Please sign in to comment.