winget-cli

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

GroupPolicy.cpp (19889B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "TestCommon.h"
      5 #include "TestSettings.h"
      6 #include "winget/GroupPolicy.h"
      7 #include <AppInstallerStrings.h>
      8 #include <CertificateResources.h>
      9 
     10 using namespace TestCommon;
     11 using namespace AppInstaller::Settings;
     12 using namespace std::string_view_literals;
     13 
     14 namespace
     15 {
     16     std::wstring GetSourceJson(std::wstring_view name, std::wstring_view arg, std::wstring_view type, std::wstring_view data, std::wstring_view identifier, std::wstring_view trustLevel, std::wstring_view isExplicit, std::wstring_view pinningConfig = {})
     17     {
     18         std::wstringstream json;
     19         json << L"{ \"Name\":\"" << name << L"\", \"Arg\":\"" << arg << L"\", \"Type\":\"" << type << L"\", \"Data\":\"" << data << L"\", \"Identifier\":\"" << identifier << L"\", \"TrustLevel\":" << trustLevel << L", \"Explicit\":" << isExplicit;
     20         if (!pinningConfig.empty())
     21         {
     22             json << L", \"CertificatePinning\":" << pinningConfig;
     23         }
     24         json << " }";
     25         return json.str();
     26     }
     27 }
     28 
     29 TEST_CASE("GroupPolicy_NoPolicies", "[groupPolicy]")
     30 {
     31     auto policiesKey = RegCreateVolatileTestRoot();
     32     GroupPolicy groupPolicy{ policiesKey.get() };
     33 
     34     // Policies setting a value should be empty
     35     REQUIRE(!groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>().has_value());
     36     REQUIRE(!groupPolicy.GetValue<ValuePolicy::AdditionalSources>().has_value());
     37     REQUIRE(!groupPolicy.GetValue<ValuePolicy::AllowedSources>().has_value());
     38 
     39     // Everything should be not configured
     40     for (const auto& policy : TogglePolicy::GetAllPolicies())
     41     {
     42         REQUIRE(groupPolicy.GetState(policy.GetPolicy()) == PolicyState::NotConfigured);
     43     }
     44 }
     45 
     46 TEST_CASE("GroupPolicy_UpdateInterval", "[groupPolicy]")
     47 {
     48     auto policiesKey = RegCreateVolatileTestRoot();
     49 
     50     SECTION("Good value")
     51     {
     52         SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, 5);
     53         GroupPolicy groupPolicy{ policiesKey.get() };
     54 
     55         auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>();
     56         REQUIRE(policy.has_value());
     57         REQUIRE(*policy == 5);
     58     }
     59 
     60     SECTION("Wrong type")
     61     {
     62         SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, L"Wrong");
     63         GroupPolicy groupPolicy{ policiesKey.get() };
     64 
     65         auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>();
     66         REQUIRE(!policy.has_value());
     67     }
     68 }
     69 
     70 
     71 TEST_CASE("GroupPolicy_UpdateInterval_OldName", "[groupPolicy]")
     72 {
     73     auto policiesKey = RegCreateVolatileTestRoot();
     74 
     75     SECTION("New name shadows old")
     76     {
     77         SECTION("When old is valid")
     78         {
     79             SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 3);
     80         }
     81         SECTION("When old is invalid")
     82         {
     83             SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, L"Invalid type");
     84         }
     85 
     86         SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, 1);
     87         GroupPolicy groupPolicy{ policiesKey.get() };
     88 
     89         auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>();
     90         REQUIRE(policy.has_value());
     91         REQUIRE(*policy == 1);
     92     }
     93 
     94     SECTION("Fallback to old name")
     95     {
     96         SECTION("When new name has invalid data")
     97         {
     98             SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, L"Wrong type");
     99             SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 20);
    100             GroupPolicy groupPolicy{ policiesKey.get() };
    101 
    102             // We should not fall back on this case
    103             auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>();
    104             REQUIRE(!policy.has_value());
    105         }
    106         SECTION("When new name is missing")
    107         {
    108             // Don't add the registry value with the new name
    109             SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 20);
    110             GroupPolicy groupPolicy{ policiesKey.get() };
    111 
    112             auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>();
    113             REQUIRE(policy.has_value());
    114             REQUIRE(*policy == 20);
    115         }
    116     }
    117 }
    118 
    119 TEST_CASE("GroupPolicy_Sources", "[groupPolicy]")
    120 {
    121     auto policiesKey = RegCreateVolatileTestRoot();
    122 
    123     // Note that the following tests mix using Additional/Allowed sources policy.
    124     SECTION("Single source")
    125     {
    126         // We can read single source correctly
    127         auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName);
    128         SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"source-name", L"source-arg", L"source-type", L"source-data", L"source-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"true"), REG_SZ);
    129         GroupPolicy groupPolicy{ policiesKey.get() };
    130 
    131         auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>();
    132         REQUIRE(policy.has_value());
    133         REQUIRE(policy->size() == 1);
    134         REQUIRE(policy.value()[0].Name == "source-name");
    135         REQUIRE(policy.value()[0].Arg == "source-arg");
    136         REQUIRE(policy.value()[0].Type == "source-type");
    137         REQUIRE(policy.value()[0].Data == "source-data");
    138         REQUIRE(policy.value()[0].Identifier == "source-identifier");
    139         REQUIRE(policy.value()[0].TrustLevel[0] == "Trusted");
    140         REQUIRE(policy.value()[0].TrustLevel[1] == "StoreOrigin");
    141         REQUIRE(policy.value()[0].Explicit == true);
    142     }
    143     SECTION("Missing field")
    144     {
    145         // A single missing field causes the source to not be read.
    146         // "Type" is missing here.
    147         std::wstring sourceJson = L"{ \"Name\":\"source_name\", \"Arg\":\"source_arg\", \"Data\":\"source_data\", \"Identifier\":\"source_identifier\" }";
    148         auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName);
    149         SetRegistryValue(additionalSourcesKey.get(), L"0", sourceJson, REG_SZ);
    150         GroupPolicy groupPolicy{ policiesKey.get() };
    151 
    152         auto policy = groupPolicy.GetValue<ValuePolicy::AllowedSources>();
    153         REQUIRE(policy.has_value());
    154         REQUIRE(policy->empty());
    155     }
    156     SECTION("Invalid field")
    157     {
    158         // A single invalid field causes the source to not be read.
    159         // "Data" is invalid as it is an object, not a string.
    160         std::wstring sourceJson = L"{ \"Name\":\"source_name\", \"Arg\":\"source_arg\", \"Data\":{}, \"Type\":\"source_type\", \"Identifier\":\"source_identifier\" }";
    161         auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName);
    162         SetRegistryValue(additionalSourcesKey.get(), L"0", sourceJson, REG_SZ);
    163         GroupPolicy groupPolicy{ policiesKey.get() };
    164 
    165         auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>();
    166         REQUIRE(policy.has_value());
    167         REQUIRE(policy->empty());
    168     }
    169     SECTION("Invalid source JSON")
    170     {
    171         // An invalid source JSON causes the source to not be read.
    172         auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName);
    173         SetRegistryValue(additionalSourcesKey.get(), L"0", L"not a JSON", REG_SZ);
    174         GroupPolicy groupPolicy{ policiesKey.get() };
    175 
    176         auto policy = groupPolicy.GetValue<ValuePolicy::AllowedSources>();
    177         REQUIRE(policy.has_value());
    178         REQUIRE(policy->empty());
    179     }
    180     SECTION("Missing key")
    181     {
    182         // If the key does not exist we should not get anything.
    183         GroupPolicy groupPolicy{ policiesKey.get() };
    184 
    185         auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>();
    186         REQUIRE_FALSE(policy.has_value());
    187     }
    188     SECTION("Empty key")
    189     {
    190         // If the key is empty we should get an empty list.
    191         // Note that the policy editor doesn't actually create empty keys.
    192         auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName);
    193         GroupPolicy groupPolicy{ policiesKey.get() };
    194 
    195         auto policy = groupPolicy.GetValue<ValuePolicy::AllowedSources>();
    196         REQUIRE(policy.has_value());
    197         REQUIRE(policy->empty());
    198     }
    199     SECTION("Valid list")
    200     {
    201         // We should be able to read multiple values.
    202         // No specific order is required, but it will likely be the same.
    203         auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName);
    204         SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"s0-name", L"s0-arg", L"s0-type", L"s0-data", L"s0-identifier", L"[\"None\"]", L"true"), REG_SZ);
    205         SetRegistryValue(additionalSourcesKey.get(), L"1", GetSourceJson(L"s1-name", L"s1-arg", L"s1-type", L"s1-data", L"s1-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"false"), REG_SZ);
    206         SetRegistryValue(additionalSourcesKey.get(), L"2", GetSourceJson(L"s2-name", L"s2-arg", L"s2-type", L"s2-data", L"s2-identifier", L"[\"StoreOrigin\", \"Trusted\"]", L"true"), REG_SZ);
    207         GroupPolicy groupPolicy{ policiesKey.get() };
    208 
    209         auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>();
    210         REQUIRE(policy.has_value());
    211         REQUIRE(policy->size() == 3);
    212 
    213         REQUIRE(policy.value()[0].Name == "s0-name");
    214         REQUIRE(policy.value()[0].Arg == "s0-arg");
    215         REQUIRE(policy.value()[0].Type == "s0-type");
    216         REQUIRE(policy.value()[0].Data == "s0-data");
    217         REQUIRE(policy.value()[0].Identifier == "s0-identifier");
    218         REQUIRE(policy.value()[0].TrustLevel[0] == "None");
    219         REQUIRE(policy.value()[0].Explicit == true);
    220 
    221         REQUIRE(policy.value()[1].Name == "s1-name");
    222         REQUIRE(policy.value()[1].Arg == "s1-arg");
    223         REQUIRE(policy.value()[1].Type == "s1-type");
    224         REQUIRE(policy.value()[1].Data == "s1-data");
    225         REQUIRE(policy.value()[1].Identifier == "s1-identifier");
    226         REQUIRE(policy.value()[1].TrustLevel[0] == "Trusted");
    227         REQUIRE(policy.value()[1].TrustLevel[1] == "StoreOrigin");
    228         REQUIRE(policy.value()[1].Explicit == false);
    229 
    230         REQUIRE(policy.value()[2].Name == "s2-name");
    231         REQUIRE(policy.value()[2].Arg == "s2-arg");
    232         REQUIRE(policy.value()[2].Type == "s2-type");
    233         REQUIRE(policy.value()[2].Data == "s2-data");
    234         REQUIRE(policy.value()[2].Identifier == "s2-identifier");
    235         REQUIRE(policy.value()[2].TrustLevel[0] == "StoreOrigin");
    236         REQUIRE(policy.value()[2].TrustLevel[1] == "Trusted");
    237         REQUIRE(policy.value()[2].Explicit == true);
    238     }
    239     SECTION("Invalid source in list")
    240     {
    241         // If a single source is invalid we should still get all others
    242         auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName);
    243         SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"s0-name", L"s0-arg", L"s0-type", L"s0-data", L"s0-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"false"), REG_SZ);
    244         SetRegistryValue(additionalSourcesKey.get(), L"1", L"not a source", REG_SZ);
    245         SetRegistryValue(additionalSourcesKey.get(), L"2", GetSourceJson(L"s2-name", L"s2-arg", L"s2-type", L"s2-data", L"s2-identifier", L"[\"StoreOrigin\", \"Trusted\"]", L"true"), REG_SZ);
    246         GroupPolicy groupPolicy{ policiesKey.get() };
    247 
    248         auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>();
    249         REQUIRE(policy.has_value());
    250         REQUIRE(policy->size() == 2);
    251 
    252         REQUIRE(policy.value()[0].Name == "s0-name");
    253         REQUIRE(policy.value()[0].Arg == "s0-arg");
    254         REQUIRE(policy.value()[0].Type == "s0-type");
    255         REQUIRE(policy.value()[0].Data == "s0-data");
    256         REQUIRE(policy.value()[0].Identifier == "s0-identifier");
    257         REQUIRE(policy.value()[0].TrustLevel[0] == "Trusted");
    258         REQUIRE(policy.value()[0].TrustLevel[1] == "StoreOrigin");
    259         REQUIRE(policy.value()[0].Explicit == false);
    260 
    261         REQUIRE(policy.value()[1].Name == "s2-name");
    262         REQUIRE(policy.value()[1].Arg == "s2-arg");
    263         REQUIRE(policy.value()[1].Type == "s2-type");
    264         REQUIRE(policy.value()[1].Data == "s2-data");
    265         REQUIRE(policy.value()[1].Identifier == "s2-identifier");
    266         REQUIRE(policy.value()[1].TrustLevel[0] == "StoreOrigin");
    267         REQUIRE(policy.value()[1].TrustLevel[1] == "Trusted");
    268         REQUIRE(policy.value()[1].Explicit == true);
    269     }
    270     SECTION("Exported JSON")
    271     {
    272         // Policy should be able to use an exported JSON strings
    273         SourceFromPolicy source;
    274         source.Name = "json-name";
    275         source.Type = "json-type";
    276         source.Arg = "json-arg";
    277         source.Data = "json-data";
    278         source.Identifier = "json-id";
    279         source.TrustLevel = {"Trusted", "StoreOrigin"};
    280         source.Explicit = false;
    281 
    282         auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName);
    283         SetRegistryValue(additionalSourcesKey.get(), L"0", AppInstaller::Utility::ConvertToUTF16(source.ToJsonString()));
    284         GroupPolicy groupPolicy{ policiesKey.get() };
    285 
    286         auto policy = groupPolicy.GetValue<ValuePolicy::AllowedSources>();
    287         REQUIRE(policy.has_value());
    288         REQUIRE(policy->size() == 1);
    289         REQUIRE(policy.value()[0].Name == source.Name);
    290         REQUIRE(policy.value()[0].Arg == source.Arg);
    291         REQUIRE(policy.value()[0].Type == source.Type);
    292         REQUIRE(policy.value()[0].Data == source.Data);
    293         REQUIRE(policy.value()[0].Identifier == source.Identifier);
    294         REQUIRE(policy.value()[0].TrustLevel[0] == source.TrustLevel[0]); // Trusted
    295         REQUIRE(policy.value()[0].TrustLevel[1] == source.TrustLevel[1]); // StoreOrigin
    296         REQUIRE(policy.value()[0].Explicit == source.Explicit);
    297     }
    298     SECTION("Source with PinningConfiguration")
    299     {
    300         using namespace AppInstaller::Certificates;
    301 
    302         auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName);
    303 
    304         PinningDetails rootCert;
    305         rootCert.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE);
    306         PinningDetails intermediateCert;
    307         intermediateCert.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE);
    308         PinningDetails leafCert;
    309         leafCert.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
    310 
    311         auto getBytesString = [](const PinningDetails& details)
    312         {
    313             std::vector<BYTE> bytes;
    314             bytes.assign(details.GetCertificate()->pbCertEncoded, details.GetCertificate()->pbCertEncoded + details.GetCertificate()->cbCertEncoded);
    315             return AppInstaller::Utility::ConvertToUTF16(AppInstaller::Utility::ConvertToHexString(bytes));
    316         };
    317 
    318         std::wostringstream pinningConfig;
    319         pinningConfig <<
    320 LR"({
    321     "Chains": [{
    322         "Chain":[
    323             { "Validation": ["publickey"], "EmbeddedCertificate": ")" << getBytesString(rootCert) << LR"(" },
    324             { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(intermediateCert) << LR"(" },
    325             { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(leafCert) << LR"(" }
    326         ]
    327     }]
    328 })";
    329 
    330         SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"source-name", L"source-arg", L"source-type", L"source-data", L"source-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"true", pinningConfig.str()), REG_SZ);
    331         GroupPolicy groupPolicy{ policiesKey.get() };
    332 
    333         auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>();
    334         REQUIRE(policy.has_value());
    335         REQUIRE(policy->size() == 1);
    336         const auto& sourceInfo = policy.value()[0];
    337         REQUIRE(sourceInfo.Name == "source-name");
    338         REQUIRE(sourceInfo.Arg == "source-arg");
    339         REQUIRE(sourceInfo.Type == "source-type");
    340         REQUIRE(sourceInfo.Data == "source-data");
    341         REQUIRE(sourceInfo.Identifier == "source-identifier");
    342         REQUIRE(sourceInfo.TrustLevel[0] == "Trusted");
    343         REQUIRE(sourceInfo.TrustLevel[1] == "StoreOrigin");
    344         REQUIRE(sourceInfo.Explicit == true);
    345 
    346         // Use loaded pinning config and validate against leaf certificate
    347         REQUIRE(!sourceInfo.PinningConfiguration.IsEmpty());
    348         REQUIRE(sourceInfo.PinningConfiguration.Validate(leafCert.GetCertificate()));
    349     }
    350 }
    351 
    352 TEST_CASE("GroupPolicy_Toggle", "[groupPolicy]")
    353 {
    354     auto policiesKey = RegCreateVolatileTestRoot();
    355 
    356     SECTION("'None' is not configured")
    357     {
    358         GroupPolicy groupPolicy{ policiesKey.get() };
    359         REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::None) == PolicyState::NotConfigured);
    360         REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::None));
    361     }
    362 
    363     SECTION("Enabled")
    364     {
    365         SetRegistryValue(policiesKey.get(), WinGetPolicyValueName, 1);
    366         GroupPolicy groupPolicy{ policiesKey.get() };
    367         REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::WinGet) == PolicyState::Enabled);
    368         REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::WinGet));
    369     }
    370 
    371     SECTION("Disabled")
    372     {
    373         SetRegistryValue(policiesKey.get(), LocalManifestsPolicyValueName, 0);
    374         GroupPolicy groupPolicy{ policiesKey.get() };
    375         REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::LocalManifestFiles) == PolicyState::Disabled);
    376         REQUIRE_FALSE(groupPolicy.IsEnabled(TogglePolicy::Policy::LocalManifestFiles));
    377     }
    378 
    379     SECTION("Wrong type")
    380     {
    381         SetRegistryValue(policiesKey.get(), ExperimentalFeaturesPolicyValueName, L"Wrong");
    382         GroupPolicy groupPolicy{ policiesKey.get() };
    383         REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::DefaultSource) == PolicyState::NotConfigured);
    384         REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::DefaultSource));
    385     }
    386 }
    387 
    388 TEST_CASE("GroupPolicy_AllEnabled", "[groupPolicy]")
    389 {
    390     auto policiesKey = RegCreateVolatileTestRoot();
    391     SetRegistryValue(policiesKey.get(), WinGetPolicyValueName, 1);
    392     SetRegistryValue(policiesKey.get(), WinGetSettingsPolicyValueName, 1);
    393     SetRegistryValue(policiesKey.get(), ExperimentalFeaturesPolicyValueName, 1);
    394     SetRegistryValue(policiesKey.get(), LocalManifestsPolicyValueName, 1);
    395     SetRegistryValue(policiesKey.get(), EnableHashOverridePolicyValueName, 1);
    396     SetRegistryValue(policiesKey.get(), EnableLocalArchiveMalwareScanOverridePolicyValueName, 1);
    397     SetRegistryValue(policiesKey.get(), DefaultSourcePolicyValueName, 1);
    398     SetRegistryValue(policiesKey.get(), MSStoreSourcePolicyValueName, 1);;
    399     SetRegistryValue(policiesKey.get(), AdditionalSourcesPolicyValueName, 1);
    400     SetRegistryValue(policiesKey.get(), AllowedSourcesPolicyValueName, 1);
    401     SetRegistryValue(policiesKey.get(), BypassCertificatePinningForMicrosoftStoreValueName, 1);
    402     SetRegistryValue(policiesKey.get(), EnableWindowsPackageManagerCommandLineInterfaces, 1);
    403     SetRegistryValue(policiesKey.get(), ConfigurationPolicyValueName, 1);
    404     SetRegistryValue(policiesKey.get(), ProxyCommandLineOptionsPolicyValueName, 1);
    405     SetRegistryValue(policiesKey.get(), McpServerValueName, 1);
    406 
    407     GroupPolicy groupPolicy{ policiesKey.get() };
    408     for (const auto& policy : TogglePolicy::GetAllPolicies())
    409     {
    410         REQUIRE(groupPolicy.GetState(policy.GetPolicy()) == PolicyState::Enabled);
    411     }
    412 }