winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit e69201b0c8f49002e9961b460ca009c313e5c67b
parent 030a4c5cb8c4f4a30384ec3f1b406d524f022454
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Tue, 21 Jan 2025 15:31:25 -0800

Add configuration environment (#5139)

## Change
This change adds a `ConfigurationEnvironment` type which is a property
of a `ConfigurationUnit`. It defines the environment in which the unit
should be run. This currently encompasses two properties; the security
context and the processor.

The values are parsed from the metadata, but the object is considered
authoritative at runtime and when serializing. They are also inherited
in schema 0.3, allowing the entire set to be defined in a single
environment in it's metadata.

The code that was using the `securityContext` metadata has been updated
to use the environment property instead. It also uses the unique
environment calculation function for the set to more efficiently
determine which contexts are present.
Diffstat:
M.github/actions/spelling/allow.txt | 1+
Msrc/AppInstallerCLICore/Commands/DebugCommand.cpp | 1+
Msrc/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp | 48++++++++++++++----------------------------------
Msrc/AppInstallerCLIPackage/Package.appxmanifest | 1+
Asrc/Microsoft.Management.Configuration.Processor/Extensions/DictionaryExtensions.cs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs | 5+++--
Msrc/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs | 22+++++++++++++++++++---
Asrc/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationEnvironmentData.cs | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationExtensions.cs | 2+-
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationMixedElevationTests.cs | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs | 450+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/Microsoft.Management.Configuration/ArgumentValidation.cpp | 136++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/Microsoft.Management.Configuration/ArgumentValidation.h | 6++++++
Asrc/Microsoft.Management.Configuration/ConfigurationEnvironment.cpp | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/ConfigurationEnvironment.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationSet.cpp | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationSet.h | 3+++
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser.cpp | 17+++++++++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser.h | 3+++
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp | 1+
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.cpp | 138++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h | 15++++++++++++---
Msrc/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp | 123++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/Microsoft.Management.Configuration/ConfigurationSetSerializer.h | 15++++++++-------
Msrc/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp | 7+++++--
Msrc/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.cpp | 154++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.h | 6+++++-
Msrc/Microsoft.Management.Configuration/ConfigurationSetUtilities.cpp | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationSetUtilities.h | 26+++++++++++++++++++++++++-
Msrc/Microsoft.Management.Configuration/ConfigurationUnit.cpp | 11+++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationUnit.h | 6++++++
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl | 45++++++++++++++++++++++++++++++++++++++++++++-
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj | 2++
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters | 6++++++
35 files changed, 1808 insertions(+), 157 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -321,6 +321,7 @@ STDAPI STGM storeedgefd stpkgmanvalwestustest +stringable STRINGID STRINGIZE STRSAFE diff --git a/src/AppInstallerCLICore/Commands/DebugCommand.cpp b/src/AppInstallerCLICore/Commands/DebugCommand.cpp @@ -102,6 +102,7 @@ namespace AppInstaller::CLI OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::TestConfigurationUnitResult>>(context); OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::IApplyGroupMemberSettingsResult>>(context); OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::ITestSettingsResult>>(context); + OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::ConfigurationEnvironment>>(context); OutputProxyStubInterfaceRegistration<winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessorDetails2>(context); OutputProxyStubInterfaceRegistration<winrt::Microsoft::Management::Configuration::IGetAllSettingsConfigurationUnitProcessor>(context); OutputProxyStubInterfaceRegistration<winrt::Microsoft::Management::Configuration::IConfigurationStatics2>(context); diff --git a/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp b/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp @@ -142,9 +142,9 @@ namespace AppInstaller::CLI::ConfigurationRemoting // Check for multiple integrity level requirements bool multipleIntegrityLevels = false; bool higherIntegrityLevelsThanCurrent = false; - for (const auto& existingUnit : m_configurationSet.Units()) + for (const auto& environment : m_configurationSet.GetUnitEnvironments()) { - auto integrityLevel = GetIntegrityLevelForUnit(existingUnit); + auto integrityLevel = SecurityContextToIntegrityLevel(environment.Context()); if (integrityLevel != m_currentIntegrityLevel) { multipleIntegrityLevels = true; @@ -183,9 +183,9 @@ namespace AppInstaller::CLI::ConfigurationRemoting std::call_once(m_createUnitSetProcessorsOnce, [&]() { - for (const auto& existingUnit : m_configurationSet.Units()) + for (const auto& environment : m_configurationSet.GetUnitEnvironments()) { - Security::IntegrityLevel requiredIntegrityLevel = GetIntegrityLevelForUnit(existingUnit); + Security::IntegrityLevel requiredIntegrityLevel = SecurityContextToIntegrityLevel(environment.Context()); if (m_setProcessors.find(requiredIntegrityLevel) == m_setProcessors.end()) { @@ -212,16 +212,13 @@ namespace AppInstaller::CLI::ConfigurationRemoting private: // Converts the string representation of SecurityContext to the integrity level - Security::IntegrityLevel SecurityContextToIntegrityLevel(winrt::hstring securityContext) + Security::IntegrityLevel SecurityContextToIntegrityLevel(SecurityContext securityContext) { - std::wstring securityContextLower = Utility::ToLower(securityContext); - - if (securityContextLower == L"elevated") - { - return Security::IntegrityLevel::High; - } - else if (securityContextLower == L"restricted") + switch (securityContext) { + case SecurityContext::Current: + return m_currentIntegrityLevel; + case SecurityContext::Restricted: #ifndef AICLI_DISABLE_TEST_HOOKS if (m_enableRestrictedIntegrityLevel) { @@ -236,34 +233,17 @@ namespace AppInstaller::CLI::ConfigurationRemoting // Technically this means the default level of the user token, so if UAC is disabled it would be the only integrity level (aka current). // return Security::IntegrityLevel::Medium; } + case SecurityContext::Elevated: + return Security::IntegrityLevel::High; + default: + THROW_WIN32(ERROR_NOT_SUPPORTED); } - else if (securityContextLower == L"current") - { - return m_currentIntegrityLevel; - } - - THROW_WIN32(ERROR_NOT_SUPPORTED); } // Gets the integrity level that the given unit should be run at Security::IntegrityLevel GetIntegrityLevelForUnit(const ConfigurationUnit& unit) { - // Support for 0.2 schema via metadata value - // TODO: Support case-insensitive lookup by iteration - auto unitMetadata = unit.Metadata(); - auto securityContext = unitMetadata.TryLookup(L"securityContext"); - if (securityContext) - { - auto securityContextProperty = securityContext.try_as<IPropertyValue>(); - if (securityContextProperty && securityContextProperty.Type() == PropertyType::String) - { - return SecurityContextToIntegrityLevel(securityContextProperty.GetString()); - } - } - - // TODO: Support for 0.3 schema will require a group processor wrapper - - return m_currentIntegrityLevel; + return SecurityContextToIntegrityLevel(unit.Environment().Context()); } // Serializes the set properties to be sent to the remote server diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -113,6 +113,7 @@ <Interface Name="Windows.Foundation.Collections.IIterable`1&lt;Microsoft.Management.Configuration.TestConfigurationUnitResult&gt;" InterfaceId="73848262-86D4-5FFC-8353-8408C4E649DE" /> <Interface Name="Windows.Foundation.Collections.IIterable`1&lt;Microsoft.Management.Configuration.IApplyGroupMemberSettingsResult&gt;" InterfaceId="5086070C-F468-5B00-8352-50FB420BA8B0" /> <Interface Name="Windows.Foundation.Collections.IIterable`1&lt;Microsoft.Management.Configuration.ITestSettingsResult&gt;" InterfaceId="2D28E6AA-7036-5D78-9B58-9456F1E332FE" /> + <Interface Name="Windows.Foundation.Collections.IIterable`1&lt;Microsoft.Management.Configuration.ConfigurationEnvironment&gt;" InterfaceId="47B18106-976B-5532-8E81-F58D304DFA43" /> <Interface Name="Microsoft.Management.Configuration.IConfigurationUnitProcessorDetails2" InterfaceId="E89623ED-76E2-5145-B920-D09659554E35" /> <Interface Name="Microsoft.Management.Configuration.IGetAllSettingsConfigurationUnitProcessor" InterfaceId="72EB8304-D8D3-57D4-9940-7C1C4AD8C40C" /> <Interface Name="Microsoft.Management.Configuration.IConfigurationStatics2" InterfaceId="540BE073-F2EF-5375-83AA-8E23086B0669" /> diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/DictionaryExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/DictionaryExtensions.cs @@ -0,0 +1,62 @@ +// ----------------------------------------------------------------------------- +// <copyright file="DictionaryExtensions.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Extensions +{ + using System; + using System.Collections; + using System.Collections.Generic; + using Windows.Foundation.Collections; + + /// <summary> + /// Extensions for dictionaries. + /// </summary> + internal static class DictionaryExtensions + { + /// <summary> + /// Performs a deep compare of the dictionaries. + /// </summary> + /// <param name="first">First dictionary.</param> + /// <param name="second">Second dictionary.</param> + /// <returns>Whether the two dictionaries equal.</returns> + internal static bool ContentEquals(this IDictionary<string, string> first, IDictionary<string, string> second) + { + if (first.Count != second.Count) + { + return false; + } + + foreach (var keyValuePair in first) + { + string key = keyValuePair.Key; + if (!second.ContainsKey(key)) + { + return false; + } + + var firstValue = keyValuePair.Value; + var secondValue = second[key]; + + // Empty value check. + if (firstValue == null && secondValue == null) + { + continue; + } + else if (firstValue == null || secondValue == null) + { + return false; + } + + if (firstValue != secondValue) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs @@ -105,13 +105,14 @@ namespace Microsoft.Management.Configuration.Processor.Extensions foreach (var keyValuePair in first) { - if (!second.ContainsKey(keyValuePair.Key)) + string key = keyValuePair.Key; + if (!second.ContainsKey(key)) { return false; } var firstValue = keyValuePair.Value; - var secondValue = second[keyValuePair.Key]; + var secondValue = second[key]; // Empty value check. if (firstValue == null && secondValue == null) diff --git a/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs @@ -217,9 +217,25 @@ namespace Microsoft.Management.Configuration.Processor.Set private static bool ConfigurationUnitEquals(ConfigurationUnit first, ConfigurationUnit second) { - if (first.Identifier != second.Identifier || - first.Type != second.Type || - first.Intent != second.Intent) + var firstIdentifier = first.Identifier; + var firstIntent = first.Intent; + var firstType = first.Type; + var secondIdentifier = second.Identifier; + var secondType = second.Type; + var secondIntent = second.Intent; + + if (firstIdentifier != secondIdentifier || + firstType != secondType || + firstIntent != secondIntent) + { + return false; + } + + var firstEnvironment = first.Environment; + var secondEnvironment = second.Environment; + if (firstEnvironment.Context != secondEnvironment.Context || + firstEnvironment.ProcessorIdentifier != secondEnvironment.ProcessorIdentifier || + !firstEnvironment.ProcessorProperties.ContentEquals(secondEnvironment.ProcessorProperties)) { return false; } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationEnvironmentData.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationEnvironmentData.cs @@ -0,0 +1,86 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigurationEnvironmentData.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Contains the data defining a configuration environment. + /// </summary> + internal class ConfigurationEnvironmentData + { + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationEnvironmentData"/> class. + /// </summary> + internal ConfigurationEnvironmentData() { } + + /// <summary> + /// Gets or sets the security context. + /// </summary> + internal SecurityContext Context { get; set; } = SecurityContext.Current; + + /// <summary> + /// Gets or sets the processor identifier. + /// </summary> + internal string ProcessorIdentifier { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets the processor properties. + /// </summary> + internal Dictionary<string, string> ProcessorProperties { get; set; } = new (); + + /// <summary> + /// Applies this environment to the given unit. + /// </summary> + /// <param name="unit">The unit to apply to.</param> + /// <returns>The given unit.</returns> + internal ConfigurationUnit ApplyToUnit(ConfigurationUnit unit) + { + var environment = unit.Environment; + + environment.Context = this.Context; + environment.ProcessorIdentifier = this.ProcessorIdentifier; + environment.ProcessorProperties.Clear(); + foreach (var property in this.ProcessorProperties) + { + environment.ProcessorProperties.Add(property.Key, property.Value); + } + + return unit; + } + + /// <summary> + /// Tests whether the given properties match this object's properties. + /// </summary> + /// <param name="properties">The properties to test.</param> + /// <returns>True if the properties match; false if not.</returns> + internal bool PropertiesEqual(IDictionary<string, string> properties) + { + if (properties.Count != this.ProcessorProperties.Count) + { + return false; + } + + foreach (var property in properties) + { + string? value = null; + if (!this.ProcessorProperties.TryGetValue(property.Key, out value)) + { + return false; + } + + if (property.Value != value) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationExtensions.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationExtensions.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ConfigurationExtensions.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationMixedElevationTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationMixedElevationTests.cs @@ -64,7 +64,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); elevatedUnit.Metadata.Add("version", version.ToString()); elevatedUnit.Metadata.Add("module", moduleName); - elevatedUnit.Metadata.Add("securityContext", "elevated"); + elevatedUnit.Environment.Context = SecurityContext.Elevated; elevatedUnit.Settings.Add("directoryPath", tempDirectory); elevatedUnit.Type = resourceName; elevatedUnit.Intent = ConfigurationUnitIntent.Apply; @@ -102,6 +102,69 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests } /// <summary> + /// Verifies that applying units of mixed elevation is successful. Also verifies that the elevated processor has a different process id. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> + [Fact] + public async Task ApplyMixedElevationUnits_Schema_0_3() + { + string resourceName = "xE2ETestResource/E2ETestResourcePID"; + Version version = new Version("0.0.0.1"); + + string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDirectory); + + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.SchemaVersion = "0.3"; + configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); + + ConfigurationUnit unit = this.ConfigurationUnit(); + unit.Metadata.Add("version", version.ToString()); + unit.Settings.Add("directoryPath", tempDirectory); + unit.Type = resourceName; + unit.Identifier = "current"; + + ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); + elevatedUnit.Intent = ConfigurationUnitIntent.Unknown; + elevatedUnit.Metadata.Add("version", version.ToString()); + elevatedUnit.Environment.Context = SecurityContext.Elevated; + elevatedUnit.Settings.Add("directoryPath", tempDirectory); + elevatedUnit.Type = resourceName; + elevatedUnit.Identifier = "elevated"; + + configurationSet.Units = new ConfigurationUnit[] { unit, elevatedUnit }; + + IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + + // Get the number of unique PIDs from temp directory. + int pidCount = Directory.GetFiles(tempDirectory).Length; + + // Clean up temp directory folder. + Directory.Delete(tempDirectory, true); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.Equal(2, result.UnitResults.Count); + + foreach (var unitResult in result.UnitResults) + { + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + // There should be exactly 2 unique PIDs, one for each integrity level. + Assert.Equal(2, pidCount); + } + + /// <summary> /// Verifies that creating a high integrity unit processor for a non elevated unit should return an invalid operation result. /// </summary> /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> @@ -121,13 +184,14 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests ConfigurationUnit unit = this.ConfigurationUnit(); unit.Metadata.Add("version", version.ToString()); unit.Metadata.Add("module", moduleName); + unit.Metadata.Add("unique", "value"); unit.Type = resourceName; unit.Intent = ConfigurationUnitIntent.Apply; ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); elevatedUnit.Metadata.Add("version", version.ToString()); elevatedUnit.Metadata.Add("module", moduleName); - elevatedUnit.Metadata.Add("securityContext", "elevated"); + elevatedUnit.Environment.Context = SecurityContext.Elevated; elevatedUnit.Type = resourceName; elevatedUnit.Intent = ConfigurationUnitIntent.Apply; @@ -182,7 +246,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); elevatedUnit.Metadata.Add("version", version.ToString()); elevatedUnit.Metadata.Add("module", moduleName); - elevatedUnit.Metadata.Add("securityContext", "elevated"); + elevatedUnit.Environment.Context = SecurityContext.Elevated; elevatedUnit.Settings.Add("directoryPath", tempDirectory); elevatedUnit.Type = resourceName; elevatedUnit.Intent = ConfigurationUnitIntent.Apply; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs @@ -7,9 +7,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { using System; + using System.Collections.Generic; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Microsoft.VisualBasic; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Windows.Foundation.Collections; using Windows.Storage.Streams; using Xunit; @@ -130,5 +132,116 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests string yamlOutput = this.ReadStream(stream); Assert.NotNull(yamlOutput); } + + /// <summary> + /// Test for unique unit environment calculation. + /// </summary> + [Fact] + public void ConfigurationSet_UnitEnvironments() + { + ConfigurationSet testSet = this.ConfigurationSet(); + + Dictionary<string, string> firstProperty = new Dictionary<string, string>(); + firstProperty.Add("property", "value1"); + + Dictionary<string, string> secondProperty = new Dictionary<string, string>(); + secondProperty.Add("property", "value2"); + + Helpers.ConfigurationEnvironmentData[] environments = new Helpers.ConfigurationEnvironmentData[] + { + new () { ProcessorIdentifier = "dsc3" }, + new () { ProcessorIdentifier = "pwsh" }, + new () { ProcessorIdentifier = "dsc3", Context = SecurityContext.Elevated }, + new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Restricted }, + new () { ProcessorIdentifier = "dsc3", ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", ProcessorProperties = secondProperty }, + new () { ProcessorIdentifier = "dsc3", Context = SecurityContext.Restricted, ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Elevated, ProcessorProperties = firstProperty }, + }; + + foreach (int index in new int[] { 0, 1, 1, 2, 3, 5, 4, 6, 7, 8, 2, 7, 7, 7 }) + { + Assert.True(index < environments.Length); + testSet.Units.Add(environments[index].ApplyToUnit(this.ConfigurationUnit())); + } + + var uniqueEnvironments = testSet.GetUnitEnvironments(); + this.EnsureEnvironmentEquivalence(environments, uniqueEnvironments); + } + + /// <summary> + /// Test for unique unit environment calculation with group units. + /// </summary> + [Fact] + public void ConfigurationSet_GroupUnitEnvironments() + { + ConfigurationSet testSet = this.ConfigurationSet(); + + Dictionary<string, string> firstProperty = new Dictionary<string, string>(); + firstProperty.Add("property", "value1"); + + Dictionary<string, string> secondProperty = new Dictionary<string, string>(); + secondProperty.Add("property", "value2"); + + Helpers.ConfigurationEnvironmentData[] environments = new Helpers.ConfigurationEnvironmentData[] + { + new () { ProcessorIdentifier = "dsc3" }, + new () { ProcessorIdentifier = "pwsh" }, + new () { ProcessorIdentifier = "dsc3", Context = SecurityContext.Elevated }, + new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Restricted }, + new () { ProcessorIdentifier = "dsc3", ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", ProcessorProperties = secondProperty }, + new () { ProcessorIdentifier = "dsc3", Context = SecurityContext.Restricted, ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Elevated, ProcessorProperties = firstProperty }, + new (), // The default environment for the group unit + }; + + foreach (int index in new int[] { 0, 1, 1, 3, 5, 4, 6, 8 }) + { + Assert.True(index < environments.Length); + testSet.Units.Add(environments[index].ApplyToUnit(this.ConfigurationUnit())); + } + + var groupUnit = this.ConfigurationUnit(); + groupUnit.IsGroup = true; + + foreach (int index in new int[] { 7, 5, 2 }) + { + Assert.True(index < environments.Length); + groupUnit.Units.Add(environments[index].ApplyToUnit(this.ConfigurationUnit())); + } + + testSet.Units.Add(groupUnit); + + var uniqueEnvironments = testSet.GetUnitEnvironments(); + this.EnsureEnvironmentEquivalence(environments, uniqueEnvironments); + } + + private void EnsureEnvironmentEquivalence(Helpers.ConfigurationEnvironmentData[] expectedEnvironments, IList<ConfigurationEnvironment>? actualEnvironments) + { + Assert.NotNull(actualEnvironments); + Assert.Equal(expectedEnvironments.Length, actualEnvironments.Count); + + bool[] foundEnvironments = new bool[expectedEnvironments.Length]; + foreach (var actual in actualEnvironments) + { + for (int i = 0; i < expectedEnvironments.Length; i++) + { + var expected = expectedEnvironments[i]; + if (actual.Context == expected.Context && actual.ProcessorIdentifier == expected.ProcessorIdentifier && expected.PropertiesEqual(actual.ProcessorProperties)) + { + foundEnvironments[i] = true; + break; + } + } + } + + for (int i = 0; i < foundEnvironments.Length; i++) + { + Assert.True(foundEnvironments[i], $"Found expected environment: {i}"); + } + } } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs @@ -527,14 +527,14 @@ properties: Assert.Equal("FakeResource", set.Units[0].Type); Assert.Equal(ConfigurationUnitIntent.Assert, set.Units[0].Intent); Assert.Equal("TestId", set.Units[0].Identifier); - this.VerifyValueSet(set.Units[0].Metadata, new ("description", "FakeDescription"), new ("allowPrerelease", true), new ("securityContext", "elevated"), new ("module", "FakeModule")); + this.VerifyValueSet(set.Units[0].Metadata, new ("description", "FakeDescription"), new ("allowPrerelease", true), new ("module", "FakeModule")); this.VerifyValueSet(set.Units[0].Settings, new ("TestString", "Hello"), new ("TestBool", false), new ("TestInt", 1234)); Assert.Equal("FakeResource2", set.Units[1].Type); Assert.Equal(ConfigurationUnitIntent.Apply, set.Units[1].Intent); Assert.Equal("TestId2", set.Units[1].Identifier); this.VerifyStringArray(set.Units[1].Dependencies, "TestId", "dependency2", "dependency3"); - this.VerifyValueSet(set.Units[1].Metadata, new ("description", "FakeDescription2"), new ("securityContext", "elevated"), new ("module", "FakeModule2")); + this.VerifyValueSet(set.Units[1].Metadata, new ("description", "FakeDescription2"), new ("module", "FakeModule2")); ValueSet mapping = new ValueSet(); mapping.Add("Key", "TestValue"); @@ -568,7 +568,7 @@ resources: metadata: description: FakeDescription allowPrerelease: true - securityContext: elevated + myVal: mine properties: TestString: Hello TestBool: false @@ -581,7 +581,7 @@ resources: - dependency3 metadata: description: FakeDescription2 - securityContext: elevated + myVal: yours properties: TestString: Bye TestBool: true @@ -615,13 +615,13 @@ resources: Assert.Equal("FakeModule/FakeResource", set.Units[0].Type); Assert.Equal("TestId", set.Units[0].Identifier); - this.VerifyValueSet(set.Units[0].Metadata, new ("description", "FakeDescription"), new ("allowPrerelease", true), new ("securityContext", "elevated")); + this.VerifyValueSet(set.Units[0].Metadata, new ("description", "FakeDescription"), new ("allowPrerelease", true), new ("myVal", "mine")); this.VerifyValueSet(set.Units[0].Settings, new ("TestString", "Hello"), new ("TestBool", false), new ("TestInt", 1234)); Assert.Equal("FakeModule2/FakeResource2", set.Units[1].Type); Assert.Equal("TestId2", set.Units[1].Identifier); this.VerifyStringArray(set.Units[1].Dependencies, "TestId", "dependency2", "dependency3"); - this.VerifyValueSet(set.Units[1].Metadata, new ("description", "FakeDescription2"), new ("securityContext", "elevated")); + this.VerifyValueSet(set.Units[1].Metadata, new ("description", "FakeDescription2"), new ("myVal", "yours")); ValueSet mapping = new ValueSet(); mapping.Add("Key", "TestValue"); @@ -768,6 +768,444 @@ resources: } } + /// <summary> + /// Verifies that the configuration set (0.2) with environments parses and serializes. + /// </summary> + [Fact] + public void Environment_0_2() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + resources: + - resource: FakeModule/FakeResource + id: elevated + directives: + description: FakeDescription + allowPrerelease: true + securityContext: elevated + settings: + TestString: Hello + - resource: FakeModule2/FakeResource2 + id: restricted + directives: + description: FakeDescription2 + securityContext: restricted + settings: + TestString: Bye + - resource: FakeModule2/FakeResource2 + id: current + directives: + securityContext: current + settings: + TestString: Bye + - resource: FakeModule2/FakeResource2 + id: default + settings: + TestString: Bye +")); + + Dictionary<string, SecurityContext> expectedEnvironments = new Dictionary<string, SecurityContext>(); + expectedEnvironments.Add("elevated", SecurityContext.Elevated); + expectedEnvironments.Add("restricted", SecurityContext.Restricted); + expectedEnvironments.Add("current", SecurityContext.Current); + expectedEnvironments.Add("default", SecurityContext.Current); + + this.ValidateSecurityContexts(openResult, expectedEnvironments); + + // Shuffle security contexts, serialize, parse and validate again + expectedEnvironments["elevated"] = SecurityContext.Restricted; + expectedEnvironments["restricted"] = SecurityContext.Current; + expectedEnvironments["current"] = SecurityContext.Restricted; + expectedEnvironments["default"] = SecurityContext.Elevated; + + var units = openResult.Set.Units; + foreach (var unit in units) + { + SecurityContext newContext = SecurityContext.Current; + Assert.True(expectedEnvironments.TryGetValue(unit.Identifier, out newContext)); + unit.Environment.Context = newContext; + } + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateSecurityContexts(serializedSetResult, expectedEnvironments); + } + + /// <summary> + /// Verifies that the configuration set (0.3) inherits set environment. + /// </summary> + [Fact] + public void SetMetadataEnvironmentInheritance_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh + properties: + a: b +resources: + - name: first + type: Module/Resource + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' +")); + + Dictionary<string, string> environmentProperties = new Dictionary<string, string>(); + environmentProperties.Add("a", "b"); + + Dictionary<string, ConfigurationEnvironmentData> expectedEnvironments = new Dictionary<string, ConfigurationEnvironmentData>(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }); + + this.ValidateEnvironments(openResult, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, expectedEnvironments); + } + + /// <summary> + /// Verifies that the configuration set (0.3) inherits set environment. + /// </summary> + [Fact] + public void SetMetadataEnvironmentInheritance_ProcessorOverridden_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh + properties: + a: b +resources: + - name: first + type: Module/Resource + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' + metadata: + winget: + processor: not-pwsh +")); + + Dictionary<string, string> environmentProperties = new Dictionary<string, string>(); + environmentProperties.Add("a", "b"); + + Dictionary<string, ConfigurationEnvironmentData> expectedEnvironments = new Dictionary<string, ConfigurationEnvironmentData>(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "not-pwsh" }); + + this.ValidateEnvironments(openResult, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, expectedEnvironments); + } + + /// <summary> + /// Verifies that the configuration set (0.3) inherits set environment. + /// </summary> + [Fact] + public void SetMetadataEnvironmentInheritance_ContextOverridden_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh + properties: + a: b +resources: + - name: first + type: Module/Resource + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' + metadata: + winget: + securityContext: restricted +")); + + Dictionary<string, string> environmentProperties = new Dictionary<string, string>(); + environmentProperties.Add("a", "b"); + + Dictionary<string, ConfigurationEnvironmentData> expectedEnvironments = new Dictionary<string, ConfigurationEnvironmentData>(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }); + + this.ValidateEnvironments(openResult, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, expectedEnvironments); + } + + /// <summary> + /// Verifies that the configuration set (0.3) serializes common environment to the set metadata. + /// </summary> + [Fact] + public void CommonEnvironmentElevatedToSetMetadata_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: first + type: Module/Resource + metadata: + winget: + securityContext: elevated + processor: pwsh + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' + metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh +")); + + Dictionary<string, ConfigurationEnvironmentData> expectedEnvironments = new Dictionary<string, ConfigurationEnvironmentData>(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh" }); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh" }); + + this.ValidateEnvironments(openResult, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, expectedEnvironments); + } + + /// <summary> + /// Verifies that the configuration set (0.3) serializes common environment to the set metadata. + /// </summary> + [Fact] + public void CommonProcessorElevatedToSetMetadata_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: first + type: Module/Resource + metadata: + winget: + securityContext: elevated + processor: pwsh + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' + metadata: + winget: + securityContext: restricted + processor: + identifier: pwsh +")); + + Dictionary<string, ConfigurationEnvironmentData> expectedEnvironments = new Dictionary<string, ConfigurationEnvironmentData>(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh" }); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted, ProcessorIdentifier = "pwsh" }); + + this.ValidateEnvironments(openResult, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, expectedEnvironments); + } + + /// <summary> + /// Verifies that the configuration set (0.3) environments work with group units. + /// </summary> + [Fact] + public void EnvironmentsWithGroups_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh + properties: + a: b +resources: + - name: non-group + type: Module/Resource2 + properties: + l: '10' + - name: group + type: Module/Resource + metadata: + isGroup: true + winget: + securityContext: restricted + properties: + resources: + - name: inherit + type: Module/Resource + properties: + a: b + - name: override + type: Module/Resource + properties: + c: d + metadata: + winget: + processor: not-pwsh +")); + + Dictionary<string, string> environmentProperties = new Dictionary<string, string>(); + environmentProperties.Add("a", "b"); + + Dictionary<string, ConfigurationEnvironmentData> expectedEnvironments = new Dictionary<string, ConfigurationEnvironmentData>(); + expectedEnvironments.Add("non-group", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }); + expectedEnvironments.Add("group", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }); + + Dictionary<string, ConfigurationEnvironmentData> groupExpectedEnvironments = new Dictionary<string, ConfigurationEnvironmentData>(); + groupExpectedEnvironments.Add("inherit", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }); + groupExpectedEnvironments.Add("override", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted, ProcessorIdentifier = "not-pwsh" }); + + this.ValidateEnvironments(openResult, expectedEnvironments, "group", groupExpectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(openResult, expectedEnvironments, "group", groupExpectedEnvironments); + } + + private void ValidateEnvironments(OpenConfigurationSetResult openResult, Dictionary<string, ConfigurationEnvironmentData> expectedEnvironments, string? groupToCheck = null, Dictionary<string, ConfigurationEnvironmentData>? groupExpectedEnvironments = null) + { + Assert.Null(openResult.ResultCode); + Assert.NotNull(openResult.Set); + ConfigurationSet configurationSet = openResult.Set; + + var units = configurationSet.Units; + this.ValidateEnvironments(units, expectedEnvironments, groupToCheck, groupExpectedEnvironments); + } + + private void ValidateEnvironments(IList<ConfigurationUnit> units, Dictionary<string, ConfigurationEnvironmentData> expectedEnvironments, string? groupToCheck = null, Dictionary<string, ConfigurationEnvironmentData>? groupExpectedEnvironments = null) + { + Assert.Equal(expectedEnvironments.Count, units.Count); + foreach (var unit in units) + { + ConfigurationEnvironmentData? expectedEnvironment = null; + Assert.True(expectedEnvironments.TryGetValue(unit.Identifier, out expectedEnvironment)); + Assert.NotNull(expectedEnvironment); + Assert.Equal(expectedEnvironment.Context, unit.Environment.Context); + Assert.Equal(expectedEnvironment.ProcessorIdentifier, unit.Environment.ProcessorIdentifier); + Assert.True(expectedEnvironment.PropertiesEqual(unit.Environment.ProcessorProperties)); + + if (unit.Identifier == groupToCheck) + { + Assert.True(unit.IsGroup); + Assert.NotNull(groupExpectedEnvironments); + var groupUnits = unit.Units; + this.ValidateEnvironments(groupUnits, groupExpectedEnvironments); + } + } + } + + private void ValidateSecurityContexts(OpenConfigurationSetResult openResult, Dictionary<string, SecurityContext> expectedContexts) + { + Assert.Null(openResult.ResultCode); + Assert.NotNull(openResult.Set); + ConfigurationSet configurationSet = openResult.Set; + + var units = configurationSet.Units; + Assert.Equal(expectedContexts.Count, units.Count); + foreach (var unit in units) + { + SecurityContext expectedContext = SecurityContext.Current; + Assert.True(expectedContexts.TryGetValue(unit.Identifier, out expectedContext)); + Assert.Equal(expectedContext, unit.Environment.Context); + } + } + private void TestParameterDefaultValue(string type, string defaultValue, object? expectedValue = null, Windows.Foundation.PropertyType? expectedType = null, bool secure = false) { ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); diff --git a/src/Microsoft.Management.Configuration/ArgumentValidation.cpp b/src/Microsoft.Management.Configuration/ArgumentValidation.cpp @@ -9,38 +9,38 @@ namespace winrt::Microsoft::Management::Configuration::implementation { switch (type) { - case winrt::Windows::Foundation::PropertyType::UInt8: - case winrt::Windows::Foundation::PropertyType::Int16: - case winrt::Windows::Foundation::PropertyType::UInt16: - case winrt::Windows::Foundation::PropertyType::Int32: - case winrt::Windows::Foundation::PropertyType::UInt32: - case winrt::Windows::Foundation::PropertyType::Int64: - case winrt::Windows::Foundation::PropertyType::UInt64: - case winrt::Windows::Foundation::PropertyType::Single: - case winrt::Windows::Foundation::PropertyType::Double: - case winrt::Windows::Foundation::PropertyType::Char16: - case winrt::Windows::Foundation::PropertyType::Boolean: - case winrt::Windows::Foundation::PropertyType::String: - case winrt::Windows::Foundation::PropertyType::Inspectable: - case winrt::Windows::Foundation::PropertyType::DateTime: - case winrt::Windows::Foundation::PropertyType::TimeSpan: - case winrt::Windows::Foundation::PropertyType::Guid: - case winrt::Windows::Foundation::PropertyType::UInt8Array: - case winrt::Windows::Foundation::PropertyType::Int16Array: - case winrt::Windows::Foundation::PropertyType::UInt16Array: - case winrt::Windows::Foundation::PropertyType::Int32Array: - case winrt::Windows::Foundation::PropertyType::UInt32Array: - case winrt::Windows::Foundation::PropertyType::Int64Array: - case winrt::Windows::Foundation::PropertyType::UInt64Array: - case winrt::Windows::Foundation::PropertyType::SingleArray: - case winrt::Windows::Foundation::PropertyType::DoubleArray: - case winrt::Windows::Foundation::PropertyType::Char16Array: - case winrt::Windows::Foundation::PropertyType::BooleanArray: - case winrt::Windows::Foundation::PropertyType::StringArray: - case winrt::Windows::Foundation::PropertyType::InspectableArray: - case winrt::Windows::Foundation::PropertyType::DateTimeArray: - case winrt::Windows::Foundation::PropertyType::TimeSpanArray: - case winrt::Windows::Foundation::PropertyType::GuidArray: + case Windows::Foundation::PropertyType::UInt8: + case Windows::Foundation::PropertyType::Int16: + case Windows::Foundation::PropertyType::UInt16: + case Windows::Foundation::PropertyType::Int32: + case Windows::Foundation::PropertyType::UInt32: + case Windows::Foundation::PropertyType::Int64: + case Windows::Foundation::PropertyType::UInt64: + case Windows::Foundation::PropertyType::Single: + case Windows::Foundation::PropertyType::Double: + case Windows::Foundation::PropertyType::Char16: + case Windows::Foundation::PropertyType::Boolean: + case Windows::Foundation::PropertyType::String: + case Windows::Foundation::PropertyType::Inspectable: + case Windows::Foundation::PropertyType::DateTime: + case Windows::Foundation::PropertyType::TimeSpan: + case Windows::Foundation::PropertyType::Guid: + case Windows::Foundation::PropertyType::UInt8Array: + case Windows::Foundation::PropertyType::Int16Array: + case Windows::Foundation::PropertyType::UInt16Array: + case Windows::Foundation::PropertyType::Int32Array: + case Windows::Foundation::PropertyType::UInt32Array: + case Windows::Foundation::PropertyType::Int64Array: + case Windows::Foundation::PropertyType::UInt64Array: + case Windows::Foundation::PropertyType::SingleArray: + case Windows::Foundation::PropertyType::DoubleArray: + case Windows::Foundation::PropertyType::Char16Array: + case Windows::Foundation::PropertyType::BooleanArray: + case Windows::Foundation::PropertyType::StringArray: + case Windows::Foundation::PropertyType::InspectableArray: + case Windows::Foundation::PropertyType::DateTimeArray: + case Windows::Foundation::PropertyType::TimeSpanArray: + case Windows::Foundation::PropertyType::GuidArray: return; } @@ -135,4 +135,76 @@ namespace winrt::Microsoft::Management::Configuration::implementation { THROW_HR_IF(E_INVALIDARG, !IsLengthType(type)); } + + bool IsStringableType(Windows::Foundation::PropertyType type) + { + switch (type) + { + case Windows::Foundation::PropertyType::UInt8: + case Windows::Foundation::PropertyType::Int16: + case Windows::Foundation::PropertyType::UInt16: + case Windows::Foundation::PropertyType::Int32: + case Windows::Foundation::PropertyType::UInt32: + case Windows::Foundation::PropertyType::Int64: + case Windows::Foundation::PropertyType::UInt64: + case Windows::Foundation::PropertyType::Single: + case Windows::Foundation::PropertyType::Double: + case Windows::Foundation::PropertyType::Char16: + case Windows::Foundation::PropertyType::Boolean: + case Windows::Foundation::PropertyType::String: + return true; + } + + return false; + } + + hstring ToString(Windows::Foundation::IPropertyValue value) + { + Windows::Foundation::PropertyType type = value.Type(); + if (type == Windows::Foundation::PropertyType::String) + { + return value.GetString(); + } + + std::wostringstream stream; + + switch (value.Type()) + { + case Windows::Foundation::PropertyType::UInt8: + stream << value.GetUInt8(); + break; + case Windows::Foundation::PropertyType::Int16: + stream << value.GetInt16(); + break; + case Windows::Foundation::PropertyType::UInt16: + stream << value.GetUInt16(); + break; + case Windows::Foundation::PropertyType::Int32: + stream << value.GetInt32(); + break; + case Windows::Foundation::PropertyType::UInt32: + stream << value.GetUInt32(); + break; + case Windows::Foundation::PropertyType::Int64: + stream << value.GetInt64(); + break; + case Windows::Foundation::PropertyType::UInt64: + stream << value.GetUInt64(); + break; + case Windows::Foundation::PropertyType::Single: + stream << value.GetSingle(); + break; + case Windows::Foundation::PropertyType::Double: + stream << value.GetDouble(); + break; + case Windows::Foundation::PropertyType::Char16: + stream << value.GetChar16(); + break; + case Windows::Foundation::PropertyType::Boolean: + stream << value.GetBoolean() ? L"true" : L"false"; + break; + } + + return hstring{ stream.str() }; + } } diff --git a/src/Microsoft.Management.Configuration/ArgumentValidation.h b/src/Microsoft.Management.Configuration/ArgumentValidation.h @@ -25,4 +25,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Ensures that the given type supports length restrictions. void EnsureLengthType(Windows::Foundation::PropertyType type); + + // Determines if the given type is a scalar that can be converted to a reasonable string representation. + bool IsStringableType(Windows::Foundation::PropertyType type); + + // Gets the string version of the given property, if it is stringable. + hstring ToString(Windows::Foundation::IPropertyValue value); } diff --git a/src/Microsoft.Management.Configuration/ConfigurationEnvironment.cpp b/src/Microsoft.Management.Configuration/ConfigurationEnvironment.cpp @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationEnvironment.h" +#include "ConfigurationEnvironment.g.cpp" +#include "ArgumentValidation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + template <typename T> + void DeepCopyEnvironmentFrom(implementation::ConfigurationEnvironment& self, const T& toDeepCopy) + { + self.Context(toDeepCopy.Context()); + self.ProcessorIdentifier(toDeepCopy.ProcessorIdentifier()); + self.ProcessorProperties(toDeepCopy.ProcessorProperties()); + } + } + + ConfigurationEnvironment::ConfigurationEnvironment() : + m_processorProperties(multi_threaded_map<hstring, hstring>()) + {} + + ConfigurationEnvironment::ConfigurationEnvironment(SecurityContext context, const std::wstring& processorIdentifier, std::map<hstring, hstring>&& processorProperties) : + m_context(context), m_processorIdentifier(processorIdentifier), m_processorProperties(multi_threaded_map(std::move(processorProperties))) + {} + + ConfigurationEnvironment::ConfigurationEnvironment(const implementation::ConfigurationEnvironment& toDeepCopy) + { + DeepCopy(toDeepCopy); + } + + ConfigurationEnvironment::ConfigurationEnvironment(const Configuration::ConfigurationEnvironment& toDeepCopy) + { + DeepCopyEnvironmentFrom(*this, toDeepCopy); + } + + SecurityContext ConfigurationEnvironment::Context() const + { + return m_context; + } + + void ConfigurationEnvironment::Context(SecurityContext value) + { + m_context = value; + } + + hstring ConfigurationEnvironment::ProcessorIdentifier() const + { + return m_processorIdentifier; + } + + void ConfigurationEnvironment::ProcessorIdentifier(const hstring& value) + { + m_processorIdentifier = value; + } + + Windows::Foundation::Collections::IMap<hstring, hstring> ConfigurationEnvironment::ProcessorProperties() const + { + return m_processorProperties; + } + + void ConfigurationEnvironment::DeepCopy(const implementation::ConfigurationEnvironment& toDeepCopy) + { + DeepCopyEnvironmentFrom(*this, toDeepCopy); + } + + void ConfigurationEnvironment::ProcessorProperties(const Windows::Foundation::Collections::IMap<hstring, hstring>& values) + { + std::map<hstring, hstring> properties; + for (const auto& property : values) + { + properties.emplace(property.Key(), property.Value()); + } + m_processorProperties = multi_threaded_map(std::move(properties)); + } + + void ConfigurationEnvironment::ProcessorProperties(const Windows::Foundation::Collections::ValueSet& values) + { + std::map<hstring, hstring> properties; + + for (const auto& value : values) + { + Windows::Foundation::IPropertyValue property = value.Value().try_as<Windows::Foundation::IPropertyValue>(); + if (property && IsStringableType(property.Type())) + { + properties.emplace(value.Key(), ToString(property)); + } + } + + m_processorProperties = multi_threaded_map(std::move(properties)); + } + + bool ConfigurationEnvironment::IsDefault() const + { + return m_context == SecurityContext::Current && m_processorIdentifier.empty() && m_processorProperties.Size() == 0; + } + + com_ptr<ConfigurationEnvironment> ConfigurationEnvironment::CalculateCommonEnvironment(const std::vector<Configuration::ConfigurationEnvironment>& environments) + { + com_ptr<ConfigurationEnvironment> result; + + if (environments.empty()) + { + result = make_self<ConfigurationEnvironment>(); + } + else + { + result = make_self<ConfigurationEnvironment>(environments.front()); + + for (size_t i = 1; i < environments.size(); ++i) + { + const Configuration::ConfigurationEnvironment& environment = environments[i]; + + if (result->m_context != environment.Context()) + { + result->m_context = SecurityContext::Current; + } + + if (result->m_processorIdentifier != environment.ProcessorIdentifier()) + { + result->m_processorIdentifier.clear(); + } + + if (!AreEqual(result->m_processorProperties, environment.ProcessorProperties())) + { + result->m_processorProperties = single_threaded_map<hstring, hstring>(); + } + + // Check if we have already found everything to be different + if (result->IsDefault()) + { + break; + } + } + } + + return result; + } + + bool ConfigurationEnvironment::AreEqual(const Windows::Foundation::Collections::IMap<hstring, hstring>& a, const Windows::Foundation::Collections::IMap<hstring, hstring>& b) + { + uint32_t a_size = a ? a.Size() : 0; + uint32_t b_size = b ? b.Size() : 0; + + if (a_size == 0 && b_size == 0) + { + return true; + } + else if (a_size != b_size) + { + return false; + } + + for (const auto& entry : a) + { + hstring key = entry.Key(); + if (!b.HasKey(key) || entry.Value() != b.Lookup(key)) + { + return false; + } + } + + return true; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationEnvironment.h b/src/Microsoft.Management.Configuration/ConfigurationEnvironment.h @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationEnvironment.g.h" +#include <winrt/Windows.Foundation.Collections.h> +#include <winget/ModuleCountBase.h> + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationEnvironment : ConfigurationEnvironmentT<ConfigurationEnvironment>, AppInstaller::WinRT::ModuleCountBase + { + ConfigurationEnvironment(); + ConfigurationEnvironment(SecurityContext context, const std::wstring& processorIdentifier, std::map<hstring, hstring>&& processorProperties); + ConfigurationEnvironment(const implementation::ConfigurationEnvironment& toDeepCopy); + ConfigurationEnvironment(const Configuration::ConfigurationEnvironment& toDeepCopy); + + SecurityContext Context() const; + void Context(SecurityContext value); + + hstring ProcessorIdentifier() const; + void ProcessorIdentifier(const hstring& value); + + Windows::Foundation::Collections::IMap<hstring, hstring> ProcessorProperties() const; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + // Copies the values from the given environment, including making a new map of the properties. + void DeepCopy(const implementation::ConfigurationEnvironment& toDeepCopy); + + // Copies the properties from the given map. + void ProcessorProperties(const Windows::Foundation::Collections::IMap<hstring, hstring>& values); + + // Copies the scalar properties from the given ValueSet. + // Ignores all values that cannot be converted to a string. + void ProcessorProperties(const Windows::Foundation::Collections::ValueSet& values); + + // Determines if this environment represents the default environment. + bool IsDefault() const; + + // Creates an environment, setting only fields that are identical between all given environments. + static com_ptr<ConfigurationEnvironment> CalculateCommonEnvironment(const std::vector<Configuration::ConfigurationEnvironment>& environments); + + // Checks if the two given properties maps are equal. + static bool AreEqual(const Windows::Foundation::Collections::IMap<hstring, hstring>& a, const Windows::Foundation::Collections::IMap<hstring, hstring>& b); + + private: + SecurityContext m_context = SecurityContext::Current; + hstring m_processorIdentifier; + Windows::Foundation::Collections::IMap<hstring, hstring> m_processorProperties; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.cpp b/src/Microsoft.Management.Configuration/ConfigurationSet.cpp @@ -6,9 +6,69 @@ #include "ConfigurationSetParser.h" #include "ConfigurationSetSerializer.h" #include "Database/ConfigurationDatabase.h" +#include <AppInstallerLanguageUtilities.h> namespace winrt::Microsoft::Management::Configuration::implementation { + namespace impl + { + struct EnvironmentData + { + EnvironmentData(Configuration::ConfigurationEnvironment environment) : + Environment(environment), Context(environment.Context()), Identifier(environment.ProcessorIdentifier()), Properties(environment.ProcessorProperties()) + { + } + + EnvironmentData(EnvironmentData&&) = default; + + bool operator==(const EnvironmentData& other) const + { + return + Context == other.Context && + Identifier == other.Identifier && + ConfigurationEnvironment::AreEqual(Properties, other.Properties); + } + + Configuration::ConfigurationEnvironment Environment; + SecurityContext Context; + hstring Identifier; + Windows::Foundation::Collections::IMap<hstring, hstring> Properties; + }; + + bool ContainsEnvironment(const std::vector<EnvironmentData>& uniqueEnvironments, const EnvironmentData& data) + { + for (const EnvironmentData& item : uniqueEnvironments) + { + if (item == data) + { + return true; + } + } + + return false; + } + + void ComputeUniqueEnvironments(std::vector<EnvironmentData>& uniqueEnvironments, const Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>& units) + { + for (const Configuration::ConfigurationUnit& unit : units) + { + if (unit.IsActive()) + { + EnvironmentData data{ unit.Environment() }; + if (!ContainsEnvironment(uniqueEnvironments, data)) + { + uniqueEnvironments.emplace_back(std::move(data)); + } + + if (unit.IsGroup()) + { + ComputeUniqueEnvironments(uniqueEnvironments, unit.Units()); + } + } + } + } + } + ConfigurationSet::ConfigurationSet() { GUID instanceIdentifier; @@ -223,6 +283,24 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_schemaUri = value; } + std::vector<Configuration::ConfigurationEnvironment> ConfigurationSet::GetUnitEnvironmentsInternal() + { + std::vector<impl::EnvironmentData> uniqueEnvironments; + ComputeUniqueEnvironments(uniqueEnvironments, m_units); + + std::vector<Configuration::ConfigurationEnvironment> result; + for (const impl::EnvironmentData& data : uniqueEnvironments) + { + result.emplace_back(*make_self<implementation::ConfigurationEnvironment>(data.Environment)); + } + return result; + } + + Windows::Foundation::Collections::IVector<Configuration::ConfigurationEnvironment> ConfigurationSet::GetUnitEnvironments() + { + return single_threaded_vector(GetUnitEnvironmentsInternal()); + } + HRESULT STDMETHODCALLTYPE ConfigurationSet::SetLifetimeWatcher(IUnknown* watcher) { return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.h b/src/Microsoft.Management.Configuration/ConfigurationSet.h @@ -25,6 +25,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void Units(std::vector<Configuration::ConfigurationUnit>&& units); void Parameters(std::vector<Configuration::ConfigurationParameter>&& value); void ConfigurationSetChange(com_ptr<ConfigurationSetChangeData>& data, const std::optional<guid>& unitInstanceIdentifier); + std::vector<Configuration::ConfigurationEnvironment> GetUnitEnvironmentsInternal(); #endif hstring Name(); @@ -67,6 +68,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation Windows::Foundation::Uri SchemaUri(); void SchemaUri(const Windows::Foundation::Uri& value); + Windows::Foundation::Collections::IVector<Configuration::ConfigurationEnvironment> GetUnitEnvironments(); + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp @@ -561,4 +561,21 @@ namespace winrt::Microsoft::Management::Configuration::implementation FIELD_VALUE_ERROR(GetConfigurationFieldName(fieldForErrors), node.as<std::string>(), node.Mark()); } } + + void ConfigurationSetParser::ExtractSecurityContext(implementation::ConfigurationUnit* unit, SecurityContext defaultContext) + { + THROW_HR_IF_NULL(E_POINTER, unit); + + SecurityContext computedContext = defaultContext; + + Windows::Foundation::Collections::ValueSet metadata = unit->Metadata(); + auto securityContext = TryLookupProperty(metadata, ConfigurationField::SecurityContextMetadata, Windows::Foundation::PropertyType::String); + if (securityContext) + { + TryParseSecurityContext(securityContext.GetString(), computedContext); + metadata.Remove(GetConfigurationFieldNameHString(ConfigurationField::SecurityContextMetadata)); + } + + unit->EnvironmentInternal().Context(computedContext); + } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser.h @@ -129,6 +129,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Parses an object from the given node, attempting to treat it as the requested type if possible. void ParseObject(const AppInstaller::YAML::Node& node, ConfigurationField fieldForErrors, Windows::Foundation::PropertyType type, Windows::Foundation::IInspectable& result); + // Extracts the security context from the metadata in the given unit; if not present use `defaultContext`. + void ExtractSecurityContext(implementation::ConfigurationUnit* unit, SecurityContext defaultContext = SecurityContext::Current); + private: // Support older schema parsing. static std::unique_ptr<ConfigurationSetParser> GetSchemaVersionFromOldFormat(AppInstaller::YAML::Node& document, std::string& schemaVersionString); diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp @@ -28,5 +28,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation { CHECK_ERROR(ConfigurationSetParser_0_1::ParseConfigurationUnit(unit, unitNode, intent)); ValidateType(unit, unitNode, ConfigurationField::Resource, true, false); + ExtractSecurityContext(unit); } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.cpp @@ -10,20 +10,27 @@ #include <sstream> +using namespace AppInstaller::YAML; +using namespace winrt::Windows::Foundation; + namespace winrt::Microsoft::Management::Configuration::implementation { - using namespace AppInstaller::YAML; + using IInspectable = winrt::Windows::Foundation::IInspectable; void ConfigurationSetParser_0_3::Parse() { auto result = make_self<implementation::ConfigurationSet>(); CHECK_ERROR(ParseValueSet(m_document, ConfigurationField::Metadata, false, result->Metadata())); + + m_setEnvironment = make_self<implementation::ConfigurationEnvironment>(); + CHECK_ERROR(ExtractEnvironmentFromMetadata(result->Metadata(), *m_setEnvironment)); + CHECK_ERROR(ParseParameters(result)); CHECK_ERROR(ParseValueSet(m_document, ConfigurationField::Variables, false, result->Variables())); std::vector<Configuration::ConfigurationUnit> units; - CHECK_ERROR(ParseConfigurationUnitsFromField(m_document, ConfigurationField::Resources, units)); + CHECK_ERROR(ParseConfigurationUnitsFromField(m_document, ConfigurationField::Resources, *m_setEnvironment, units)); result->Units(std::move(units)); result->SchemaVersion(GetSchemaVersion()); @@ -62,14 +69,14 @@ namespace winrt::Microsoft::Management::Configuration::implementation CHECK_ERROR(ParseValueSet(node, ConfigurationField::Metadata, false, parameter->Metadata())); CHECK_ERROR(GetStringValueForParameter(node, ConfigurationField::Description, parameter, &ConfigurationParameter::Description)); - Windows::Foundation::PropertyType parameterType = parameter->Type(); + PropertyType parameterType = parameter->Type(); CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::DefaultValue, parameterType, parameter, &ConfigurationParameter::DefaultValue)); - std::vector<Windows::Foundation::IInspectable> allowedValues; + std::vector<IInspectable> allowedValues; CHECK_ERROR(ParseSequence(node, ConfigurationField::AllowedValues, false, std::nullopt, [&](const Node& item) { - Windows::Foundation::IInspectable object; + IInspectable object; CHECK_ERROR(ParseObject(item, ConfigurationField::AllowedValues, parameterType, object)); allowedValues.emplace_back(std::move(object)); })); @@ -155,32 +162,32 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetParser_0_3::ParseObjectValueForParameter( const AppInstaller::YAML::Node& node, ConfigurationField field, - Windows::Foundation::PropertyType type, + PropertyType type, ConfigurationParameter* parameter, - void(ConfigurationParameter::* propertyFunction)(const Windows::Foundation::IInspectable& value)) + void(ConfigurationParameter::* propertyFunction)(const IInspectable& value)) { const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, false, std::nullopt)); if (valueNode) { - Windows::Foundation::IInspectable valueObject; + IInspectable valueObject; CHECK_ERROR(ParseObject(valueNode, field, type, valueObject)); (parameter->*propertyFunction)(valueObject); } } - void ConfigurationSetParser_0_3::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, std::vector<Configuration::ConfigurationUnit>& result) + void ConfigurationSetParser_0_3::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, const ConfigurationEnvironment& defaultEnvironment, std::vector<Configuration::ConfigurationUnit>& result) { ParseSequence(document, field, false, Node::Type::Mapping, [&](const Node& item) { auto configurationUnit = make_self<ConfigurationUnit>(); - ParseConfigurationUnit(configurationUnit.get(), item); + ParseConfigurationUnit(configurationUnit.get(), item, defaultEnvironment); result.emplace_back(*configurationUnit); }); } - void ConfigurationSetParser_0_3::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode) + void ConfigurationSetParser_0_3::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode, const ConfigurationEnvironment& defaultEnvironment) { // Set unknown intent as the new schema doesn't express it directly unit->Intent(ConfigurationUnitIntent::Unknown); @@ -188,6 +195,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Name, true, unit, &ConfigurationUnit::Identifier)); CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Type, true, unit, &ConfigurationUnit::Type)); CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Metadata, false, unit->Metadata())); + CHECK_ERROR(ExtractEnvironmentForUnit(unit, defaultEnvironment)); CHECK_ERROR(ValidateType(unit, unitNode, ConfigurationField::Type, false, true)); CHECK_ERROR(GetStringArrayForUnit(unitNode, ConfigurationField::DependsOn, false, unit, &ConfigurationUnit::Dependencies)); @@ -204,7 +212,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (propertiesNode) { std::vector<Configuration::ConfigurationUnit> units; - CHECK_ERROR(ParseConfigurationUnitsFromField(propertiesNode, ConfigurationField::Resources, units)); + CHECK_ERROR(ParseConfigurationUnitsFromField(propertiesNode, ConfigurationField::Resources, unit->EnvironmentInternal(), units)); unit->Units(std::move(units)); } } @@ -216,8 +224,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation auto isGroupObject = unit->Metadata().TryLookup(GetConfigurationFieldNameHString(ConfigurationField::IsGroupMetadata)); if (isGroupObject) { - auto isGroupProperty = isGroupObject.try_as<Windows::Foundation::IPropertyValue>(); - if (isGroupProperty && isGroupProperty.Type() == Windows::Foundation::PropertyType::Boolean) + auto isGroupProperty = isGroupObject.try_as<IPropertyValue>(); + if (isGroupProperty && isGroupProperty.Type() == PropertyType::Boolean) { return isGroupProperty.GetBoolean(); } @@ -228,56 +236,126 @@ namespace winrt::Microsoft::Management::Configuration::implementation return false; } - std::optional<std::pair<Windows::Foundation::PropertyType, bool>> ParseWindowsFoundationPropertyType(std::string_view value) + void ConfigurationSetParser_0_3::ExtractEnvironmentFromMetadata(const Collections::ValueSet& metadata, ConfigurationEnvironment& targetEnvironment) + { + auto root = TryLookupValueSet(metadata, ConfigurationField::WingetMetadataRoot); + if (root) + { + // Get security context + auto securityContext = TryLookupProperty(root, ConfigurationField::SecurityContextMetadata, PropertyType::String); + if (securityContext) + { + SecurityContext computedContext = SecurityContext::Current; + if (TryParseSecurityContext(securityContext.GetString(), computedContext)) + { + targetEnvironment.Context(computedContext); + } + root.Remove(GetConfigurationFieldNameHString(ConfigurationField::SecurityContextMetadata)); + } + + // Get processor + hstring processorFieldName = GetConfigurationFieldNameHString(ConfigurationField::ProcessorMetadata); + IInspectable processor = root.TryLookup(processorFieldName); + Collections::ValueSet processorValueSet = processor.try_as<Collections::ValueSet>(); + if (processorValueSet) + { + targetEnvironment.ProcessorIdentifier({}); + targetEnvironment.ProcessorProperties().Clear(); + + IPropertyValue identifier = TryLookupProperty(processorValueSet, ConfigurationField::ProcessorIdentifierMetadata, PropertyType::String); + if (identifier) + { + targetEnvironment.ProcessorIdentifier(identifier.GetString()); + + Collections::ValueSet processorSettings = TryLookupValueSet(processorValueSet, ConfigurationField::ProcessorPropertiesMetadata); + if (processorSettings) + { + targetEnvironment.ProcessorProperties(processorSettings); + } + } + + root.Remove(processorFieldName); + } + else + { + IPropertyValue processorProperty = processor.try_as<IPropertyValue>(); + if (processorProperty) + { + targetEnvironment.ProcessorIdentifier(processorProperty.GetString()); + targetEnvironment.ProcessorProperties().Clear(); + root.Remove(processorFieldName); + } + } + + if (root.Size() == 0) + { + metadata.Remove(GetConfigurationFieldNameHString(ConfigurationField::WingetMetadataRoot)); + } + } + } + + void ConfigurationSetParser_0_3::ExtractEnvironmentForUnit(ConfigurationUnit* unit, const ConfigurationEnvironment& defaultEnvironment) + { + auto& environmentInternal = unit->EnvironmentInternal(); + environmentInternal.DeepCopy(defaultEnvironment); + + // Get unnested security context + ExtractSecurityContext(unit, defaultEnvironment.Context()); + + // Get nested environment + ExtractEnvironmentFromMetadata(unit->Metadata(), unit->EnvironmentInternal()); + } + + std::optional<std::pair<PropertyType, bool>> ParseWindowsFoundationPropertyType(std::string_view value) { if (value == "string") { - return std::make_pair(Windows::Foundation::PropertyType::String, false); + return std::make_pair(PropertyType::String, false); } else if (value == "securestring") { - return std::make_pair(Windows::Foundation::PropertyType::String, true); + return std::make_pair(PropertyType::String, true); } else if (value == "int") { - return std::make_pair(Windows::Foundation::PropertyType::Int64, false); + return std::make_pair(PropertyType::Int64, false); } else if (value == "bool") { - return std::make_pair(Windows::Foundation::PropertyType::Boolean, false); + return std::make_pair(PropertyType::Boolean, false); } else if (value == "object") { - return std::make_pair(Windows::Foundation::PropertyType::Inspectable, false); + return std::make_pair(PropertyType::Inspectable, false); } else if (value == "secureobject") { - return std::make_pair(Windows::Foundation::PropertyType::Inspectable, true); + return std::make_pair(PropertyType::Inspectable, true); } else if (value == "array") { - return std::make_pair(Windows::Foundation::PropertyType::InspectableArray, false); + return std::make_pair(PropertyType::InspectableArray, false); } // TODO: Consider supporting an expanded set of type strings return std::nullopt; } - std::string_view ToString(Windows::Foundation::PropertyType value, bool isSecure) + std::string_view ToString(PropertyType value, bool isSecure) { switch (value) { - case Windows::Foundation::PropertyType::Int16: - case Windows::Foundation::PropertyType::Int32: - case Windows::Foundation::PropertyType::Int64: + case PropertyType::Int16: + case PropertyType::Int32: + case PropertyType::Int64: return "int"sv; - case Windows::Foundation::PropertyType::Boolean: + case PropertyType::Boolean: return "bool"sv; - case Windows::Foundation::PropertyType::String: + case PropertyType::String: return isSecure ? "securestring"sv : "string"sv; - case Windows::Foundation::PropertyType::Inspectable: + case PropertyType::Inspectable: return isSecure ? "secureobject"sv : "object"sv; - case Windows::Foundation::PropertyType::InspectableArray: + case PropertyType::InspectableArray: return "array"sv; default: return {}; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h @@ -2,7 +2,8 @@ // Licensed under the MIT License. #pragma once #include "ConfigurationSetParser.h" -#include <ConfigurationParameter.h> +#include "ConfigurationParameter.h" +#include <winrt/Microsoft.Management.Configuration.h> #include <winget/Yaml.h> #include <optional> @@ -52,12 +53,20 @@ namespace winrt::Microsoft::Management::Configuration::implementation ConfigurationParameter* parameter, void(ConfigurationParameter::* propertyFunction)(const Windows::Foundation::IInspectable& value)); - void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, std::vector<Configuration::ConfigurationUnit>& result); - virtual void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode); + void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, const ConfigurationEnvironment& defaultEnvironment, std::vector<Configuration::ConfigurationUnit>& result); + virtual void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, const ConfigurationEnvironment& defaultEnvironment); // Determines if the given unit should be converted to a group. bool ShouldConvertToGroup(ConfigurationUnit* unit); + // Extracts the environment configuration from the given metadata. + // This only examines the winget subnode. + void ExtractEnvironmentFromMetadata(const Windows::Foundation::Collections::ValueSet& metadata, ConfigurationEnvironment& targetEnvironment); + + // Extracts the environment for a unit. + void ExtractEnvironmentForUnit(ConfigurationUnit* unit, const ConfigurationEnvironment& defaultEnvironment); + AppInstaller::YAML::Node m_document; + com_ptr<ConfigurationEnvironment> m_setEnvironment; }; std::optional<std::pair<Windows::Foundation::PropertyType, bool>> ParseWindowsFoundationPropertyType(std::string_view value); diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp @@ -20,6 +20,95 @@ namespace winrt::Microsoft::Management::Configuration::implementation namespace anon { static constexpr std::string_view s_nullValue = "null"; + + struct ValueSetWriter + { + ValueSetWriter(const Windows::Foundation::Collections::ValueSet& valueSet, const std::vector<std::pair<ConfigurationField, Windows::Foundation::IInspectable>>& overrides) : + m_valueSet(valueSet), m_overrides(overrides) + { + // Create a sorted list of the field names to exclude + for (const auto & override : m_overrides) + { + m_exclusionStrings.push_back(GetConfigurationFieldNameHString(override.first)); + } + std::sort(m_exclusionStrings.begin(), m_exclusionStrings.end()); + } + + bool IsResultEmpty() + { + size_t nullOverrides = 0; + + for (const auto& override : m_overrides) + { + // A non-null override will always be output + if (override.second) + { + return false; + } + else + { + ++nullOverrides; + } + } + + if (m_valueSet) + { + // If there are more values than null overrides, something will be output + if (static_cast<size_t>(m_valueSet.Size()) > nullOverrides) + { + return false; + } + + // Check for a value that we would output + for (const auto& [key, value] : m_valueSet) + { + if (value != nullptr && + !std::binary_search(m_exclusionStrings.begin(), m_exclusionStrings.end(), key)) + { + return false; + } + } + } + + return true; + } + + void Write(AppInstaller::YAML::Emitter& emitter, void(* WriteYamlValue)(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value)) + { + emitter << BeginMap; + + if (m_valueSet) + { + for (const auto& [key, value] : m_valueSet) + { + if (value != nullptr && + !std::binary_search(m_exclusionStrings.begin(), m_exclusionStrings.end(), key)) + { + std::string keyName = winrt::to_string(key); + emitter << Key << keyName << Value; + WriteYamlValue(emitter, value); + } + } + } + + for (const auto & override : m_overrides) + { + if (override.second != nullptr) + { + std::string_view keyName = GetConfigurationFieldName(override.first); + emitter << Key << keyName << Value; + WriteYamlValue(emitter, override.second); + } + } + + emitter << EndMap; + } + + private: + const Windows::Foundation::Collections::ValueSet& m_valueSet; + const std::vector<std::pair<ConfigurationField, Windows::Foundation::IInspectable>>& m_overrides; + std::vector<winrt::hstring> m_exclusionStrings; + }; } std::unique_ptr<ConfigurationSetSerializer> ConfigurationSetSerializer::CreateSerializer(hstring version, bool strictVersionMatching) @@ -65,39 +154,21 @@ namespace winrt::Microsoft::Management::Configuration::implementation return emitter.str(); } - void ConfigurationSetSerializer::WriteYamlValueSetIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const Windows::Foundation::Collections::ValueSet& valueSet) + void ConfigurationSetSerializer::WriteYamlValueSetIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const Windows::Foundation::Collections::ValueSet& valueSet, const std::vector<std::pair<ConfigurationField, Windows::Foundation::IInspectable>>& overrides) { - if (valueSet && valueSet.Size() != 0) + anon::ValueSetWriter writer{ valueSet, overrides }; + + if (!writer.IsResultEmpty()) { emitter << Key << GetConfigurationFieldName(key); - WriteYamlValueSet(emitter, valueSet); + writer.Write(emitter, WriteYamlValue); } } - void ConfigurationSetSerializer::WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, std::initializer_list<ConfigurationField> exclusions) + void ConfigurationSetSerializer::WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const std::vector<std::pair<ConfigurationField, Windows::Foundation::IInspectable>>& overrides) { - // Create a sorted list of the field names to exclude - std::vector<winrt::hstring> exclusionStrings; - for (ConfigurationField field : exclusions) - { - exclusionStrings.emplace_back(GetConfigurationFieldNameHString(field)); - } - std::sort(exclusionStrings.begin(), exclusionStrings.end()); - - emitter << BeginMap; - - for (const auto& [key, value] : valueSet) - { - if (value != nullptr && - !std::binary_search(exclusionStrings.begin(), exclusionStrings.end(), key)) - { - std::string keyName = winrt::to_string(key); - emitter << Key << keyName << Value; - WriteYamlValue(emitter, value); - } - } - - emitter << EndMap; + anon::ValueSetWriter writer{ valueSet, overrides }; + writer.Write(emitter, WriteYamlValue); } void ConfigurationSetSerializer::WriteYamlStringArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector<hstring>& values) diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.h b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.h @@ -8,6 +8,7 @@ #include <winget/Yaml.h> #include <initializer_list> #include <string_view> +#include <utility> namespace winrt::Microsoft::Management::Configuration::implementation { @@ -34,15 +35,15 @@ namespace winrt::Microsoft::Management::Configuration::implementation protected: ConfigurationSetSerializer() = default; - void WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, std::initializer_list<ConfigurationField> exclusions = {}); - void WriteYamlValueSetIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const Windows::Foundation::Collections::ValueSet& valueSet); - void WriteYamlValueSetAsArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSetArray); + static void WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const std::vector<std::pair<ConfigurationField, Windows::Foundation::IInspectable>>& overrides = {}); + static void WriteYamlValueSetIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const Windows::Foundation::Collections::ValueSet& valueSet, const std::vector<std::pair<ConfigurationField, Windows::Foundation::IInspectable>>& overrides = {}); + static void WriteYamlValueSetAsArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSetArray); - void WriteYamlStringArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector<hstring>& values); + static void WriteYamlStringArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector<hstring>& values); - void WriteYamlValue(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value); - void WriteYamlValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const winrt::Windows::Foundation::IInspectable& value); - void WriteYamlStringValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, hstring value); + static void WriteYamlValue(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value); + static void WriteYamlValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const winrt::Windows::Foundation::IInspectable& value); + static void WriteYamlStringValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, hstring value); std::wstring_view GetSchemaVersionCommentPrefix(); }; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp @@ -122,7 +122,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetSerializer_0_2::WriteResourceDirectives(AppInstaller::YAML::Emitter& emitter, const ConfigurationUnit& unit) { - emitter << Key << GetConfigurationFieldName(ConfigurationField::Directives); - WriteYamlValueSet(emitter, unit.Metadata(), { ConfigurationField::ModuleDirective }); + SecurityContext securityContext = unit.Environment().Context(); + + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Directives, unit.Metadata(), + { { ConfigurationField::ModuleDirective, nullptr }, + { ConfigurationField::SecurityContextMetadata, (securityContext != SecurityContext::Current ? PropertyValue::CreateString(ToWString(securityContext)) : nullptr)} }); } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.cpp @@ -5,6 +5,7 @@ #include "ArgumentValidation.h" #include "ConfigurationSetParser_0_3.h" #include "ConfigurationSetUtilities.h" +#include "ConfigurationEnvironment.h" #include <AppInstallerErrors.h> #include <AppInstallerStrings.h> @@ -14,6 +15,112 @@ using namespace winrt::Windows::Foundation; namespace winrt::Microsoft::Management::Configuration::implementation { + namespace + { + Windows::Foundation::Collections::ValueSet GetWingetProcessorMetadataValueSet(Windows::Foundation::Collections::ValueSet& metadata) + { + Windows::Foundation::Collections::ValueSet result = nullptr; + hstring processorMetadataKey = GetConfigurationFieldNameHString(ConfigurationField::ProcessorMetadata); + + if (metadata) + { + Windows::Foundation::IInspectable processorMetadataObject = metadata.TryLookup(processorMetadataKey); + if (processorMetadataObject) + { + result = processorMetadataObject.try_as<Windows::Foundation::Collections::ValueSet>(); + THROW_HR_IF(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, !result); + } + } + else + { + metadata = Collections::ValueSet{}; + } + + if (!result) + { + result = Collections::ValueSet{}; + metadata.Insert(processorMetadataKey, result); + } + + return result; + } + + Windows::Foundation::Collections::ValueSet CreateValueSetFromStringMap(const Windows::Foundation::Collections::IMap<hstring, hstring>& map) + { + Windows::Foundation::Collections::ValueSet result; + if (map) + { + for (const auto& item : map) + { + result.Insert(item.Key(), PropertyValue::CreateString(item.Value())); + } + } + return result; + } + + void AddEnvironmentToMetadata( + Windows::Foundation::Collections::ValueSet& metadata, + SecurityContext context, + hstring processor, + Windows::Foundation::Collections::IMap<hstring, hstring> properties, + SecurityContext defaultContext = SecurityContext::Current, + hstring defaultProcessor = {}, + Windows::Foundation::Collections::IMap<hstring, hstring> defaultProperties = nullptr) + { + if (context != defaultContext) + { + if (!metadata) + { + metadata = Collections::ValueSet{}; + } + + metadata.Insert(GetConfigurationFieldNameHString(ConfigurationField::SecurityContextMetadata), PropertyValue::CreateString(ToWString(context))); + } + + Windows::Foundation::Collections::ValueSet processorValueSet{ nullptr }; + + if (processor != defaultProcessor) + { + if (!processorValueSet) + { + processorValueSet = GetWingetProcessorMetadataValueSet(metadata); + } + + processorValueSet.Insert(GetConfigurationFieldNameHString(ConfigurationField::ProcessorIdentifierMetadata), PropertyValue::CreateString(processor)); + } + + if (!ConfigurationEnvironment::AreEqual(properties, defaultProperties)) + { + if (!processorValueSet) + { + processorValueSet = GetWingetProcessorMetadataValueSet(metadata); + } + + processorValueSet.Insert(GetConfigurationFieldNameHString(ConfigurationField::ProcessorPropertiesMetadata), CreateValueSetFromStringMap(properties)); + } + } + + void AddEnvironmentToMetadata( + Windows::Foundation::Collections::ValueSet& metadata, + const com_ptr<implementation::ConfigurationEnvironment>& environment) + { + if (environment) + { + AddEnvironmentToMetadata(metadata, environment->Context(), environment->ProcessorIdentifier(), environment->ProcessorProperties()); + } + } + + void AddEnvironmentToMetadata( + Windows::Foundation::Collections::ValueSet& metadata, + const Configuration::ConfigurationEnvironment& environment, + const Configuration::ConfigurationEnvironment& commonEnvironment) + { + AddEnvironmentToMetadata(metadata, + environment.Context(), environment.ProcessorIdentifier(), environment.ProcessorProperties(), + commonEnvironment.Context(), commonEnvironment.ProcessorIdentifier(), commonEnvironment.ProcessorProperties()); + } + } + hstring ConfigurationSetSerializer_0_3::Serialize(ConfigurationSet* configurationSet) { Emitter emitter; @@ -22,10 +129,20 @@ namespace winrt::Microsoft::Management::Configuration::implementation emitter << Key << GetConfigurationFieldName(ConfigurationField::Schema) << Value << ConvertToUTF8(configurationSet->SchemaUri().ToString()); - WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, configurationSet->Metadata()); + // Prepare an override if necessary + Collections::ValueSet wingetMetadataOverride = nullptr; + auto commonEnvironment = ConfigurationEnvironment::CalculateCommonEnvironment(configurationSet->GetUnitEnvironmentsInternal()); + AddEnvironmentToMetadata(wingetMetadataOverride, commonEnvironment); + + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, configurationSet->Metadata(), + { + { ConfigurationField::WingetMetadataRoot, wingetMetadataOverride }, + { ConfigurationField::SecurityContextMetadata, nullptr }, + }); + WriteYamlParameters(emitter, configurationSet->Parameters()); WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Variables, configurationSet->Variables()); - WriteYamlConfigurationUnits(emitter, configurationSet->Units()); + WriteYamlConfigurationUnits(emitter, configurationSet->Units(), *commonEnvironment); emitter << EndMap; @@ -101,7 +218,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation emitter << EndMap; } - void ConfigurationSetSerializer_0_3::WriteYamlConfigurationUnits(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>& values) + void ConfigurationSetSerializer_0_3::WriteYamlConfigurationUnits( + AppInstaller::YAML::Emitter& emitter, + const Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>& values, + const Configuration::ConfigurationEnvironment& commonEnvironment) { emitter << Key << GetConfigurationFieldName(ConfigurationField::Resources); @@ -119,7 +239,13 @@ namespace winrt::Microsoft::Management::Configuration::implementation THROW_HR_IF(WINGET_CONFIG_ERROR_MISSING_FIELD, type.empty()); emitter << Key << GetConfigurationFieldName(ConfigurationField::Type) << Value << ConvertToUTF8(type); - WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, unit.Metadata()); + // Prepare an override if necessary + Collections::ValueSet wingetMetadataOverride = nullptr; + Configuration::ConfigurationEnvironment unitEnvironment = unit.Environment(); + AddEnvironmentToMetadata(wingetMetadataOverride, unitEnvironment, commonEnvironment); + + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, unit.Metadata(), + { { ConfigurationField::WingetMetadataRoot, wingetMetadataOverride } }); auto dependencies = unit.Dependencies(); if (dependencies && dependencies.Size() != 0) @@ -136,7 +262,25 @@ namespace winrt::Microsoft::Management::Configuration::implementation emitter << EndSeq; } - WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Properties, unit.Settings()); + // If this unit is a group, write the units directly + if (unit.IsGroup()) + { + auto groupUnits = unit.Units(); + + if (groupUnits.Size() != 0) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::Properties); + emitter << BeginMap; + + WriteYamlConfigurationUnits(emitter, groupUnits, unitEnvironment); + + emitter << EndMap; + } + } + else + { + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Properties, unit.Settings()); + } emitter << EndMap; } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.h b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include "ConfigurationSetSerializer.h" +#include "ConfigurationEnvironment.h" namespace winrt::Microsoft::Management::Configuration::implementation { @@ -21,6 +22,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation protected: void WriteYamlParameters(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector<Configuration::ConfigurationParameter>& values); - void WriteYamlConfigurationUnits(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>& values); + void WriteYamlConfigurationUnits( + AppInstaller::YAML::Emitter& emitter, + const Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>& values, + const Configuration::ConfigurationEnvironment& commonEnvironment); }; } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.cpp @@ -23,6 +23,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation case ConfigurationField::Resources: return "resources"sv; case ConfigurationField::ModuleDirective: return "module"sv; + case ConfigurationField::SecurityContextMetadata: return "securityContext"sv; case ConfigurationField::Schema: return "$schema"sv; case ConfigurationField::Metadata: return "metadata"sv; @@ -38,6 +39,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation case ConfigurationField::MaximumLength: return "maxLength"sv; case ConfigurationField::MinimumValue: return "minValue"sv; case ConfigurationField::MaximumValue: return "maxValue"sv; + case ConfigurationField::WingetMetadataRoot: return "winget"sv; + case ConfigurationField::ProcessorMetadata: return "processor"sv; + case ConfigurationField::ProcessorIdentifierMetadata: return "identifier"sv; + case ConfigurationField::ProcessorPropertiesMetadata: return "properties"sv; } THROW_HR(E_UNEXPECTED); @@ -47,4 +52,87 @@ namespace winrt::Microsoft::Management::Configuration::implementation { return hstring{ AppInstaller::Utility::ConvertToUTF16(GetConfigurationFieldName(fieldName)) }; } + + bool TryParseSecurityContext(const hstring& value, SecurityContext& result) + { + std::wstring securityContextLower = AppInstaller::Utility::ToLower(value); + + if (securityContextLower == L"elevated") + { + result = SecurityContext::Elevated; + } + else if (securityContextLower == L"restricted") + { + result = SecurityContext::Restricted; + } + else if (securityContextLower == L"current") + { + result = SecurityContext::Current; + } + else + { + return false; + } + + return true; + } + + SecurityContext ParseSecurityContext(const hstring& value) + { + SecurityContext result = SecurityContext::Current; + THROW_HR_IF(E_INVALIDARG, !TryParseSecurityContext(value, result)); + return result; + } + + std::string_view ToString(SecurityContext value) + { + switch (value) + { + case SecurityContext::Current: return "current"; + case SecurityContext::Restricted: return "restricted"; + case SecurityContext::Elevated: return "elevated"; + } + + THROW_HR(E_INVALIDARG); + } + + std::wstring_view ToWString(SecurityContext value) + { + switch (value) + { + case SecurityContext::Current: return L"current"; + case SecurityContext::Restricted: return L"restricted"; + case SecurityContext::Elevated: return L"elevated"; + } + + THROW_HR(E_INVALIDARG); + } + + Windows::Foundation::Collections::ValueSet TryLookupValueSet(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field) + { + Windows::Foundation::IInspectable value = valueSet.TryLookup(GetConfigurationFieldNameHString(field)); + + if (value) + { + return value.try_as<Windows::Foundation::Collections::ValueSet>(); + } + + return nullptr; + } + + Windows::Foundation::IPropertyValue TryLookupProperty(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field, Windows::Foundation::PropertyType type) + { + Windows::Foundation::IInspectable value = valueSet.TryLookup(GetConfigurationFieldNameHString(field)); + + if (value) + { + Windows::Foundation::IPropertyValue property = value.try_as<Windows::Foundation::IPropertyValue>(); + if (property && (type == Windows::Foundation::PropertyType::Empty || property.Type() == type)) + { + return property; + } + } + + return nullptr; + } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.h b/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.h @@ -2,7 +2,7 @@ // Licensed under the MIT License. #pragma once #include <string_view> -#include <winrt/base.h> +#include <winrt/Microsoft.Management.Configuration.h> namespace winrt::Microsoft::Management::Configuration::implementation { @@ -22,6 +22,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Universal Resources, ModuleDirective, + SecurityContextMetadata, // v0.3 Schema, @@ -38,10 +39,33 @@ namespace winrt::Microsoft::Management::Configuration::implementation MaximumLength, MinimumValue, MaximumValue, + WingetMetadataRoot, + ProcessorMetadata, + ProcessorIdentifierMetadata, + ProcessorPropertiesMetadata, }; // Gets the name value of the configuration field. std::string_view GetConfigurationFieldName(ConfigurationField fieldName); winrt::hstring GetConfigurationFieldNameHString(ConfigurationField fieldName); + + // Attempts to parse a security context from a string. + // Returns true if successful; false otherwise. + bool TryParseSecurityContext(const hstring& value, SecurityContext& result); + + // Parses a security context from a string. + SecurityContext ParseSecurityContext(const hstring& value); + + // Gets the string representation of a security context. + std::string_view ToString(SecurityContext value); + + // Gets the string representation of a security context. + std::wstring_view ToWString(SecurityContext value); + + // Tries to get the field value from the given value set; only if it is a value set. + Windows::Foundation::Collections::ValueSet TryLookupValueSet(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field); + + // Tries to get the field value from the given value set; only if it is a value set. + Windows::Foundation::IPropertyValue TryLookupProperty(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field, Windows::Foundation::PropertyType type = Windows::Foundation::PropertyType::Empty); } diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp @@ -168,6 +168,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation result->m_metadata = Clone(m_metadata); result->m_settings = Clone(m_settings); result->m_details = m_details; + result->m_environment = make_self<implementation::ConfigurationEnvironment>(*m_environment); return *result; } @@ -214,6 +215,16 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_units = winrt::multi_threaded_vector<Configuration::ConfigurationUnit>(std::move(value)); } + Configuration::ConfigurationEnvironment ConfigurationUnit::Environment() + { + return *m_environment; + } + + implementation::ConfigurationEnvironment& ConfigurationUnit::EnvironmentInternal() + { + return *m_environment; + } + HRESULT STDMETHODCALLTYPE ConfigurationUnit::SetLifetimeWatcher(IUnknown* watcher) { return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.h b/src/Microsoft.Management.Configuration/ConfigurationUnit.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include "ConfigurationUnit.g.h" +#include "ConfigurationEnvironment.h" #include <winget/ILifetimeWatcher.h> #include <winget/ModuleCountBase.h> #include <winrt/Windows.Foundation.Collections.h> @@ -15,6 +16,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) ConfigurationUnit(const guid& instanceIdentifier); + + implementation::ConfigurationEnvironment& EnvironmentInternal(); #endif hstring Type(); @@ -54,6 +57,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit> Units(); void Units(const Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>& value); + Configuration::ConfigurationEnvironment Environment(); + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) @@ -73,6 +78,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation bool m_isActive = true; bool m_isGroup = false; Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit> m_units = nullptr; + com_ptr<implementation::ConfigurationEnvironment> m_environment{ make_self<implementation::ConfigurationEnvironment>() }; #endif }; } diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Configuration { - [contractversion(2)] + [contractversion(3)] apicontract Contract{}; // The current state of a configuration set. @@ -74,6 +74,32 @@ namespace Microsoft.Management.Configuration Precondition, }; + // A security context; typically used to define the context in which a configuration unit should be processed. + [contract(Microsoft.Management.Configuration.Contract, 3)] + enum SecurityContext + { + // The default value; indicates that the configuration unit should be processed at the caller's current level. + Current, + // A standard user without administrator privileges. + Restricted, + // A user with administrator privileges. + Elevated, + }; + + // Information about the environment in which a configuration is processed. + [contract(Microsoft.Management.Configuration.Contract, 3)] + runtimeclass ConfigurationEnvironment + { + // The security context in which a configuration is processed. + SecurityContext Context; + + // A string that identifies the processor that should be used for the configuration. + String ProcessorIdentifier; + + // Processor specific properties. + Windows.Foundation.Collections.IMap<String, String> ProcessorProperties{ get; }; + }; + // Information on a result for a single unit of configuration. [contract(Microsoft.Management.Configuration.Contract, 1)] interface IConfigurationUnitResultInformation @@ -245,6 +271,8 @@ namespace Microsoft.Management.Configuration // Dependencies, Metadata, and Settings will by new containers with identical values inside // Details will be the same value (not a copy, just another reference) // State, ResultInformation, and ShouldApply will be their default constructed state + // IsGroup will be false and child units will not be copied + // Environment will be copied ConfigurationUnit Copy(); [contract(Microsoft.Management.Configuration.Contract, 2)] @@ -256,6 +284,14 @@ namespace Microsoft.Management.Configuration // The configuration units that are part of this unit (if IsGroup is true). Windows.Foundation.Collections.IVector<ConfigurationUnit> Units; } + + [contract(Microsoft.Management.Configuration.Contract, 3)] + { + // The environment in which to process the configuration unit. + // This defines the initial processing environment state used by the configuration system, + // and may be overridden by the processor later. + ConfigurationEnvironment Environment{ get; }; + } } // The change event type that has occurred for a configuration set change. @@ -393,6 +429,12 @@ namespace Microsoft.Management.Configuration // Setting SchemaUri to a different value will change SchemaVersion. Windows.Foundation.Uri SchemaUri; } + + [contract(Microsoft.Management.Configuration.Contract, 3)] + { + // Gets the union of environments as defined by all of the active units within the set. + Windows.Foundation.Collections.IVector<ConfigurationEnvironment> GetUnitEnvironments(); + } } // The result of applying the settings with an IConfigurationUnitProcessor. @@ -954,6 +996,7 @@ namespace Microsoft.Management.Configuration interface Windows.Foundation.Collections.IVectorView<ApplyConfigurationUnitResult>; interface Windows.Foundation.Collections.IVectorView<TestConfigurationUnitResult>; interface Windows.Foundation.Collections.IVectorView<ConfigurationSet>; + interface Windows.Foundation.Collections.IVector<ConfigurationEnvironment>; } // Provides a way to centralize the distribution of interfaces relevant to specific implementations of IConfigurationSetProcessorFactory. diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -205,6 +205,7 @@ <ClInclude Include="ConfigurationChangeData.h" /> <ClInclude Include="ConfigurationConflict.h" /> <ClInclude Include="ConfigurationConflictSetting.h" /> + <ClInclude Include="ConfigurationEnvironment.h" /> <ClInclude Include="ConfigurationParameter.h" /> <ClInclude Include="ConfigurationProcessor.h" /> <ClInclude Include="ConfigurationSequencer.h" /> @@ -261,6 +262,7 @@ <ClCompile Include="ConfigurationChangeData.cpp" /> <ClCompile Include="ConfigurationConflict.cpp" /> <ClCompile Include="ConfigurationConflictSetting.cpp" /> + <ClCompile Include="ConfigurationEnvironment.cpp" /> <ClCompile Include="ConfigurationParameter.cpp" /> <ClCompile Include="ConfigurationProcessor.cpp" /> <ClCompile Include="ConfigurationSequencer.cpp" /> diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters @@ -153,6 +153,9 @@ <ClCompile Include="ConfigurationSetSerializer_0_3.cpp"> <Filter>Parser</Filter> </ClCompile> + <ClCompile Include="ConfigurationEnvironment.cpp"> + <Filter>API Source</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h" /> @@ -315,6 +318,9 @@ <ClInclude Include="ConfigurationSetSerializer_0_3.h"> <Filter>Parser</Filter> </ClInclude> + <ClInclude Include="ConfigurationEnvironment.h"> + <Filter>API Headers</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Midl Include="Microsoft.Management.Configuration.idl" />