diff --git a/doc/admx/DesktopAppInstaller.admx b/doc/admx/DesktopAppInstaller.admx index c692c37061..79ebb2598a 100644 --- a/doc/admx/DesktopAppInstaller.admx +++ b/doc/admx/DesktopAppInstaller.admx @@ -183,6 +183,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -194,60 +276,60 @@ - + - + - + - + - + - + - + - + - + - + diff --git a/doc/admx/en-US/DesktopAppInstaller.adml b/doc/admx/en-US/DesktopAppInstaller.adml index 746b678784..29f755a71a 100644 --- a/doc/admx/en-US/DesktopAppInstaller.adml +++ b/doc/admx/en-US/DesktopAppInstaller.adml @@ -122,20 +122,24 @@ If you disable this setting, users will not be able to use the Windows Package M If you disable or do not configure this setting, no proxy will be used by default. If you enable this setting, the specified proxy will be used by default. + Enable App Installer Allowed Zones for the Windows Package Manager + + Enable Windows Package Manager SmartScreen Checks Bypass + Enable App Installer Allowed Zones for MSIX Packages This policy controls whether App Installer allows installing packages originating from specific URL Zones. A package's origin is determined by its URI and whether a Mart-of-the-Web (MotW) is present. If multiple URIs are involved, all of them are considered; for example, when using a .appinstaller file that involves redirection. If you enable this policy, users will be able to install MSIX packages according to the configuration for each zone. If you disable or do not configure this policy, users will be able to install MSIX packages from any zone except for Untrusted. - Allow - Block Enable Microsoft SmartScreen checks for MSIX Packages This policy controls whether App Installer performs Microsoft SmartScreen checks when installing MSIX packages. If you enable or do not configure this policy, the package URI will be evaluated with Microsoft SmartScreen before installation. This check is only done for packages that come from the internet. If you disable, Microsoft SmartScreen will not be consulted before installing a package. + Allow + Block @@ -152,6 +156,13 @@ If you disable, Microsoft SmartScreen will not be consulted before installing a + + Local Machine + Intranet + Trusted Sites + Internet + Untrusted Sites + Local Machine Intranet diff --git a/schemas/JSON/settings/settings.export.schema.0.1.json b/schemas/JSON/settings/settings.export.schema.0.1.json index f8809255f5..2ac562b6d4 100644 --- a/schemas/JSON/settings/settings.export.schema.0.1.json +++ b/schemas/JSON/settings/settings.export.schema.0.1.json @@ -32,6 +32,11 @@ "type": "boolean", "default": false }, + "BypassSmartScreenCheck": { + "description": "Enable smart screen check bypass.", + "type": "boolean", + "default": false + }, "DefaultProxy": { "description": "Default proxy.", "type": "string" diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index 3c78e96464..b0881bb932 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -207,6 +207,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VcpkgCustomTriplets", "Vcpk VcpkgCustomTriplets\x86.cmake = VcpkgCustomTriplets\x86.cmake EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internal", "Internal", "{A66FF147-BBB4-4A84-95A0-18413D973941}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UriValidation", "Internal\UriValidation\UriValidation.vcxproj", "{98920AB6-27B0-4C0F-B336-FA49DE57A1BA}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment.OutOfProc", "Microsoft.Management.Deployment.OutOfProc\Microsoft.Management.Deployment.OutOfProc.vcxproj", "{0BA531C8-CF0C-405B-8221-0FE51BA529D1}" ProjectSection(ProjectDependencies) = postProject {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} @@ -1218,6 +1222,36 @@ Global {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.TestRelease|x64.Build.0 = Release|x64 {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.TestRelease|x86.ActiveCfg = Release|Win32 {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4}.TestRelease|x86.Build.0 = Release|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Debug|ARM64.ActiveCfg = Debug|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Debug|ARM64.Build.0 = Debug|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Debug|x64.ActiveCfg = Debug|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Debug|x64.Build.0 = Debug|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Debug|x86.ActiveCfg = Debug|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Debug|x86.Build.0 = Debug|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Fuzzing|ARM64.ActiveCfg = Debug|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Fuzzing|ARM64.Build.0 = Debug|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Fuzzing|x64.ActiveCfg = Debug|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Fuzzing|x64.Build.0 = Debug|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Fuzzing|x86.ActiveCfg = Debug|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Fuzzing|x86.Build.0 = Debug|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Release|ARM64.ActiveCfg = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Release|ARM64.Build.0 = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Release|x64.ActiveCfg = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Release|x64.Build.0 = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Release|x86.ActiveCfg = Release|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.Release|x86.Build.0 = Release|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.ReleaseStatic|ARM64.ActiveCfg = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.ReleaseStatic|ARM64.Build.0 = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.ReleaseStatic|x64.Build.0 = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.ReleaseStatic|x86.ActiveCfg = Release|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.ReleaseStatic|x86.Build.0 = Release|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.TestRelease|ARM64.ActiveCfg = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.TestRelease|ARM64.Build.0 = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.TestRelease|x64.ActiveCfg = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.TestRelease|x64.Build.0 = Release|x64 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.TestRelease|x86.ActiveCfg = Release|Win32 + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA}.TestRelease|x86.Build.0 = Release|Win32 {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|ARM64.ActiveCfg = Debug|ARM64 {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|ARM64.Build.0 = Debug|ARM64 {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x64.ActiveCfg = Debug|x64 @@ -1301,6 +1335,7 @@ Global {5A52D9FC-0059-4A4A-8196-427A7AA0D1C5} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} {1B9077B3-8923-4ECD-8FC9-B3190FCBE4D4} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {76B26B2C-602A-4AD0-9736-4162D3FCA92A} = {1A5D7A7D-5CB2-47D5-B40D-4E61CAEDC798} + {98920AB6-27B0-4C0F-B336-FA49DE57A1BA} = {A66FF147-BBB4-4A84-95A0-18413D973941} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index f78059aafb..81e8a823da 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -223,9 +223,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) true true true @@ -265,10 +265,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) true true true @@ -302,10 +302,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\Internal;%(AdditionalIncludeDirectories) true true true @@ -429,6 +429,7 @@ + @@ -511,6 +512,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index a8f48edd34..2e72317921 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -257,6 +257,9 @@ Commands + + Workflows + Header Files @@ -493,6 +496,9 @@ Commands + + Workflows + Source Files diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 62455f0981..398b472e97 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -95,6 +95,8 @@ namespace AppInstaller::CLI return { type, "skip-dependencies"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; case Execution::Args::Type::AllowReboot: return { type, "allow-reboot"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::IgnoreSmartScreen: + return { type, "ignore-smartscreen"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; // Uninstall behavior case Execution::Args::Type::Purge: @@ -442,6 +444,8 @@ namespace AppInstaller::CLI return Argument{ type, Resource::String::NoProxyArgumentDescription, ArgumentType::Flag, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; case Args::Type::Family: return Argument{ type, Resource::String::FontFamilyNameArgumentDescription, ArgumentType::Positional, false }; + case Args::Type::IgnoreSmartScreen: + return Argument{ type, Resource::String::IgnoreSmartScreenArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::BypassSmartScreenCheck, Settings::BoolAdminSetting::BypassSmartScreenCheck }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCLICore/Commands/DownloadCommand.cpp b/src/AppInstallerCLICore/Commands/DownloadCommand.cpp index 0848ecda55..e05f17353f 100644 --- a/src/AppInstallerCLICore/Commands/DownloadCommand.cpp +++ b/src/AppInstallerCLICore/Commands/DownloadCommand.cpp @@ -42,6 +42,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::AcceptSourceAgreements), Argument::ForType(Args::Type::SkipMicrosoftStorePackageLicense), Argument::ForType(Args::Type::Platform), + Argument::ForType(Args::Type::IgnoreSmartScreen), }; } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp index c93bf59a80..a8d187784e 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -56,6 +56,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Rename), Argument::ForType(Args::Type::UninstallPrevious), Argument::ForType(Args::Type::Force), + Argument::ForType(Args::Type::IgnoreSmartScreen), Argument{ Args::Type::IncludeUnknown, Resource::String::IncludeUnknownArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden}, }; } diff --git a/src/AppInstallerCLICore/Commands/RepairCommand.cpp b/src/AppInstallerCLICore/Commands/RepairCommand.cpp index 3df1a6a0e6..89385f1363 100644 --- a/src/AppInstallerCLICore/Commands/RepairCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RepairCommand.cpp @@ -39,6 +39,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Force), Argument::ForType(Args::Type::HashOverride), Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::IgnoreSmartScreen), }; } diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.cpp b/src/AppInstallerCLICore/Commands/SourceCommand.cpp index 4b0b557ffb..3c3201c350 100644 --- a/src/AppInstallerCLICore/Commands/SourceCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SourceCommand.cpp @@ -56,6 +56,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::CustomHeader), Argument::ForType(Args::Type::AcceptSourceAgreements), Argument::ForType(Args::Type::SourceExplicit), + Argument::ForType(Args::Type::IgnoreSmartScreen), }; } diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index e3dedc94e4..af9376a60e 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -74,6 +74,7 @@ namespace AppInstaller::CLI Argument{ Args::Type::IncludePinned, Resource::String::IncludePinnedArgumentDescription, ArgumentType::Flag}, Argument::ForType(Args::Type::UninstallPrevious), Argument::ForType(Args::Type::Force), + Argument::ForType(Args::Type::IgnoreSmartScreen), }; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index 9c0329aa21..acec3f9814 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -50,6 +50,7 @@ namespace AppInstaller::CLI::Execution Rename, // Renames the file of the executable. Only applies to the portable installerType NoUpgrade, // Install flow should not try to convert to upgrade flow upon finding existing installed version AllowReboot, // Allows the reboot flow to proceed if applicable + IgnoreSmartScreen, // Ignore smart screen check // Uninstall behavior Purge, // Removes all files and directories related to a package during an uninstall. Only applies to the portable installerType. diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 2b72479a41..583f36046d 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -277,6 +277,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(IdArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(IgnoreLocalArchiveMalwareScanArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(IgnoreResumeLimitArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IgnoreSmartScreenArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(IgnoreWarningsArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandReportDependencies); @@ -702,6 +703,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); + WINGET_DEFINE_RESOURCE_STRINGID(UriBlockedBySmartScreen); + WINGET_DEFINE_RESOURCE_STRINGID(UriSecurityZoneBlockedByPolicy); WINGET_DEFINE_RESOURCE_STRINGID(UriNotWellFormed); WINGET_DEFINE_RESOURCE_STRINGID(UriSchemeNotSupported); WINGET_DEFINE_RESOURCE_STRINGID(Usage); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp index 5d8d641a98..1190f52ca5 100644 --- a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "ConfigurationFlow.h" +#include "UriValidationFLow.h" #include "ImportExportFlow.h" #include "PromptFlow.h" #include "TableOutput.h" @@ -1328,14 +1329,21 @@ namespace AppInstaller::CLI::Workflow return "None"; } } + + void CreateConfigurationProcessorInternal(Context& context) + { + auto progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); + + anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ anon::CreateConfigurationSetProcessorFactory(context) }); + } } void CreateConfigurationProcessor(Context& context) { - auto progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); - - anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ anon::CreateConfigurationSetProcessorFactory(context) }); + context << + ExecuteUriValidation(UriValidationSource::Configuration) << + anon::CreateConfigurationProcessorInternal; } void CreateConfigurationProcessorWithoutFactory(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp index 119e2d1b1d..944623af57 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "DownloadFlow.h" +#include "UriValidationFlow.h" #include "MSStoreInstallerHandler.h" #include #include @@ -259,20 +260,8 @@ namespace AppInstaller::CLI::Workflow } } - void DownloadInstaller(Execution::Context& context) + void DownloadInstallerInternal(Execution::Context& context) { - // Check if file was already downloaded. - // This may happen after a failed installation or if the download was done - // separately before, e.g. on COM scenarios. - context << - ReportExecutionStage(ExecutionStage::Download) << - CheckForExistingInstaller; - - if (context.IsTerminated()) - { - return; - } - bool installerDownloadOnly = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly); // CheckForExistingInstaller will set the InstallerPath if found @@ -334,6 +323,18 @@ namespace AppInstaller::CLI::Workflow } } + void DownloadInstaller(Execution::Context& context) + { + // Check if file was already downloaded. + // This may happen after a failed installation or if the download was done + // separately before, e.g. on COM scenarios. + context << + ReportExecutionStage(ExecutionStage::Download) << + CheckForExistingInstaller << + ExecuteUriValidation(UriValidationSource::Package) << + DownloadInstallerInternal; + } + void CheckForExistingInstaller(Execution::Context& context) { const auto& installer = context.Get().value(); diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp index 7d8df61ffd..acb634a2d3 100644 --- a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp @@ -6,6 +6,7 @@ #include "PromptFlow.h" #include "TableOutput.h" #include "WorkflowBase.h" +#include "UriValidationFlow.h" namespace AppInstaller::CLI::Workflow { @@ -13,6 +14,29 @@ namespace AppInstaller::CLI::Workflow using namespace AppInstaller::Settings; using namespace AppInstaller::Utility::literals; + namespace + { + void AddSourceInternal(Execution::Context& context) + { + auto& sourceToAdd = context.Get(); + auto details = sourceToAdd.GetDetails(); + + context.Reporter.Info() << + Resource::String::SourceAddBegin << std::endl << + " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; + + auto addFunction = [&](IProgressCallback& progress)->bool { return sourceToAdd.Add(progress); }; + if (!context.Reporter.ExecuteWithProgress(addFunction)) + { + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::Done << std::endl; + } + } + } + void GetSourceList(Execution::Context& context) { context.Add(Repository::Source::GetCurrentSources()); @@ -94,22 +118,9 @@ namespace AppInstaller::CLI::Workflow void AddSource(Execution::Context& context) { - auto& sourceToAdd = context.Get(); - auto details = sourceToAdd.GetDetails(); - - context.Reporter.Info() << - Resource::String::SourceAddBegin << std::endl << - " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; - - auto addFunction = [&](IProgressCallback& progress)->bool { return sourceToAdd.Add(progress); }; - if (!context.Reporter.ExecuteWithProgress(addFunction)) - { - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::Done << std::endl; - } + context << + ExecuteUriValidation(UriValidationSource::SourceAdd) << + AddSourceInternal; } void CreateSourceForSourceAdd(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/UriValidationFlow.cpp b/src/AppInstallerCLICore/Workflows/UriValidationFlow.cpp new file mode 100644 index 0000000000..814daf33c6 --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/UriValidationFlow.cpp @@ -0,0 +1,315 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "UriValidationFlow.h" +#include +#include +#include +#include + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + // Convert the security zone to string. + std::string ToString(Settings::SecurityZoneOptions zone) + { + switch (zone) + { + case Settings::SecurityZoneOptions::LocalMachine: + return "LocalMachine"; + case Settings::SecurityZoneOptions::Intranet: + return "Intranet"; + case Settings::SecurityZoneOptions::TrustedSites: + return "TrustedSites"; + case Settings::SecurityZoneOptions::Internet: + return "Internet"; + case Settings::SecurityZoneOptions::UntrustedSites: + return "UntrustedSites"; + default: + return "Unknown"; + } + } + + // Check if smart screen is required for a given zone. + bool IsSmartScreenRequired(Execution::Context& context, Settings::SecurityZoneOptions zone, bool isTrusted) + { + // Smart screen is required only for Internet and UntrustedSites zones. + if (zone != Settings::SecurityZoneOptions::Internet && zone != Settings::SecurityZoneOptions::UntrustedSites) + { + AICLI_LOG(Core, Info, << "Skipping smart screen validation for zone " << ToString(zone)); + return false; + } + + auto policyState = Settings::GroupPolicies().GetState(Settings::TogglePolicy::Policy::BypassSmartScreenCheck); + + // If group policy is disabled, smart screen validation cannot be bypassed even for trusted URIs. + if (policyState == Settings::PolicyState::Disabled) + { + AICLI_LOG(Core, Info, << "Smart screen validation is required by group policy"); + return true; + } + + // Skip smart screen validation if the --ignore-smartscreen argument is provided. + if (context.Args.Contains(Execution::Args::Type::IgnoreSmartScreen)) + { + AICLI_LOG(Core, Info, << "Skipping smart screen validation as the user has opted out"); + return false; + } + + // Skip smart screen validation for trusted URIs. + if (isTrusted) + { + AICLI_LOG(Core, Info, << "Skipping smart screen validation for trusted URI"); + return false; + } + + // Smart screen validation is required for untrusted URIs. + AICLI_LOG(Core, Info, << "Smart screen validation is required for untrusted URI"); + return true; + } + + // Check if the given uri is blocked by smart screen. + bool IsUriBlockedBySmartScreen(Execution::Context& context, const std::string& uri) + { + auto response = AppInstaller::UriValidation::ValidateUri(uri); + switch (response.Decision()) + { + case AppInstaller::UriValidation::UriValidationDecision::Block: + AICLI_LOG(Core, Verbose, << "URI '" << uri << "' was blocked by smart screen. Feedback URL: " << response.Feedback()); + context.Reporter.Error() << Resource::String::UriBlockedBySmartScreen << std::endl; + return true; + case AppInstaller::UriValidation::UriValidationDecision::Allow: + default: + return false; + } + } + + // Get Uri zone for a given uri or file path. + HRESULT GetUriZone(const std::string& uri, Settings::SecurityZoneOptions& zone) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + // For testing purposes, allow the zone to be set via the uri + std::smatch match; + if (std::regex_search(uri, match, std::regex("/zone(\\d+)/"))) + { + std::string number = match[1].str(); + zone = static_cast(std::stoul(number)); + return S_OK; + } +#endif + + DWORD dwZone; + auto pInternetSecurityManager = winrt::create_instance(CLSID_InternetSecurityManager, CLSCTX_ALL); + auto mapResult = pInternetSecurityManager->MapUrlToZone(AppInstaller::Utility::ConvertToUTF16(uri).c_str(), &dwZone, 0); + + // Ensure MapUrlToZone was successful and the zone value is valid + if (FAILED(mapResult)) + { + return mapResult; + } + + // Treat all zones higher than untrusted as untrusted + if (dwZone > static_cast(Settings::SecurityZoneOptions::UntrustedSites)) + { + zone = Settings::SecurityZoneOptions::UntrustedSites; + } + else + { + zone = static_cast(dwZone); + } + + return S_OK; + } + + // Get whether or not the source is trusted. + HRESULT GetIsSourceTrusted(const Execution::Context& context, bool& isTrusted) + { + if (context.Contains(Execution::Data::PackageVersion)) + { + const auto packageVersion = context.Get(); + const auto source = packageVersion->GetSource(); + isTrusted = WI_IsFlagSet(source.GetDetails().TrustLevel, Repository::SourceTrustLevel::Trusted); + return S_OK; + } + + return E_FAIL; + } + + // Get the installer url from the context. + HRESULT GetInstallerUrl(const Execution::Context& context, std::string& installerUrl) + { + if (context.Contains(Execution::Data::Installer)) + { + installerUrl = context.Get()->Url; + return S_OK; + } + + return E_FAIL; + } + + // Get the configuration uri from the context. + HRESULT GetConfigurationUri(const Execution::Context& context, std::string& configurationUri) + { + if (context.Args.Contains(Execution::Args::Type::ConfigurationFile)) + { + configurationUri = context.Args.GetArg(Execution::Args::Type::ConfigurationFile); + return S_OK; + } + + return E_FAIL; + } + + // Get the source add url from the context. + HRESULT GetSourceAddUrl(const Execution::Context& context, std::string& sourceAddUrl) + { + if (context.Contains(Execution::Data::Source)) + { + auto& sourceToAdd = context.Get(); + auto details = sourceToAdd.GetDetails(); + sourceAddUrl = details.Arg; + return S_OK; + } + + return E_FAIL; + } + + // Validate group policy for a given zone. + bool IsZoneBlockedByGroupPolicy(Execution::Context& context, const Settings::SecurityZoneOptions& zone) + { + // If the group policy for allowed security zones is not enabled then skip validation. + if (!Settings::GroupPolicies().IsEnabled(Settings::TogglePolicy::Policy::AllowedSecurityZones)) + { + AICLI_LOG(Core, Info, << "Group policy for allowed security zones is disabled."); + return false; + } + + // If the group policy is enabled but no zones are configured then skip validation. + auto allowedSecurityZones = Settings::GroupPolicies().GetValue(); + if (!allowedSecurityZones.has_value()) + { + AICLI_LOG(Core, Warning, << "Group policy for allowed security zones is enabled but no zones are configured."); + return false; + } + + // If the zone is not found in the allowed security zones then skip validation. + auto zoneIterator = allowedSecurityZones->find(zone); + if (zoneIterator == allowedSecurityZones->end()) + { + AICLI_LOG(Core, Warning, << "Security zone " << ToString(zone) << " was not found in the group policy for allowed security zones."); + return false; + } + + // If the zone is found in the allowed security zones but is not allowed then block the configuration. + auto isAllowed = zoneIterator->second; + if (!isAllowed) + { + AICLI_LOG(Core, Error, << "Security zone " << ToString(zone) << " is blocked by group policy"); + context.Reporter.Error() << Resource::String::UriSecurityZoneBlockedByPolicy << std::endl; + return true; + } + + AICLI_LOG(Core, Info, << "Security zone " << ToString(zone) << " is allowed by group policy"); + return false; + } + + // Core logic to evaluate the uri. + HRESULT EvaluateUri(Execution::Context& context, std::string uri, bool isUriTrusted) + { + Settings::SecurityZoneOptions uriZone; + if (FAILED(GetUriZone(uri, uriZone))) + { + AICLI_LOG(Core, Warning, << "Failed to get security zone for URI: " << uri << ". Skipping validation."); + return S_OK; + } + + if (IsZoneBlockedByGroupPolicy(context, uriZone)) + { + AICLI_LOG(Core, Error, << "URI security zone is blocked by group policy: " << uri << " (" << ToString(uriZone) << ")"); + return APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY; + } + + if (IsSmartScreenRequired(context, uriZone, isUriTrusted) && IsUriBlockedBySmartScreen(context, uri)) + { + AICLI_LOG(Core, Error, << "URI was blocked by smart screen: " << uri); + return APPINSTALLER_CLI_ERROR_BLOCKED_BY_REPUTATION_SERVICE; + } + + AICLI_LOG(Core, Info, << "URI was validated successfully: " << uri); + return S_OK; + + } + + // Evaluate the configuration uri + HRESULT EvaluateConfigurationUri(Execution::Context& context) + { + std::string configurationUri; + if (FAILED(GetConfigurationUri(context, configurationUri))) + { + AICLI_LOG(Core, Warning, << "Configuration URI is not available. Skipping validation."); + return S_OK; + } + + return EvaluateUri(context, configurationUri, false); + } + + // Evaluate the download uri + HRESULT EvaluateDownloadUri(Execution::Context& context) + { + std::string installerUrl; + if (FAILED(GetInstallerUrl(context, installerUrl))) + { + AICLI_LOG(Core, Warning, << "Installer URL is not available. Skipping validation."); + return S_OK; + } + + bool isSourceTrusted; + if (FAILED(GetIsSourceTrusted(context, isSourceTrusted))) + { + AICLI_LOG(Core, Warning, << "Source trust level is not available. Skipping smart screen validation."); + return S_OK; + } + + return EvaluateUri(context, installerUrl, isSourceTrusted); + } + + // Evaluate the source add uri + HRESULT EvaluateSourceAddUri(Execution::Context& context) + { + std::string sourceAddUrl; + if (FAILED(GetSourceAddUrl(context, sourceAddUrl))) + { + AICLI_LOG(Core, Warning, << "Source URL is not available. Skipping validation."); + return S_OK; + } + + return EvaluateUri(context, sourceAddUrl, false); + } + + // Evaluate the uri based on the source. + HRESULT EvaluateUriBySource(Execution::Context& context, UriValidationSource uriValidationSource) + { + switch (uriValidationSource) + { + case UriValidationSource::Configuration: + return EvaluateConfigurationUri(context); + case UriValidationSource::Package: + return EvaluateDownloadUri(context); + case UriValidationSource::SourceAdd: + return EvaluateSourceAddUri(context); + default: + THROW_HR(E_UNEXPECTED); + } + } + } + + // Execute the smart screen flow. + void ExecuteUriValidation::operator()(Execution::Context& context) const + { + auto uriValidation = EvaluateUriBySource(context, m_uriValidationSource); + if (FAILED(uriValidation)) + { + AICLI_TERMINATE_CONTEXT(uriValidation); + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/UriValidationFlow.h b/src/AppInstallerCLICore/Workflows/UriValidationFlow.h new file mode 100644 index 0000000000..b239dbca6c --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/UriValidationFlow.h @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + enum UriValidationSource + { + Configuration, + Package, + SourceAdd, + }; + + // Composite flow that chooses what to do based on whether or not the + // configuration flow is being run. + // Required Args: None + // Inputs: IsConfigurationFlow + // Outputs: None + struct ExecuteUriValidation: public WorkflowTask + { + ExecuteUriValidation(UriValidationSource uriValidationSource) : WorkflowTask("ExecuteUriValidation"), m_uriValidationSource(uriValidationSource) {} + + void operator()(Execution::Context& context) const override; + + private: + UriValidationSource m_uriValidationSource; + }; +} + diff --git a/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj b/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj index a9e40fc31e..bbff6d0721 100644 --- a/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj +++ b/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj @@ -235,6 +235,9 @@ WindowsPackageManager.dll + + libsmartscreen.dll + Microsoft.Management.Deployment.winmd diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index d63f5a7c57..8baedc780e 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -304,6 +304,9 @@ They can be configured through the settings file 'winget settings'. Ignore the installer hash check failure + + Ignore the smart screen check failure + Ignore the malware scan performed as part of installing an archive type package from local manifest @@ -2694,6 +2697,14 @@ Please specify one of them using the --source option to proceed. Uri not well formed: {0} {Locked="{0}"} Error message displayed when the provided uri is not well formed. {0} is a placeholder replaced by the provided uri. + + This operation was blocked as unsafe by Microsoft Defender SmartScreen. + Error message displayed when an operation is using a URI that was blocked by Microsoft Defender SmartScreen. + + + The operation you are attempting to apply has been blocked by your administrator. + Error message displayed when an operation is using a URI zone that was blocked by group policy. + Failed to parse {0} configuration unit settings content or settings content is empty. {Locked="{0}"} {0} is a placeholder replaced by the input winget configure resource unit type. @@ -2843,6 +2854,14 @@ Please specify one of them using the --source option to proceed. Enable Windows Package Manager proxy command line options Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + Enable Windows Package Manager smart screen validation + + + + Enable Windows App Installer Allowed Security Zones + + Set a proxy to use for this execution diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 538c2f6367..c2beaa6ef9 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -14,6 +14,7 @@ 10.0.17763.0 AppInstallerCLITests + @@ -339,6 +340,7 @@ + @@ -1062,6 +1064,9 @@ {866c3f06-636f-4be8-bc24-5f86ecc606a1} + + {98920ab6-27b0-4c0f-b336-fa49de57a1ba} + {82b39fda-e86b-4713-a873-9d56de00247a} diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 5da81fec15..87216e6481 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -368,6 +368,9 @@ Source Files\CLI + + Source Files\CLI + Source Files\Repository diff --git a/src/AppInstallerCLITests/GroupPolicy.cpp b/src/AppInstallerCLITests/GroupPolicy.cpp index 907c55969e..67cc684ea4 100644 --- a/src/AppInstallerCLITests/GroupPolicy.cpp +++ b/src/AppInstallerCLITests/GroupPolicy.cpp @@ -402,6 +402,8 @@ TEST_CASE("GroupPolicy_AllEnabled", "[groupPolicy]") SetRegistryValue(policiesKey.get(), EnableWindowsPackageManagerCommandLineInterfaces, 1); SetRegistryValue(policiesKey.get(), ConfigurationPolicyValueName, 1); SetRegistryValue(policiesKey.get(), ProxyCommandLineOptionsPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), AllowedSecurityZonesPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), BypassSmartScreenValidationPolicyValueName, 1); GroupPolicy groupPolicy{ policiesKey.get() }; for (const auto& policy : TogglePolicy::GetAllPolicies()) diff --git a/src/AppInstallerCLITests/TestSettings.h b/src/AppInstallerCLITests/TestSettings.h index 7da1a728ef..758e94bb1d 100644 --- a/src/AppInstallerCLITests/TestSettings.h +++ b/src/AppInstallerCLITests/TestSettings.h @@ -23,6 +23,8 @@ namespace TestCommon const std::wstring EnableWindowsPackageManagerCommandLineInterfaces = L"EnableWindowsPackageManagerCommandLineInterfaces"; const std::wstring ConfigurationPolicyValueName = L"EnableWindowsPackageManagerConfiguration"; const std::wstring ProxyCommandLineOptionsPolicyValueName = L"EnableWindowsPackageManagerProxyCommandLineOptions"; + const std::wstring AllowedSecurityZonesPolicyValueName = L"EnableWindowsPackageManagerAllowedSecurityZones"; + const std::wstring BypassSmartScreenValidationPolicyValueName = L"EnableBypassWindowsPackageManagerSmartScreenCheck"; const std::wstring SourceUpdateIntervalPolicyValueName = L"SourceAutoUpdateInterval"; const std::wstring SourceUpdateIntervalPolicyOldValueName = L"SourceAutoUpdateIntervalInMinutes"; @@ -90,4 +92,4 @@ namespace TestCommon }; #define REQUIRE_POLICY_EXCEPTION(_expr_, _policy_) REQUIRE_THROWS_MATCHES(_expr_, AppInstaller::Settings::GroupPolicyException, TestCommon::GroupPolicyExceptionMatcher(_policy_)) -} \ No newline at end of file +} diff --git a/src/AppInstallerCLITests/UriValidationFlow.cpp b/src/AppInstallerCLITests/UriValidationFlow.cpp new file mode 100644 index 0000000000..f3779577fa --- /dev/null +++ b/src/AppInstallerCLITests/UriValidationFlow.cpp @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include "TestSettings.h" +#include "TestCommon.h" +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Settings; + +// Test uri with the following format: https://URI_VALIDATION// +constexpr std::string_view InternetAllow = "https://URI_VALIDATION/zone3/allow"sv; +constexpr std::string_view LocalBlock = "https://URI_VALIDATION/zone0/block"sv; +constexpr std::string_view IntranetBlock = "https://URI_VALIDATION/zone1/block"sv; +constexpr std::string_view TrustedBlock = "https://URI_VALIDATION/zone2/block"sv; +constexpr std::string_view InternetBlock = "https://URI_VALIDATION/zone3/block"sv; +constexpr std::string_view UntrustedBlock = "https://URI_VALIDATION/zone4/block"sv; + +#define SET_POLICY_STATE(_policy_, _state_) \ + GroupPolicyTestOverride policies; \ + policies.SetState(_policy_, _state_); + +#define SET_ZONE_POLICY_STATE_AND_BLOCK_ZONE(_state_, _zone_) \ + SET_POLICY_STATE(TogglePolicy::Policy::AllowedSecurityZones, _state_); \ + std::map securityZones; \ + securityZones[_zone_] = false; \ + policies.SetValue(securityZones); \ + +#define EXECUTE_CONTEXT_FOR_CONFIGURATION(_uri_) \ + std::ostringstream uriValidationOutput; \ + TestContext context{ uriValidationOutput, std::cin }; \ + context.Args.AddArg(Execution::Args::Type::ConfigurationFile, _uri_); \ + context << ExecuteUriValidation(UriValidationSource::Configuration); \ + INFO(uriValidationOutput.str()); + +#define EXECUTE_CONTEXT_FOR_PACKAGE_CATALOG_SOURCE(_uri_) \ + std::ostringstream uriValidationOutput; \ + TestContext context{ uriValidationOutput, std::cin }; \ + context.Add(Manifest()); \ + auto source = std::make_shared(); \ + context.Add(TestPackageVersion::Make(context.Get(), source)); \ + ManifestInstaller installer; \ + installer.Url = _uri_; \ + context.Add(std::move(installer)); \ + context << ExecuteUriValidation(UriValidationSource::Package); \ + INFO(uriValidationOutput.str()); + +TEST_CASE("UriValidationFlow_Configuration_SecurityZonePolicy", "[UriValidationFlow][workflow]") +{ + SECTION("Not configured") + { + SET_POLICY_STATE(TogglePolicy::Policy::AllowedSecurityZones, PolicyState::NotConfigured); + EXECUTE_CONTEXT_FOR_CONFIGURATION(InternetAllow); + REQUIRE(S_OK == context.GetTerminationHR()); + } + + SECTION("Enabled") + { + SET_ZONE_POLICY_STATE_AND_BLOCK_ZONE(PolicyState::Enabled, SecurityZoneOptions::Internet); + EXECUTE_CONTEXT_FOR_CONFIGURATION(InternetAllow); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY); + REQUIRE_OUTPUT_HAS_LOC(uriValidationOutput, Resource::String::UriSecurityZoneBlockedByPolicy); + } + + SECTION("Disabled") + { + SET_ZONE_POLICY_STATE_AND_BLOCK_ZONE(PolicyState::Disabled, SecurityZoneOptions::Internet); + EXECUTE_CONTEXT_FOR_CONFIGURATION(InternetAllow); + REQUIRE(S_OK == context.GetTerminationHR()); + } +} + +TEST_CASE("UriValidationFlow_Configuration_SmartScreen", "[UriValidationFlow][workflow]") +{ + SECTION("Not configured") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::NotConfigured); + EXECUTE_CONTEXT_FOR_CONFIGURATION(InternetBlock); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_BLOCKED_BY_REPUTATION_SERVICE); + REQUIRE_OUTPUT_HAS_LOC(uriValidationOutput, Resource::String::UriBlockedBySmartScreen); + } + + SECTION("Enabled") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::Enabled); + EXECUTE_CONTEXT_FOR_CONFIGURATION(InternetBlock); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_BLOCKED_BY_REPUTATION_SERVICE); + REQUIRE_OUTPUT_HAS_LOC(uriValidationOutput, Resource::String::UriBlockedBySmartScreen); + } + + SECTION("Disabled") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::Disabled); + EXECUTE_CONTEXT_FOR_CONFIGURATION(InternetBlock); + REQUIRE(S_OK == context.GetTerminationHR()); + } +} + +TEST_CASE("UriValidationFlow_PackageCatalogSource_SecurityZonePolicy", "[UriValidationFlow][workflow]") +{ + SECTION("Not configured") + { + SET_POLICY_STATE(TogglePolicy::Policy::AllowedSecurityZones, PolicyState::NotConfigured); + EXECUTE_CONTEXT_FOR_PACKAGE_CATALOG_SOURCE(InternetAllow); + REQUIRE(S_OK == context.GetTerminationHR()); + } + + SECTION("Enabled") + { + SET_ZONE_POLICY_STATE_AND_BLOCK_ZONE(PolicyState::Enabled, SecurityZoneOptions::Internet); + EXECUTE_CONTEXT_FOR_PACKAGE_CATALOG_SOURCE(InternetAllow); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY); + REQUIRE_OUTPUT_HAS_LOC(uriValidationOutput, Resource::String::UriSecurityZoneBlockedByPolicy); + } + + SECTION("Disabled") + { + SET_ZONE_POLICY_STATE_AND_BLOCK_ZONE(PolicyState::Disabled, SecurityZoneOptions::Internet); + EXECUTE_CONTEXT_FOR_PACKAGE_CATALOG_SOURCE(InternetAllow); + REQUIRE(S_OK == context.GetTerminationHR()); + } +} + +TEST_CASE("UriValidationFlow_PackageCatalogSource_SmartScreen", "[UriValidationFlow][workflow]") +{ + SECTION("Not configured") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::NotConfigured); + EXECUTE_CONTEXT_FOR_PACKAGE_CATALOG_SOURCE(InternetBlock); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_BLOCKED_BY_REPUTATION_SERVICE); + REQUIRE_OUTPUT_HAS_LOC(uriValidationOutput, Resource::String::UriBlockedBySmartScreen); + } + + SECTION("Enabled") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::Enabled); + EXECUTE_CONTEXT_FOR_PACKAGE_CATALOG_SOURCE(InternetBlock); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_BLOCKED_BY_REPUTATION_SERVICE); + REQUIRE_OUTPUT_HAS_LOC(uriValidationOutput, Resource::String::UriBlockedBySmartScreen); + } + + SECTION("Disabled") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::Disabled); + EXECUTE_CONTEXT_FOR_PACKAGE_CATALOG_SOURCE(InternetBlock); + REQUIRE(S_OK == context.GetTerminationHR()); + } +} + +TEST_CASE("UriValidationFlow_SmartScreenZoneRequirement", "[UriValidationFlow][workflow]") +{ + // Smart screen should only be evaluated for Internet and Untrusted zones. + SECTION("Local") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::Enabled); + EXECUTE_CONTEXT_FOR_CONFIGURATION(LocalBlock); + REQUIRE(S_OK == context.GetTerminationHR()); + } + + SECTION("Intranet") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::Enabled); + EXECUTE_CONTEXT_FOR_CONFIGURATION(IntranetBlock); + REQUIRE(S_OK == context.GetTerminationHR()); + } + + SECTION("Trusted") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::Enabled); + EXECUTE_CONTEXT_FOR_CONFIGURATION(TrustedBlock); + REQUIRE(S_OK == context.GetTerminationHR()); + } + + SECTION("Internet") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::Enabled); + EXECUTE_CONTEXT_FOR_CONFIGURATION(InternetBlock); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_BLOCKED_BY_REPUTATION_SERVICE); + REQUIRE_OUTPUT_HAS_LOC(uriValidationOutput, Resource::String::UriBlockedBySmartScreen); + } + + SECTION("Untrusted") + { + SET_POLICY_STATE(TogglePolicy::Policy::BypassSmartScreenCheck, PolicyState::Enabled); + EXECUTE_CONTEXT_FOR_CONFIGURATION(UntrustedBlock); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_BLOCKED_BY_REPUTATION_SERVICE); + REQUIRE_OUTPUT_HAS_LOC(uriValidationOutput, Resource::String::UriBlockedBySmartScreen); + } +} + +TEST_CASE("UriValidationFlow_BlockedSecurityZoneDoesNotImpactOtherSecurityZones", "[UriValidationFlow][workflow]") +{ + SET_ZONE_POLICY_STATE_AND_BLOCK_ZONE(PolicyState::Enabled, SecurityZoneOptions::Internet); + EXECUTE_CONTEXT_FOR_CONFIGURATION(UntrustedBlock); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_BLOCKED_BY_REPUTATION_SERVICE); + REQUIRE_OUTPUT_HAS_LOC(uriValidationOutput, Resource::String::UriBlockedBySmartScreen); +} diff --git a/src/AppInstallerCLITests/WorkflowCommon.h b/src/AppInstallerCLITests/WorkflowCommon.h index 8bd5348d2d..ad75213e97 100644 --- a/src/AppInstallerCLITests/WorkflowCommon.h +++ b/src/AppInstallerCLITests/WorkflowCommon.h @@ -13,6 +13,9 @@ REQUIRE(_context_.IsTerminated()); \ REQUIRE(_hr_ == _context_.GetTerminationHR()) +#define REQUIRE_OUTPUT_HAS_LOC(_output_, _resource_) \ + REQUIRE(_output_.str().find(Resource::LocString(_resource_).get()) != std::string::npos); + namespace TestCommon { using namespace std::string_view_literals; diff --git a/src/AppInstallerCommonCore/AdminSettings.cpp b/src/AppInstallerCommonCore/AdminSettings.cpp index 89a23d64ea..843811087f 100644 --- a/src/AppInstallerCommonCore/AdminSettings.cpp +++ b/src/AppInstallerCommonCore/AdminSettings.cpp @@ -20,6 +20,7 @@ namespace AppInstaller::Settings constexpr Utility::LocIndView s_AdminSettingsYaml_InstallerHashOverride = "InstallerHashOverride"_liv; constexpr Utility::LocIndView s_AdminSettingsYaml_LocalArchiveMalwareScanOverride = "LocalArchiveMalwareScanOverride"_liv; constexpr Utility::LocIndView s_AdminSettingsYaml_ProxyCommandLineOptions = "ProxyCommandLineOptions"_liv; + constexpr Utility::LocIndView s_AdminSettingsYaml_BypassSmartScreenCheck = "BypassSmartScreenCheck"_liv; constexpr Utility::LocIndView s_AdminSettingsYaml_DefaultProxy = "DefaultProxy"_liv; @@ -46,6 +47,7 @@ namespace AppInstaller::Settings bool InstallerHashOverride = false; bool LocalArchiveMalwareScanOverride = false; bool ProxyCommandLineOptions = false; + bool BypassSmartScreenCheck = false; std::optional DefaultProxy; }; @@ -120,6 +122,9 @@ namespace AppInstaller::Settings case BoolAdminSetting::ProxyCommandLineOptions: m_settingValues.ProxyCommandLineOptions = enabled; return true; + case BoolAdminSetting::BypassSmartScreenCheck: + m_settingValues.BypassSmartScreenCheck = enabled; + return true; default: return false; } @@ -155,6 +160,8 @@ namespace AppInstaller::Settings return m_settingValues.LocalArchiveMalwareScanOverride; case BoolAdminSetting::ProxyCommandLineOptions: return m_settingValues.ProxyCommandLineOptions; + case BoolAdminSetting::BypassSmartScreenCheck: + return m_settingValues.BypassSmartScreenCheck; default: return false; } @@ -210,6 +217,7 @@ namespace AppInstaller::Settings TryReadScalar(document, s_AdminSettingsYaml_InstallerHashOverride, m_settingValues.InstallerHashOverride); TryReadScalar(document, s_AdminSettingsYaml_LocalArchiveMalwareScanOverride, m_settingValues.LocalArchiveMalwareScanOverride); TryReadScalar(document, s_AdminSettingsYaml_ProxyCommandLineOptions, m_settingValues.ProxyCommandLineOptions); + TryReadScalar(document, s_AdminSettingsYaml_BypassSmartScreenCheck, m_settingValues.BypassSmartScreenCheck); std::string defaultProxy; if (TryReadScalar(document, s_AdminSettingsYaml_DefaultProxy, defaultProxy)) @@ -227,6 +235,7 @@ namespace AppInstaller::Settings out << YAML::Key << s_AdminSettingsYaml_InstallerHashOverride << YAML::Value << m_settingValues.InstallerHashOverride; out << YAML::Key << s_AdminSettingsYaml_LocalArchiveMalwareScanOverride << YAML::Value << m_settingValues.LocalArchiveMalwareScanOverride; out << YAML::Key << s_AdminSettingsYaml_ProxyCommandLineOptions << YAML::Value << m_settingValues.ProxyCommandLineOptions; + out << YAML::Key << s_AdminSettingsYaml_BypassSmartScreenCheck << YAML::Value << m_settingValues.BypassSmartScreenCheck; if (m_settingValues.DefaultProxy) { @@ -280,6 +289,10 @@ namespace AppInstaller::Settings { result = BoolAdminSetting::ProxyCommandLineOptions; } + else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_BypassSmartScreenCheck, in)) + { + result = BoolAdminSetting::BypassSmartScreenCheck; + } return result; } @@ -310,6 +323,8 @@ namespace AppInstaller::Settings return s_AdminSettingsYaml_LocalArchiveMalwareScanOverride; case BoolAdminSetting::ProxyCommandLineOptions: return s_AdminSettingsYaml_ProxyCommandLineOptions; + case BoolAdminSetting::BypassSmartScreenCheck: + return s_AdminSettingsYaml_BypassSmartScreenCheck; default: return "Unknown"_liv; } @@ -340,6 +355,8 @@ namespace AppInstaller::Settings return TogglePolicy::Policy::LocalArchiveMalwareScanOverride; case BoolAdminSetting::ProxyCommandLineOptions: return TogglePolicy::Policy::ProxyCommandLineOptions; + case BoolAdminSetting::BypassSmartScreenCheck: + return TogglePolicy::Policy::BypassSmartScreenCheck; default: return TogglePolicy::Policy::None; } diff --git a/src/AppInstallerCommonCore/Public/winget/AdminSettings.h b/src/AppInstallerCommonCore/Public/winget/AdminSettings.h index d46fafdf01..b37167b447 100644 --- a/src/AppInstallerCommonCore/Public/winget/AdminSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/AdminSettings.h @@ -16,6 +16,7 @@ namespace AppInstaller::Settings InstallerHashOverride, LocalArchiveMalwareScanOverride, ProxyCommandLineOptions, + BypassSmartScreenCheck, Max, }; diff --git a/src/AppInstallerSharedLib/GroupPolicy.cpp b/src/AppInstallerSharedLib/GroupPolicy.cpp index 862c1ccce4..55300044ab 100644 --- a/src/AppInstallerSharedLib/GroupPolicy.cpp +++ b/src/AppInstallerSharedLib/GroupPolicy.cpp @@ -116,6 +116,44 @@ namespace AppInstaller::Settings (FoldHelper{}, ..., Validate(P)>(policiesKey, policies)); } + + template + std::optional::value_t> ReadEnums(const Registry::Key& policiesKey) + { + using Mapping = details::ValuePolicyMapping

