commit 1edc52bff52b2d3880d184b9578de3b1b14d7af5 parent 1b87a14a657a5041b4291ff4f436707ab0be03ff Author: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Mon, 13 Sep 2021 14:55:32 -0700 Disable local manifest by default (#1453) Diffstat:
18 files changed, 475 insertions(+), 62 deletions(-)
diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -268,6 +268,7 @@ <ClInclude Include="Workflows\ImportExportFlow.h" /> <ClInclude Include="Workflows\MsiInstallFlow.h" /> <ClInclude Include="Workflows\MSStoreInstallerHandler.h" /> + <ClInclude Include="Workflows\SettingsFlow.h" /> <ClInclude Include="Workflows\ShellExecuteInstallerHandler.h" /> <ClInclude Include="Workflows\InstallFlow.h" /> <ClInclude Include="Workflows\ManifestComparator.h" /> @@ -316,6 +317,7 @@ <ClCompile Include="Workflows\ImportExportFlow.cpp" /> <ClCompile Include="Workflows\MsiInstallFlow.cpp" /> <ClCompile Include="Workflows\MSStoreInstallerHandler.cpp" /> + <ClCompile Include="Workflows\SettingsFlow.cpp" /> <ClCompile Include="Workflows\ShellExecuteInstallerHandler.cpp" /> <ClCompile Include="Workflows\InstallFlow.cpp" /> <ClCompile Include="Workflows\ManifestComparator.cpp" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -167,6 +167,9 @@ <ClInclude Include="Workflows\MsiInstallFlow.h"> <Filter>Workflows</Filter> </ClInclude> + <ClInclude Include="Workflows\SettingsFlow.h"> + <Filter>Workflows</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -301,6 +304,9 @@ <ClCompile Include="Workflows\MsiInstallFlow.cpp"> <Filter>Workflows</Filter> </ClCompile> + <ClCompile Include="Workflows\SettingsFlow.cpp"> + <Filter>Workflows</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -17,7 +17,7 @@ namespace AppInstaller::CLI case Args::Type::Query: return Argument{ "query", 'q', Args::Type::Query, Resource::String::QueryArgumentDescription, ArgumentType::Positional}; case Args::Type::Manifest: - return Argument{ "manifest", 'm', Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, Settings::TogglePolicy::Policy::LocalManifestFiles }; + return Argument{ "manifest", 'm', Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, Settings::TogglePolicy::Policy::LocalManifestFiles, Settings::AdminSetting::LocalManifestFiles }; case Args::Type::Id: return Argument{ "id", NoAlias, Args::Type::Id,Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Name: diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h @@ -6,6 +6,7 @@ #include <winget/UserSettings.h> #include <winget/ExperimentalFeature.h> #include <winget/GroupPolicy.h> +#include <winget/AdminSettings.h> #include <string> #include <string_view> @@ -91,6 +92,7 @@ namespace AppInstaller::CLI Argument::Visibility GetVisibility() const; Settings::ExperimentalFeature::Feature Feature() const { return m_feature; } Settings::TogglePolicy::Policy GroupPolicy() const { return m_groupPolicy; } + Settings::AdminSetting AdminSetting() const { return m_adminSetting; } Argument& SetRequired(bool required) { m_required = required; return *this; } @@ -118,8 +120,8 @@ namespace AppInstaller::CLI Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::TogglePolicy::Policy groupPolicy) : m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_groupPolicy(groupPolicy) {} - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy) : - m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_groupPolicy(groupPolicy) {} + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy, Settings::AdminSetting adminSetting) : + m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} std::string_view m_name; char m_alias; @@ -131,5 +133,6 @@ namespace AppInstaller::CLI size_t m_countLimit = 1; Settings::ExperimentalFeature::Feature m_feature = Settings::ExperimentalFeature::Feature::None; Settings::TogglePolicy::Policy m_groupPolicy = Settings::TogglePolicy::Policy::None; + Settings::AdminSetting m_adminSetting = Settings::AdminSetting::Unknown; }; } diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -632,13 +632,6 @@ namespace AppInstaller::CLI for (const auto& arg : GetArguments()) { - if (!ExperimentalFeature::IsEnabled(arg.Feature()) && execArgs.Contains(arg.ExecArgType())) - { - auto feature = ExperimentalFeature::GetFeature(arg.Feature()); - AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " without enabling feature " << feature.JsonName()); - throw CommandException(Resource::String::FeatureDisabledMessage, feature.JsonName()); - } - if (!Settings::GroupPolicies().IsEnabled(arg.GroupPolicy()) && execArgs.Contains(arg.ExecArgType())) { auto policy = TogglePolicy::GetPolicy(arg.GroupPolicy()); @@ -646,6 +639,20 @@ namespace AppInstaller::CLI throw GroupPolicyException(arg.GroupPolicy()); } + if (arg.AdminSetting() != AdminSetting::Unknown && !Settings::IsAdminSettingEnabled(arg.AdminSetting()) && execArgs.Contains(arg.ExecArgType())) + { + auto setting = Settings::AdminSettingToString(arg.AdminSetting()); + AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " disabled by admin setting " << setting); + throw CommandException(Resource::String::FeatureDisabledByAdminSettingMessage, Utility::LocIndView{ setting }, {}); + } + + if (!ExperimentalFeature::IsEnabled(arg.Feature()) && execArgs.Contains(arg.ExecArgType())) + { + auto feature = ExperimentalFeature::GetFeature(arg.Feature()); + AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " without enabling feature " << feature.JsonName()); + throw CommandException(Resource::String::FeatureDisabledMessage, feature.JsonName()); + } + if (arg.Required() && !execArgs.Contains(arg.ExecArgType())) { throw CommandException(Resource::String::RequiredArgError, arg.Name()); diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -3,16 +3,29 @@ #include "pch.h" #include "SettingsCommand.h" #include "Workflows/WorkflowBase.h" -#include "Resources.h" -#include <winget/UserSettings.h> +#include "Workflows/SettingsFlow.h" namespace AppInstaller::CLI { using namespace Utility::literals; using namespace AppInstaller::Settings; - using namespace std::string_view_literals; + namespace + { + constexpr Utility::LocIndView s_ArgumentName_Enable = "enable"_liv; + constexpr Utility::LocIndView s_ArgumentName_Disable = "disable"_liv; + constexpr Utility::LocIndView s_ArgName_EnableAndDisable = "enable|disable"_liv; + } + + std::vector<Argument> SettingsCommand::GetArguments() const + { + return { + Argument{ s_ArgumentName_Enable, Argument::NoAlias, Execution::Args::Type::AdminSettingEnable, Resource::String::AdminSettingEnableDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ s_ArgumentName_Disable, Argument::NoAlias, Execution::Args::Type::AdminSettingDisable, Resource::String::AdminSettingDisableDescription, ArgumentType::Standard, Argument::Visibility::Help }, + }; + } + Resource::LocString SettingsCommand::ShortDescription() const { return { Resource::String::SettingsCommandShortDescription }; @@ -28,59 +41,42 @@ namespace AppInstaller::CLI return "https://aka.ms/winget-settings"; } - void SettingsCommand::ExecuteInternal(Execution::Context& context) const + void SettingsCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const { - // Show warnings only when the setting command is executed. - if (!User().GetWarnings().empty()) + if (execArgs.Contains(Execution::Args::Type::AdminSettingEnable) && execArgs.Contains(Execution::Args::Type::AdminSettingDisable)) { - context.Reporter.Warn() << Resource::String::SettingLoadFailure << std::endl; - for (const auto& warning : User().GetWarnings()) - { - auto warn = context.Reporter.Warn(); - warn << warning.Message; - if (!warning.Path.empty()) - { - if (warning.IsFieldWarning) - { - warn << ' ' << Resource::String::SettingsWarningField << ' ' << warning.Path; - } - else - { - warn << ' ' << warning.Path; - } - } - - if (!warning.Data.empty()) - { - if (warning.IsFieldWarning) - { - warn << ' ' << Resource::String::SettingsWarningValue << ' ' << warning.Data; - } - else - { - warn << - std::endl << - warning.Data; - } - } + throw CommandException(Resource::String::TooManyAdminSettingArgumentsError, s_ArgName_EnableAndDisable); + } - warn << std::endl; - } + if (execArgs.Contains(Execution::Args::Type::AdminSettingEnable) && AdminSetting::Unknown == StringToAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingEnable))) + { + throw CommandException(Resource::String::InvalidArgumentValueError, s_ArgumentName_Enable, { "LocalManifestFiles"_lis }); } - User().PrepareToShellExecuteFile(); + if (execArgs.Contains(Execution::Args::Type::AdminSettingDisable) && AdminSetting::Unknown == StringToAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingDisable))) + { + throw CommandException(Resource::String::InvalidArgumentValueError, s_ArgumentName_Disable, { "LocalManifestFiles"_lis }); + } + } - auto filePathUTF16 = UserSettings::SettingsFilePath().wstring(); + void SettingsCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::AdminSettingEnable)) + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::EnableAdminSetting; - // Some versions of windows will fail if no file extension association exists, other will pop up the dialog - // to make the user pick their default. - // Kudos to the terminal team for this work around. - HINSTANCE res = ShellExecuteW(nullptr, nullptr, filePathUTF16.c_str(), nullptr, nullptr, SW_SHOW); - if (static_cast<int>(reinterpret_cast<uintptr_t>(res)) <= 32) + } + else if (context.Args.Contains(Execution::Args::Type::AdminSettingDisable)) + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::DisableAdminSetting; + } + else { - // User doesn't have file type association. Default to notepad - AICLI_LOG(CLI, Info, << "Json file type association not found, using notepad.exe"); - ShellExecuteW(nullptr, nullptr, L"notepad", filePathUTF16.c_str(), nullptr, SW_SHOW); + context << Workflow::OpenUserSetting; } } } diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.h b/src/AppInstallerCLICore/Commands/SettingsCommand.h @@ -9,12 +9,15 @@ namespace AppInstaller::CLI { SettingsCommand(std::string_view parent) : Command("settings", parent, Settings::TogglePolicy::Policy::Settings) {} + std::vector<Argument> GetArguments() const override; + virtual Resource::LocString ShortDescription() const override; virtual Resource::LocString LongDescription() const override; std::string HelpLink() const override; protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; void ExecuteInternal(Execution::Context& context) const override; }; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h @@ -68,6 +68,10 @@ namespace AppInstaller::CLI::Execution IgnoreUnavailable, IgnoreVersions, + // Setting Command + AdminSettingEnable, + AdminSettingDisable, + // Other All, // Used in Update command to update all installed packages to latest ListVersions, // Used in Show command to list all available versions of an app diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -23,6 +23,10 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(AcceptSourceAgreementsArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFlagError); WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFoundError); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisabled); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisableDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnabled); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnableDescription); WINGET_DEFINE_RESOURCE_STRINGID(AvailableArguments); WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommands); WINGET_DEFINE_RESOURCE_STRINGID(AvailableHeader); @@ -52,6 +56,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ExportSourceArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExternalDependencies); WINGET_DEFINE_RESOURCE_STRINGID(ExtraPositionalError); + WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledByAdminSettingMessage); WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledMessage); WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandShortDescription); @@ -275,6 +280,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ToolDescription); WINGET_DEFINE_RESOURCE_STRINGID(ToolInfoArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ToolVersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(TooManyAdminSettingArgumentsError); WINGET_DEFINE_RESOURCE_STRINGID(TooManyArgError); WINGET_DEFINE_RESOURCE_STRINGID(TooManyBehaviorsError); WINGET_DEFINE_RESOURCE_STRINGID(UnexpectedErrorExecutingCommand); diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp b/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SettingsFlow.h" +#include <winget/UserSettings.h> +#include <winget/AdminSettings.h> +#include "Resources.h" + +namespace AppInstaller::CLI::Workflow +{ + using namespace AppInstaller::Settings; + + void EnableAdminSetting(Execution::Context& context) + { + Settings::EnableAdminSetting(Settings::StringToAdminSetting(context.Args.GetArg(Execution::Args::Type::AdminSettingEnable))); + context.Reporter.Info() << Resource::String::AdminSettingEnabled; + } + + void DisableAdminSetting(Execution::Context& context) + { + Settings::DisableAdminSetting(Settings::StringToAdminSetting(context.Args.GetArg(Execution::Args::Type::AdminSettingDisable))); + context.Reporter.Info() << Resource::String::AdminSettingDisabled; + } + + void OpenUserSetting(Execution::Context& context) + { + // Show warnings only when the setting command is executed. + if (!User().GetWarnings().empty()) + { + context.Reporter.Warn() << Resource::String::SettingLoadFailure << std::endl; + for (const auto& warning : User().GetWarnings()) + { + auto warn = context.Reporter.Warn(); + warn << warning.Message; + if (!warning.Path.empty()) + { + if (warning.IsFieldWarning) + { + warn << ' ' << Resource::String::SettingsWarningField << ' ' << warning.Path; + } + else + { + warn << ' ' << warning.Path; + } + } + + if (!warning.Data.empty()) + { + if (warning.IsFieldWarning) + { + warn << ' ' << Resource::String::SettingsWarningValue << ' ' << warning.Data; + } + else + { + warn << + std::endl << + warning.Data; + } + } + + warn << std::endl; + } + } + + User().PrepareToShellExecuteFile(); + + auto filePathUTF16 = UserSettings::SettingsFilePath().wstring(); + + // Some versions of windows will fail if no file extension association exists, other will pop up the dialog + // to make the user pick their default. + // Kudos to the terminal team for this work around. + HINSTANCE res = ShellExecuteW(nullptr, nullptr, filePathUTF16.c_str(), nullptr, nullptr, SW_SHOW); + if (static_cast<int>(reinterpret_cast<uintptr_t>(res)) <= 32) + { + // User doesn't have file type association. Default to notepad + AICLI_LOG(CLI, Info, << "Json file type association not found, using notepad.exe"); + ShellExecuteW(nullptr, nullptr, L"notepad", filePathUTF16.c_str(), nullptr, SW_SHOW); + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.h b/src/AppInstallerCLICore/Workflows/SettingsFlow.h @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Enables an admin setting. + // Required Args: AdminSettingEnable + // Inputs: None + // Outputs: None + void EnableAdminSetting(Execution::Context& context); + + // Disables an admin setting. + // Required Args: AdminSettingDisable + // Inputs: None + // Outputs: None + void DisableAdminSetting(Execution::Context& context); + + // Opens the user settings. + // Required Args: None + // Inputs: None + // Outputs: None + void OpenUserSetting(Execution::Context& context); +} diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -466,10 +466,10 @@ They can be configured through the settings file 'winget settings'.</value> <value>The following failures were found validating the settings:</value> </data> <data name="SettingsCommandLongDescription" xml:space="preserve"> - <value>Open settings in the default json text editor. If no editor is configured, opens settings in notepad. For available settings see https://aka.ms/winget-settings</value> + <value>Open settings in the default json text editor. If no editor is configured, opens settings in notepad. For available settings see https://aka.ms/winget-settings This command can also be used to set administrator settings by providing 'enable' or 'disable' argument</value> </data> <data name="SettingsCommandShortDescription" xml:space="preserve"> - <value>Open settings</value> + <value>Open settings or set administrator settings</value> </data> <data name="SettingsWarnings" xml:space="preserve"> <value>Unexpected error while loading settings. Please verify your settings by running the settings command.</value> @@ -1078,4 +1078,23 @@ Do you agree to the terms?</value> <data name="ShowLabelReleaseNotesUrl" xml:space="preserve"> <value>Release Notes Url:</value> </data> + <data name="FeatureDisabledByAdminSettingMessage" xml:space="preserve"> + <value>This feature needs to be enabled by administrators. To enable it, run 'winget settings --enable %1' as administrator.</value> + <comment>{Locked="winget settings --enable %1"} The value will be replaced with the admin setting</comment> + </data> + <data name="AdminSettingEnableDescription" xml:space="preserve"> + <value>Enables the specific administrator setting</value> + </data> + <data name="AdminSettingDisableDescription" xml:space="preserve"> + <value>Disables the specific administrator setting</value> + </data> + <data name="TooManyAdminSettingArgumentsError" xml:space="preserve"> + <value>Too many admin setting arguments provided</value> + </data> + <data name="AdminSettingEnabled" xml:space="preserve"> + <value>Admin setting enabled.</value> + </data> + <data name="AdminSettingDisabled" xml:space="preserve"> + <value>Admin setting disabled.</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -25,6 +25,7 @@ #include <Commands/ImportCommand.h> #include <Commands/InstallCommand.h> #include <Commands/ShowCommand.h> +#include <Commands/SettingsCommand.h> #include <Commands/SearchCommand.h> #include <Commands/UninstallCommand.h> #include <Commands/UpgradeCommand.h> @@ -2169,3 +2170,44 @@ TEST_CASE("OpenSource_WithCustomHeader", "[OpenSource][CustomHeader]") auto source = context.Get<Execution::Data::Source>(); REQUIRE(source.get()->GetDetails().CustomHeader.value_or("").compare(customHeader2) == 0); } + +TEST_CASE("AdminSetting_LocalManifestFiles", "[LocalManifests][workflow]") +{ + RemoveSetting(Streams::AdminSettings); + + // If there's no admin setting, using local manifest should fail. + Execution::Args args; + args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); + InstallCommand installCommand({}); + REQUIRE_THROWS(installCommand.ValidateArguments(args)); + + // Using settings command to enable local manifests + std::ostringstream settingsOutput; + TestContext context{ settingsOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::AdminSettingEnable, "LocalManifestFiles"sv); + context.Override({ EnsureRunningAsAdmin, [](TestContext&){} }); + SettingsCommand settings({}); + settings.Execute(context); + INFO(settingsOutput.str()); + + // Now using local manifests should succeed + Execution::Args args2; + args2.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); + InstallCommand installCommand2({}); + REQUIRE_NOTHROW(installCommand2.ValidateArguments(args2)); + + // Using settings command to disable local manifests + std::ostringstream settingsOutput2; + TestContext context2{ settingsOutput2, std::cin }; + context2.Args.AddArg(Execution::Args::Type::AdminSettingDisable, "LocalManifestFiles"sv); + context2.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); + SettingsCommand settings2({}); + settings2.Execute(context2); + INFO(settingsOutput2.str()); + + // Now using local manifests should fail + Execution::Args args3; + args3.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); + InstallCommand installCommand3({}); + REQUIRE_THROWS(installCommand3.ValidateArguments(args3)); +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/AdminSettings.cpp b/src/AppInstallerCommonCore/AdminSettings.cpp @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerLogging.h" +#include "AppInstallerStrings.h" +#include "winget/Settings.h" +#include "winget/AdminSettings.h" +#include "winget/GroupPolicy.h" +#include "winget/Yaml.h" + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + using namespace Utility; + + namespace + { + constexpr std::string_view s_AdminSettingsYaml_LocalManifestFiles = "LocalManifestFiles"sv; + + // Attempts to read a single scalar value from the node. + template<typename Value> + bool TryReadScalar(const YAML::Node& rootNode, std::string_view name, Value& value) + { + YAML::Node valueNode = rootNode[std::string{ name }]; + + if (!valueNode || !valueNode.IsScalar()) + { + AICLI_LOG(Core, Verbose, << "Admin setting '" << name << "' was not found or did not contain the expected format"); + return false; + } + + value = valueNode.as<Value>(); + return true; + } + + struct AdminSettingValues + { + bool LocalManifestFiles = false; + }; + + struct AdminSettingsInternal + { + AdminSettingsInternal(); + + void SetAdminSetting(AdminSetting setting, bool enabled); + + bool GetAdminSettingBoolValue(AdminSetting setting) const; + + private: + void LoadAdminSettings(); + void SaveAdminSettings() const; + + AdminSettingValues m_settingValues; + }; + + AdminSettingsInternal::AdminSettingsInternal() + { + LoadAdminSettings(); + } + + void AdminSettingsInternal::SetAdminSetting(AdminSetting setting, bool enabled) + { + switch (setting) + { + case AdminSetting::LocalManifestFiles: + m_settingValues.LocalManifestFiles = enabled; + break; + default: + return; + } + + SaveAdminSettings(); + } + + bool AdminSettingsInternal::GetAdminSettingBoolValue(AdminSetting setting) const + { + switch (setting) + { + case AdminSetting::LocalManifestFiles: + return m_settingValues.LocalManifestFiles; + default: + return false; + } + } + + void AdminSettingsInternal::LoadAdminSettings() + { + auto stream = Settings::GetSettingStream(Settings::Streams::AdminSettings); + if (!stream) + { + AICLI_LOG(Core, Verbose, << "Admin settings was not found"); + return; + } + + std::string adminSettingsYaml = Utility::ReadEntireStream(*stream); + + YAML::Node document; + try + { + document = YAML::Load(adminSettingsYaml); + } + catch (const std::exception& e) + { + AICLI_LOG(YAML, Error, << "Admin settings contained invalid YAML (" << e.what() << ")"); + return; + } + + if (document.IsNull()) + { + AICLI_LOG(Core, Info, << "Admin settings is empty"); + return; + } + + if (!document.IsMap()) + { + AICLI_LOG(Core, Error, << "Admin settings did not contain the expected format"); + return; + } + + TryReadScalar<bool>(document, s_AdminSettingsYaml_LocalManifestFiles, m_settingValues.LocalManifestFiles); + } + + void AdminSettingsInternal::SaveAdminSettings() const + { + YAML::Emitter out; + out << YAML::BeginMap; + out << YAML::Key << s_AdminSettingsYaml_LocalManifestFiles << YAML::Value << m_settingValues.LocalManifestFiles; + out << YAML::EndMap; + + Settings::SetSetting(Settings::Streams::AdminSettings, out.str()); + } + } + + AdminSetting StringToAdminSetting(std::string_view in) + { + AdminSetting result = AdminSetting::Unknown; + + if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_LocalManifestFiles, in)) + { + result = AdminSetting::LocalManifestFiles; + } + + return result; + } + + std::string_view AdminSettingToString(AdminSetting setting) + { + switch (setting) + { + case AdminSetting::LocalManifestFiles: + return s_AdminSettingsYaml_LocalManifestFiles; + default: + return "Unknown"sv; + } + } + + void EnableAdminSetting(AdminSetting setting) + { + AdminSettingsInternal adminSettingsInternal; + adminSettingsInternal.SetAdminSetting(setting, true); + } + + void DisableAdminSetting(AdminSetting setting) + { + AdminSettingsInternal adminSettingsInternal; + adminSettingsInternal.SetAdminSetting(setting, false); + } + + bool IsAdminSettingEnabled(AdminSetting setting) + { + // For some admin settings, even if it's disabled, if the corresponding policy is enabled then override it. + if (setting == AdminSetting::LocalManifestFiles && + GroupPolicies().GetState(TogglePolicy::Policy::LocalManifestFiles) == PolicyState::Enabled) + { + return true; + } + + AdminSettingsInternal adminSettingsInternal; + return adminSettingsInternal.GetAdminSettingBoolValue(setting); + } +} diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -261,6 +261,7 @@ </ItemDefinitionGroup> <ItemGroup> <ClInclude Include="DODownloader.h" /> + <ClInclude Include="Public\winget\AdminSettings.h" /> <ClInclude Include="Public\winget\GroupPolicy.h" /> <ClInclude Include="HttpStream\HttpClientWrapper.h" /> <ClInclude Include="HttpStream\HttpLocalCache.h" /> @@ -312,6 +313,7 @@ <ClInclude Include="YamlWrapper.h" /> </ItemGroup> <ItemGroup> + <ClCompile Include="AdminSettings.cpp" /> <ClCompile Include="DODownloader.cpp" /> <ClCompile Include="GroupPolicy.cpp"> <ExcludedFromBuild Condition="'$(Configuration)'=='Fuzzing'">true</ExcludedFromBuild> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -174,10 +174,13 @@ <ClInclude Include="Public\winget\TraceLogger.h"> <Filter>Public\winget</Filter> </ClInclude> - <ClInclude Include="Public\winget\Msi.h"> + <ClInclude Include="Public\winget\ThreadGlobals.h"> <Filter>Public\winget</Filter> </ClInclude> - <ClInclude Include="Public\winget\ThreadGlobals.h"> + <ClInclude Include="Public\winget\AdminSettings.h"> + <Filter>Public\winget</Filter> + </ClInclude> + <ClInclude Include="Public\winget\MsiExecArguments.h"> <Filter>Public\winget</Filter> </ClInclude> </ItemGroup> @@ -305,6 +308,12 @@ <ClCompile Include="ThreadGlobals.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="MsiExecArguments.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="AdminSettings.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/Public/winget/AdminSettings.h b/src/AppInstallerCommonCore/Public/winget/AdminSettings.h @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include <string> + +namespace AppInstaller::Settings +{ + // Enum of admin settings. + enum class AdminSetting + { + Unknown, + LocalManifestFiles, + }; + + AdminSetting StringToAdminSetting(std::string_view in); + + std::string_view AdminSettingToString(AdminSetting setting); + + void EnableAdminSetting(AdminSetting setting); + + void DisableAdminSetting(AdminSetting setting); + + bool IsAdminSettingEnabled(AdminSetting setting); +} diff --git a/src/AppInstallerCommonCore/Public/winget/Settings.h b/src/AppInstallerCommonCore/Public/winget/Settings.h @@ -47,6 +47,8 @@ namespace AppInstaller::Settings constexpr static StreamDefinition PrimaryUserSettings{ Type::UserFile, "settings.json"sv }; // The backup user settings file. constexpr static StreamDefinition BackupUserSettings{ Type::UserFile, "settings.json.backup"sv }; + // The admin settings. + constexpr static StreamDefinition AdminSettings{ Type::Secure, "admin_settings"sv }; }; // Gets a stream containing the named setting's value, if present.