; + + auto enumKey = policiesKey.SubKey(Mapping::KeyName); + if (!enumKey.has_value()) + { + return std::nullopt; + } + + typename Mapping::value_t map; + for (const auto& value : enumKey->Values()) + { + std::optional potentialValue = value.Value(); + + if (potentialValue) + { + auto entry = Mapping::ReadAndValidateItem(value); + if (entry.has_value()) + { + map.insert(*entry); + } + else + { + AICLI_LOG(Core, Warning, << "Failed to read Group Policy enum value. Policy [" << Mapping::KeyName << "], Value [" << value.Name() << ']'); + } + } + else + { + AICLI_LOG(Core, Verbose, << "Group Policy enum value not found. Policy [" << Mapping::KeyName << "], Value [" << value.Name() << ']'); + } + } + + return map; + } + // Reads a list from a Group Policy. // The list is stored in a sub-key of the policies key, and each value in that key is a list item. // Cases not considered by this function because we don't use them: @@ -255,9 +293,16 @@ namespace AppInstaller::Settings return ReadList<_policy_>(policiesKey); \ } +#define POLICY_MAPPING_DEFAULT_ENUM_READ(_policy_) \ + std::optional::value_t> ValuePolicyMapping<_policy_>::ReadAndValidate(const Registry::Key& policiesKey) \ + { \ + return ReadEnums<_policy_>(policiesKey); \ + } + POLICY_MAPPING_DEFAULT_LIST_READ(ValuePolicy::AdditionalSources); POLICY_MAPPING_DEFAULT_LIST_READ(ValuePolicy::AllowedSources); POLICY_MAPPING_DEFAULT_READ(ValuePolicy::DefaultProxy); + POLICY_MAPPING_DEFAULT_ENUM_READ(ValuePolicy::AllowedSecurityZones); std::nullopt_t ValuePolicyMapping::ReadAndValidate(const Registry::Key&) { @@ -292,6 +337,27 @@ namespace AppInstaller::Settings { return ReadSourceFromRegistryValue(item); } + + std::optional> ValuePolicyMapping::ReadAndValidateItem(const Registry::ValueList::ValueRef& entry) + { +#define CONFIGURATION_ALLOWED_ZONES_READ(_zone_) \ + if (entry.Name() == #_zone_) \ + { \ + auto data = entry.Value()->TryGetValue(); \ + auto value = data.value_or(true); \ + return std::make_pair(SecurityZoneOptions::_zone_, value); \ + } + + CONFIGURATION_ALLOWED_ZONES_READ(LocalMachine); + CONFIGURATION_ALLOWED_ZONES_READ(Intranet); + CONFIGURATION_ALLOWED_ZONES_READ(TrustedSites); + CONFIGURATION_ALLOWED_ZONES_READ(Internet); + CONFIGURATION_ALLOWED_ZONES_READ(UntrustedSites); +#undef CONFIGURATION_ALLOWED_ZONES_READ + + AICLI_LOG(Core, Warning, << "Unknown value in WindowsPackageManagerAllowedSecurityZones: " << entry.Name()); + return std::nullopt; + } } TogglePolicy TogglePolicy::GetPolicy(TogglePolicy::Policy policy) @@ -326,6 +392,10 @@ namespace AppInstaller::Settings return TogglePolicy(policy, "EnableWindowsPackageManagerConfiguration"sv, String::PolicyEnableWinGetConfiguration); case TogglePolicy::Policy::ProxyCommandLineOptions: return TogglePolicy(policy, "EnableWindowsPackageManagerProxyCommandLineOptions"sv, String::PolicyEnableProxyCommandLineOptions); + case TogglePolicy::Policy::AllowedSecurityZones: + return TogglePolicy(policy, "EnableWindowsPackageManagerAllowedSecurityZones"sv, String::PolicyEnableAllowedSecurityZones); + case TogglePolicy::Policy::BypassSmartScreenCheck: + return TogglePolicy(policy, "EnableBypassWindowsPackageManagerSmartScreenCheck"sv, String::PolicyEnableBypassSmartScreenValidation); default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index 3524d812bf..309867f47b 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -152,6 +152,7 @@ #define APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED ((HRESULT)0x8A150084) #define APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN ((HRESULT)0x8A150085) #define APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE ((HRESULT)0x8A150086) +#define APPINSTALLER_CLI_ERROR_BLOCKED_BY_REPUTATION_SERVICE ((HRESULT)0x8A150087) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h b/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h index 850526f60c..b84a233adb 100644 --- a/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h +++ b/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h @@ -23,9 +23,20 @@ namespace AppInstaller::Settings AdditionalSources, AllowedSources, DefaultProxy, + AllowedSecurityZones, Max, }; + // Enum for the security zones + enum class SecurityZoneOptions : DWORD + { + LocalMachine = 0, + Intranet = 1, + TrustedSites = 2, + Internet = 3, + UntrustedSites = 4, + }; + // A policy that acts as a toggle to enable or disable a feature. // They are backed by a DWORD value with values 0 and 1. struct TogglePolicy @@ -47,6 +58,8 @@ namespace AppInstaller::Settings WinGetCommandLineInterfaces, Configuration, ProxyCommandLineOptions, + AllowedSecurityZones, + BypassSmartScreenCheck, Max, }; @@ -113,6 +126,12 @@ namespace AppInstaller::Settings // item_t - Type of each item // KeyName -- Name of the sub-key containing the list // ReadAndValidateItem() - Function that reads a single item from a subkey + + // For enums: + // KeyName - Name of the sub-key containing the enums + // _mapTypeKey_ - Type of the key in the map + // _mapTypeValue_ - Type of the value in the map + // _mapTypeName_ - Name of the map type }; template<> @@ -149,11 +168,20 @@ namespace AppInstaller::Settings static std::optional ReadAndValidateItem(const Registry::Value& item); \ ) +#define POLICY_MAPPING_ENUM_SPECIALIZATION(_policy_, _mapTypeName_, _mapTypeKey_, _mapTypeValue_, _keyName_) \ + typedef std::map<_mapTypeKey_, _mapTypeValue_> _mapTypeName_; \ + POLICY_MAPPING_SPECIALIZATION(_policy_, _mapTypeName_, \ + static constexpr std::string_view KeyName = _keyName_; \ + static std::optional ReadAndValidateItem(const Registry::ValueList::ValueRef& item); \ + ) + POLICY_MAPPING_VALUE_SPECIALIZATION(ValuePolicy::SourceAutoUpdateIntervalInMinutes, uint32_t, "SourceAutoUpdateInterval"sv, Registry::Value::Type::DWord); POLICY_MAPPING_VALUE_SPECIALIZATION(ValuePolicy::DefaultProxy, std::string, "DefaultProxy"sv, Registry::Value::Type::String); POLICY_MAPPING_LIST_SPECIALIZATION(ValuePolicy::AdditionalSources, SourceFromPolicy, "AdditionalSources"sv); POLICY_MAPPING_LIST_SPECIALIZATION(ValuePolicy::AllowedSources, SourceFromPolicy, "AllowedSources"sv); + + POLICY_MAPPING_ENUM_SPECIALIZATION(ValuePolicy::AllowedSecurityZones, SecurityZoneMap_t, SecurityZoneOptions, bool, "WindowsPackageManagerAllowedSecurityZones"sv); } // Representation of the policies read from the registry. diff --git a/src/AppInstallerSharedLib/Public/winget/Resources.h b/src/AppInstallerSharedLib/Public/winget/Resources.h index dc6591abd0..d3ad8feed3 100644 --- a/src/AppInstallerSharedLib/Public/winget/Resources.h +++ b/src/AppInstallerSharedLib/Public/winget/Resources.h @@ -60,6 +60,8 @@ namespace AppInstaller WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWindowsPackageManagerCommandLineInterfaces); WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWinGetConfiguration); WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableProxyCommandLineOptions); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableAllowedSecurityZones); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableBypassSmartScreenValidation); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidFieldFormat); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidFieldValue); diff --git a/src/Internal/UriValidation/.gitignore b/src/Internal/UriValidation/.gitignore new file mode 100644 index 0000000000..a19a3d9a13 --- /dev/null +++ b/src/Internal/UriValidation/.gitignore @@ -0,0 +1,4 @@ +# The following files are injected in the pipeline build +packages.config +UriValidation.props +UriValidation_internal.cpp \ No newline at end of file diff --git a/src/Internal/UriValidation/UriValidation.cpp b/src/Internal/UriValidation/UriValidation.cpp new file mode 100644 index 0000000000..bc68af9656 --- /dev/null +++ b/src/Internal/UriValidation/UriValidation.cpp @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "UriValidation.h" + +namespace AppInstaller::UriValidation +{ + namespace + { +#ifndef AICLI_DISABLE_TEST_HOOKS + bool EndsWith(const std::string& value, const std::string& ending) + { + if (ending.size() > value.size()) + { + return false; + } + + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); + } +#endif + } + + UriValidationResult ValidateUri(const std::string& uri) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + // For testing purposes, block all URIs that end with "/block" + if (EndsWith(uri, "/block")) + { + return UriValidationResult(UriValidationDecision::Block, std::string()); + } +#endif + + // In Dev mode, allow all URIs + return UriValidationResult(UriValidationDecision::Allow, std::string()); + } +} diff --git a/src/Internal/UriValidation/UriValidation.h b/src/Internal/UriValidation/UriValidation.h new file mode 100644 index 0000000000..b8676c4450 --- /dev/null +++ b/src/Internal/UriValidation/UriValidation.h @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include + +namespace AppInstaller::UriValidation +{ + // The decision made based on the Uri validation. + enum class UriValidationDecision + { + Allow, + Block, + }; + + // The result of a Uri validation. + class UriValidationResult + { + public: + UriValidationResult(UriValidationDecision decision) : m_decision(decision), m_feedback(std::string()) {} + UriValidationResult(UriValidationDecision decision, std::string feedback) : m_decision(decision), m_feedback(feedback) {} + UriValidationDecision Decision() const { return m_decision; } + std::string Feedback() const { return m_feedback; } + private: + // The decision made based on the Uri validation. + UriValidationDecision m_decision; + + // Uri to give feedback to smart screen about the decision. + std::string m_feedback; + }; + + // Validate the given Uri. + UriValidationResult ValidateUri(const std::string& uri); +} diff --git a/src/Internal/UriValidation/UriValidation.vcxproj b/src/Internal/UriValidation/UriValidation.vcxproj new file mode 100644 index 0000000000..48cfe3d68c --- /dev/null +++ b/src/Internal/UriValidation/UriValidation.vcxproj @@ -0,0 +1,414 @@ + + + + + + true + true + true + 15.0 + {98920ab6-27b0-4c0f-b336-fa49de57a1ba} + Win32Proj + UriValidation + 10.0.22621.0 + 10.0.17763.0 + true + true + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Fuzzing + x64 + + + Fuzzing + Win32 + + + ReleaseStatic + ARM + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + StaticLibrary + v140 + v141 + v142 + v143 + Unicode + + + true + true + + + false + true + false + + + false + true + false + + + false + false + false + true + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\..\CodeAnalysis.ruleset + + + + NotUsing + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + true + true + true + false + false + false + + + false + Windows + Windows + Windows + + + + + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + false + + + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + true + true + false + false + false + false + false + false + false + false + + + true + true + false + Windows + Windows + Windows + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + true + true + false + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + MultiThreaded + false + false + false + false + + + true + true + false + Windows + Windows + Windows + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD;WINGET_DISABLE_FOR_FUZZING;_DISABLE_VECTOR_ANNOTATION;_DISABLE_STRING_ANNOTATION + $(ProjectDir);$(ProjectDir)..\..\AppInstallerSharedLib;$(ProjectDir)..\..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + stdcpp17 + MultiThreaded + %(AdditionalOptions) /fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div + false + + + true + true + false + Windows + + + + + AICLI_DISABLE_TEST_HOOKS;%(PreprocessorDefinitions) + + + + + WINGET_DISABLE_EXPERIMENTAL_FEATURES;%(PreprocessorDefinitions) + + + + + WINGET_ENABLE_RELEASE_BUILD;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/Internal/UriValidation/UriValidation.vcxproj.filters b/src/Internal/UriValidation/UriValidation.vcxproj.filters new file mode 100644 index 0000000000..01ce4a95da --- /dev/null +++ b/src/Internal/UriValidation/UriValidation.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + Source Files + + + + + + + + + \ No newline at end of file diff --git a/src/WindowsPackageManager/WindowsPackageManager.vcxproj b/src/WindowsPackageManager/WindowsPackageManager.vcxproj index 510d07aa61..c2f46098e8 100644 --- a/src/WindowsPackageManager/WindowsPackageManager.vcxproj +++ b/src/WindowsPackageManager/WindowsPackageManager.vcxproj @@ -17,6 +17,7 @@ . + @@ -219,7 +220,8 @@ true false $(ProjectDir)..\vcpkg_installed - + + x64-release-static @@ -519,6 +521,9 @@ {866c3f06-636f-4be8-bc24-5f86ecc606a1} + + {98920ab6-27b0-4c0f-b336-fa49de57a1ba} + {82b39fda-e86b-4713-a873-9d56de00247a} @@ -551,4 +556,4 @@ - + \ No newline at end of file