// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/policy/core/common/policy_loader_win.h"
#include <windows.h>
#include <stddef.h>
#include <stdint.h>
#include <userenv.h>
#include <algorithm>
#include <cstring>
#include <functional>
#include <string>
#include <utility>
#include "base/functional/callback.h"
#include "base/json/json_writer.h"
#include "base/path_service.h"
#include "base/process/process_handle.h"
#include "base/scoped_environment_variable_override.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/values.h"
#include "base/win/registry.h"
#include "base/win/win_util.h"
#include "components/policy/core/common/async_policy_provider.h"
#include "components/policy/core/common/configuration_policy_provider_test.h"
#include "components/policy/core/common/external_data_fetcher.h"
#include "components/policy/core/common/management/platform_management_service.h"
#include "components/policy/core/common/policy_bundle.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/core/common/schema_map.h"
#include "components/policy/policy_constants.h"
#include "components/strings/grit/components_strings.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::win::RegKey;
namespace policy {
namespace {
// Constants for registry key names.
const wchar_t kPathSep[] = L"\\";
const wchar_t kThirdParty[] = L"3rdparty";
const wchar_t kMandatory[] = L"policy";
const wchar_t kRecommended[] = L"recommended";
const wchar_t kTestPolicyKey[] = L"chrome.policy.key";
// Installs |value| in the given registry |path| and |hive|, under the key
// |name|. Returns false on errors.
// Some of the possible Value types are stored after a conversion (e.g. doubles
// are stored as strings), and can only be retrieved if a corresponding schema
// is written.
bool InstallValue(const base::Value& value,
HKEY hive,
const std::wstring& path,
const std::wstring& name) {
// KEY_ALL_ACCESS causes the ctor to create the key if it does not exist yet.
RegKey key(hive, path.c_str(), KEY_ALL_ACCESS);
EXPECT_TRUE(key.Valid());
switch (value.type()) {
case base::Value::Type::NONE:
return key.WriteValue(name.c_str(), L"") == ERROR_SUCCESS;
case base::Value::Type::BOOLEAN: {
if (!value.is_bool())
return false;
return key.WriteValue(name.c_str(), value.GetBool() ? 1 : 0) ==
ERROR_SUCCESS;
}
case base::Value::Type::INTEGER: {
if (!value.is_int())
return false;
return key.WriteValue(name.c_str(), value.GetInt()) == ERROR_SUCCESS;
}
case base::Value::Type::DOUBLE: {
std::wstring str_value = base::NumberToWString(value.GetDouble());
return key.WriteValue(name.c_str(), str_value.c_str()) == ERROR_SUCCESS;
}
case base::Value::Type::STRING: {
if (!value.is_string())
return false;
return key.WriteValue(
name.c_str(),
base::as_wcstr(base::UTF8ToUTF16(value.GetString()))) ==
ERROR_SUCCESS;
}
case base::Value::Type::DICT: {
if (!value.is_dict())
return false;
for (auto key_value : value.GetDict()) {
if (!InstallValue(key_value.second, hive, path + kPathSep + name,
base::UTF8ToWide(key_value.first))) {
return false;
}
}
return true;
}
case base::Value::Type::LIST: {
if (!value.is_list())
return false;
const base::Value::List& list = value.GetList();
for (size_t i = 0; i < list.size(); ++i) {
if (!InstallValue(list[i], hive, path + kPathSep + name,
base::NumberToWString(i + 1))) {
return false;
}
}
return true;
}
case base::Value::Type::BINARY:
return false;
}
NOTREACHED_IN_MIGRATION();
return false;
}
// This class provides sandboxing and mocking for the parts of the Windows
// Registry implementing Group Policy. It prepares two temporary sandbox keys,
// one for HKLM and one for HKCU. A test's calls to the registry are redirected
// by Windows to these sandboxes, allowing the tests to manipulate and access
// policy as if it were active, but without actually changing the parts of the
// Registry that are managed by Group Policy.
class ScopedGroupPolicyRegistrySandbox {
public:
ScopedGroupPolicyRegistrySandbox();
ScopedGroupPolicyRegistrySandbox(const ScopedGroupPolicyRegistrySandbox&) =
delete;
ScopedGroupPolicyRegistrySandbox& operator=(
const ScopedGroupPolicyRegistrySandbox&) = delete;
~ScopedGroupPolicyRegistrySandbox();
// Activates the registry keys overrides. This must be called before doing any
// writes to registry and the call should be wrapped in
// ASSERT_NO_FATAL_FAILURE.
void ActivateOverrides();
private:
void RemoveOverrides();
// Deletes the sandbox keys.
void DeleteKeys();
std::wstring key_name_;
// Keys are created for the lifetime of a test to contain
// the sandboxed HKCU and HKLM hives, respectively.
RegKey temp_hkcu_hive_key_;
RegKey temp_hklm_hive_key_;
};
// A test harness that feeds policy via the Chrome GPO registry subtree.
class RegistryTestHarness : public PolicyProviderTestHarness {
public:
RegistryTestHarness(HKEY hive, PolicyScope scope);
RegistryTestHarness(const RegistryTestHarness&) = delete;
RegistryTestHarness& operator=(const RegistryTestHarness&) = delete;
~RegistryTestHarness() override;
// PolicyProviderTestHarness:
void SetUp() override;
ConfigurationPolicyProvider* CreateProvider(
SchemaRegistry* registry,
scoped_refptr<base::SequencedTaskRunner> task_runner) override;
void InstallEmptyPolicy() override;
void InstallStringPolicy(const std::string& policy_name,
const std::string& policy_value) override;
void InstallIntegerPolicy(const std::string& policy_name,
int policy_value) override;
void InstallBooleanPolicy(const std::string& policy_name,
bool policy_value) override;
void InstallStringListPolicy(const std::string& policy_name,
const base::Value::List& policy_value) override;
void InstallDictionaryPolicy(const std::string& policy_name,
const base::Value::Dict& policy_value) override;
void Install3rdPartyPolicy(const base::Value::Dict& policies) override;
// Creates a harness instance that will install policy in HKCU or HKLM,
// respectively.
static PolicyProviderTestHarness* CreateHKCU();
static PolicyProviderTestHarness* CreateHKLM();
private:
HKEY hive_;
ScopedGroupPolicyRegistrySandbox registry_sandbox_;
};
ScopedGroupPolicyRegistrySandbox::ScopedGroupPolicyRegistrySandbox() {}
ScopedGroupPolicyRegistrySandbox::~ScopedGroupPolicyRegistrySandbox() {
RemoveOverrides();
DeleteKeys();
}
void ScopedGroupPolicyRegistrySandbox::ActivateOverrides() {
// Generate a unique registry key for the override for each test. This
// makes sure that tests executing in parallel won't delete each other's
// key, at DeleteKeys().
key_name_ = base::ASCIIToWide(base::StringPrintf(
"SOFTWARE\\chromium unittest %" CrPRIdPid, base::GetCurrentProcId()));
std::wstring hklm_key_name = key_name_ + L"\\HKLM";
std::wstring hkcu_key_name = key_name_ + L"\\HKCU";
// Delete the registry test keys if they already exist (this could happen if
// the process id got recycled and the last test running under the same
// process id crashed ).
DeleteKeys();
// Create the subkeys to hold the overridden HKLM and HKCU
// policy settings.
ASSERT_EQ(temp_hklm_hive_key_.Create(HKEY_CURRENT_USER, hklm_key_name.c_str(),
KEY_ALL_ACCESS),
ERROR_SUCCESS);
ASSERT_EQ(temp_hkcu_hive_key_.Create(HKEY_CURRENT_USER, hkcu_key_name.c_str(),
KEY_ALL_ACCESS),
ERROR_SUCCESS);
auto result_override_hklm =
RegOverridePredefKey(HKEY_LOCAL_MACHINE, temp_hklm_hive_key_.Handle());
auto result_override_hkcu =
RegOverridePredefKey(HKEY_CURRENT_USER, temp_hkcu_hive_key_.Handle());
if (result_override_hklm != ERROR_SUCCESS ||
result_override_hkcu != ERROR_SUCCESS) {
// We need to remove the overrides first in case one succeeded and one
// failed, otherwise deleting the keys fails.
RemoveOverrides();
DeleteKeys();
// Assert on the actual results to print the error code in failure case.
ASSERT_HRESULT_SUCCEEDED(result_override_hklm);
ASSERT_HRESULT_SUCCEEDED(result_override_hkcu);
}
}
void ScopedGroupPolicyRegistrySandbox::RemoveOverrides() {
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_LOCAL_MACHINE, nullptr));
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_CURRENT_USER, nullptr));
}
void ScopedGroupPolicyRegistrySandbox::DeleteKeys() {
RegKey key(HKEY_CURRENT_USER, key_name_.c_str(), KEY_ALL_ACCESS);
ASSERT_TRUE(key.Valid());
key.DeleteKey(L"");
}
RegistryTestHarness::RegistryTestHarness(HKEY hive, PolicyScope scope)
: PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY, scope,
POLICY_SOURCE_PLATFORM),
hive_(hive) {
}
RegistryTestHarness::~RegistryTestHarness() {}
void RegistryTestHarness::SetUp() {
// SetUp is called at gtest SetUp time, and gtest documentation guarantees
// that the test will not be executed if SetUp has a fatal failure. This is
// important, see crbug.com/721691.
ASSERT_NO_FATAL_FAILURE(registry_sandbox_.ActivateOverrides());
}
ConfigurationPolicyProvider* RegistryTestHarness::CreateProvider(
SchemaRegistry* registry,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
base::win::ScopedDomainStateForTesting scoped_domain(true);
std::unique_ptr<AsyncPolicyLoader> loader(new PolicyLoaderWin(
task_runner, PlatformManagementService::GetInstance(), kTestPolicyKey));
return new AsyncPolicyProvider(registry, std::move(loader));
}
void RegistryTestHarness::InstallEmptyPolicy() {}
void RegistryTestHarness::InstallStringPolicy(
const std::string& policy_name,
const std::string& policy_value) {
RegKey key(hive_, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(key.Valid());
ASSERT_HRESULT_SUCCEEDED(
key.WriteValue(base::UTF8ToWide(policy_name).c_str(),
base::UTF8ToWide(policy_value).c_str()));
}
void RegistryTestHarness::InstallIntegerPolicy(
const std::string& policy_name,
int policy_value) {
RegKey key(hive_, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(key.Valid());
key.WriteValue(base::UTF8ToWide(policy_name).c_str(),
static_cast<DWORD>(policy_value));
}
void RegistryTestHarness::InstallBooleanPolicy(
const std::string& policy_name,
bool policy_value) {
RegKey key(hive_, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(key.Valid());
key.WriteValue(base::UTF8ToWide(policy_name).c_str(),
static_cast<DWORD>(policy_value));
}
void RegistryTestHarness::InstallStringListPolicy(
const std::string& policy_name,
const base::Value::List& policy_value) {
RegKey key(
hive_,
(std::wstring(kTestPolicyKey) + L"\\" + base::UTF8ToWide(policy_name))
.c_str(),
KEY_ALL_ACCESS);
ASSERT_TRUE(key.Valid());
int index = 1;
for (const auto& element : policy_value) {
if (!element.is_string())
continue;
std::string name(base::NumberToString(index++));
key.WriteValue(base::UTF8ToWide(name).c_str(),
base::UTF8ToWide(element.GetString()).c_str());
}
}
void RegistryTestHarness::InstallDictionaryPolicy(
const std::string& policy_name,
const base::Value::Dict& policy_value) {
std::string json;
base::JSONWriter::Write(policy_value, &json);
RegKey key(hive_, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(key.Valid());
key.WriteValue(base::UTF8ToWide(policy_name).c_str(),
base::UTF8ToWide(json).c_str());
}
void RegistryTestHarness::Install3rdPartyPolicy(
const base::Value::Dict& policies) {
// The first level entries are domains, and the second level entries map
// components to their policy.
const std::wstring kPathPrefix =
std::wstring(kTestPolicyKey) + kPathSep + kThirdParty + kPathSep;
for (auto domain : policies) {
const base::Value& components = domain.second;
if (!components.is_dict()) {
ADD_FAILURE();
continue;
}
for (auto component : components.GetDict()) {
const std::wstring path = kPathPrefix + base::UTF8ToWide(domain.first) +
kPathSep + base::UTF8ToWide(component.first);
InstallValue(component.second, hive_, path, kMandatory);
}
}
}
// static
PolicyProviderTestHarness* RegistryTestHarness::CreateHKCU() {
return new RegistryTestHarness(HKEY_CURRENT_USER, POLICY_SCOPE_USER);
}
// static
PolicyProviderTestHarness* RegistryTestHarness::CreateHKLM() {
return new RegistryTestHarness(HKEY_LOCAL_MACHINE, POLICY_SCOPE_MACHINE);
}
} // namespace
// Instantiate abstract test case for basic policy reading tests.
INSTANTIATE_TEST_SUITE_P(PolicyProviderWinTest,
ConfigurationPolicyProviderTest,
testing::Values(RegistryTestHarness::CreateHKCU,
RegistryTestHarness::CreateHKLM));
// Instantiate abstract test case for 3rd party policy reading tests.
INSTANTIATE_TEST_SUITE_P(ThirdPartyPolicyProviderWinTest,
Configuration3rdPartyPolicyProviderTest,
testing::Values(RegistryTestHarness::CreateHKCU,
RegistryTestHarness::CreateHKLM));
// Test cases for windows policy provider specific functionality.
class PolicyLoaderWinTest : public PolicyTestBase {
protected:
// The policy key this tests places data under. This must match the data
// files in chrome/test/data/policy/gpo.
static const wchar_t kTestPolicyKey[];
PolicyLoaderWinTest() : scoped_domain_(false) {}
~PolicyLoaderWinTest() override {}
void SetUp() override {
PolicyTestBase::SetUp();
// Activate overrides of registry keys. gtest documentation guarantees
// that the test will not be executed if SetUp has a fatal failure. This is
// important, see crbug.com/721691.
ASSERT_NO_FATAL_FAILURE(registry_sandbox_.ActivateOverrides());
}
bool Matches(const PolicyBundle& expected) {
PolicyLoaderWin loader(task_environment_.GetMainThreadTaskRunner(),
PlatformManagementService::GetInstance(),
kTestPolicyKey);
PolicyBundle loaded = loader.InitialLoad(schema_registry_.schema_map());
return loaded.Equals(expected);
}
ScopedGroupPolicyRegistrySandbox registry_sandbox_;
base::win::ScopedDomainStateForTesting scoped_domain_;
};
const wchar_t PolicyLoaderWinTest::kTestPolicyKey[] =
L"SOFTWARE\\Policies\\Chromium";
TEST_F(PolicyLoaderWinTest, HKLMOverHKCU) {
RegKey hklm_key(HKEY_LOCAL_MACHINE, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(hklm_key.Valid());
hklm_key.WriteValue(base::UTF8ToWide(test_keys::kKeyString).c_str(),
base::UTF8ToWide("hklm").c_str());
RegKey hkcu_key(HKEY_CURRENT_USER, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(hkcu_key.Valid());
hkcu_key.WriteValue(base::UTF8ToWide(test_keys::kKeyString).c_str(),
base::UTF8ToWide("hkcu").c_str());
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
.Set(test_keys::kKeyString, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
POLICY_SOURCE_PLATFORM, base::Value("hklm"), nullptr);
expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
.GetMutable(test_keys::kKeyString)
->AddMessage(PolicyMap::MessageType::kWarning,
IDS_POLICY_CONFLICT_DIFF_VALUE);
PolicyMap::Entry conflict(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
POLICY_SOURCE_PLATFORM, base::Value("hkcu"),
nullptr);
expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
.GetMutable(test_keys::kKeyString)
->AddConflictingPolicy(std::move(conflict));
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, Merge3rdPartyPolicies) {
// Policy for the same extension will be provided at the 4 level/scope
// combinations, to verify that they overlap as expected.
const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "merge");
ASSERT_TRUE(RegisterSchema(
ns,
"{"
" \"type\": \"object\","
" \"properties\": {"
" \"a\": { \"type\": \"string\" },"
" \"b\": { \"type\": \"string\" },"
" \"c\": { \"type\": \"string\" },"
" \"d\": { \"type\": \"string\" }"
" }"
"}"));
const std::wstring kPathSuffix =
kTestPolicyKey + std::wstring(L"\\3rdparty\\extensions\\merge");
const char kUserMandatory[] = "user-mandatory";
const char kUserRecommended[] = "user-recommended";
const char kMachineMandatory[] = "machine-mandatory";
const char kMachineRecommended[] = "machine-recommended";
base::Value::Dict policy;
policy.Set("a", kMachineMandatory);
EXPECT_TRUE(InstallValue(base::Value(policy.Clone()), HKEY_LOCAL_MACHINE,
kPathSuffix, kMandatory));
policy.Set("a", kUserMandatory);
policy.Set("b", kUserMandatory);
EXPECT_TRUE(InstallValue(base::Value(policy.Clone()), HKEY_CURRENT_USER,
kPathSuffix, kMandatory));
policy.Set("a", kMachineRecommended);
policy.Set("b", kMachineRecommended);
policy.Set("c", kMachineRecommended);
EXPECT_TRUE(InstallValue(base::Value(policy.Clone()), HKEY_LOCAL_MACHINE,
kPathSuffix, kRecommended));
policy.Set("a", kUserRecommended);
policy.Set("b", kUserRecommended);
policy.Set("c", kUserRecommended);
policy.Set("d", kUserRecommended);
EXPECT_TRUE(InstallValue(base::Value(policy.Clone()), HKEY_CURRENT_USER,
kPathSuffix, kRecommended));
PolicyBundle expected;
PolicyMap& expected_policy = expected.Get(ns);
expected_policy.Set("a", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
POLICY_SOURCE_PLATFORM, base::Value(kMachineMandatory),
nullptr);
expected_policy.GetMutable("a")->AddMessage(PolicyMap::MessageType::kWarning,
IDS_POLICY_CONFLICT_DIFF_VALUE);
expected_policy.GetMutable("a")->AddMessage(PolicyMap::MessageType::kWarning,
IDS_POLICY_CONFLICT_DIFF_VALUE);
expected_policy.GetMutable("a")->AddMessage(PolicyMap::MessageType::kWarning,
IDS_POLICY_CONFLICT_DIFF_VALUE);
PolicyMap::Entry a_conflict_1(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
POLICY_SOURCE_PLATFORM,
base::Value(kMachineRecommended), nullptr);
PolicyMap::Entry a_conflict_2(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
POLICY_SOURCE_PLATFORM,
base::Value(kUserMandatory), nullptr);
PolicyMap::Entry a_conflict_3(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
POLICY_SOURCE_PLATFORM,
base::Value(kUserRecommended), nullptr);
expected_policy.GetMutable("a")->AddConflictingPolicy(
std::move(a_conflict_1));
expected_policy.GetMutable("a")->AddConflictingPolicy(
std::move(a_conflict_2));
expected_policy.GetMutable("a")->AddConflictingPolicy(
std::move(a_conflict_3));
expected_policy.Set("b", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
POLICY_SOURCE_PLATFORM, base::Value(kUserMandatory),
nullptr);
expected_policy.GetMutable("b")->AddMessage(PolicyMap::MessageType::kWarning,
IDS_POLICY_CONFLICT_DIFF_VALUE);
expected_policy.GetMutable("b")->AddMessage(PolicyMap::MessageType::kWarning,
IDS_POLICY_CONFLICT_DIFF_VALUE);
PolicyMap::Entry b_conflict_1(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
POLICY_SOURCE_PLATFORM,
base::Value(kMachineRecommended), nullptr);
PolicyMap::Entry b_conflict_2(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
POLICY_SOURCE_PLATFORM,
base::Value(kUserRecommended), nullptr);
expected_policy.GetMutable("b")->AddConflictingPolicy(
std::move(b_conflict_1));
expected_policy.GetMutable("b")->AddConflictingPolicy(
std::move(b_conflict_2));
expected_policy.Set("c", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
POLICY_SOURCE_PLATFORM, base::Value(kMachineRecommended),
nullptr);
expected_policy.GetMutable("c")->AddMessage(PolicyMap::MessageType::kWarning,
IDS_POLICY_CONFLICT_DIFF_VALUE);
PolicyMap::Entry c_conflict_1(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
POLICY_SOURCE_PLATFORM,
base::Value(kUserRecommended), nullptr);
expected_policy.GetMutable("c")->AddConflictingPolicy(
std::move(c_conflict_1));
expected_policy.Set("d", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
POLICY_SOURCE_PLATFORM, base::Value(kUserRecommended),
nullptr);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, LoadStringEncodedValues) {
// Create a dictionary with all the types that can be stored encoded in a
// string.
const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "string");
ASSERT_TRUE(RegisterSchema(ns,
R"({
"type": "object",
"id": "MainType",
"properties": {
"bool": { "type": "boolean" },
"int": { "type": "integer" },
"double": { "type": "number" },
"list": {
"type": "array",
"items": { "$ref": "MainType" }
},
"dict": { "$ref": "MainType" }
}
})"));
base::Value::Dict policy;
policy.Set("bool", true);
policy.Set("int", -123);
policy.Set("double", 456.78e9);
base::Value::List list;
list.Append(policy.Clone());
list.Append(policy.Clone());
policy.Set("list", list.Clone());
// Encode |policy| before adding the "dict" entry.
std::string encoded_dict;
base::JSONWriter::Write(policy, &encoded_dict);
ASSERT_FALSE(encoded_dict.empty());
policy.Set("dict", policy.Clone());
std::string encoded_list;
base::JSONWriter::Write(list, &encoded_list);
ASSERT_FALSE(encoded_list.empty());
base::Value::Dict encoded_policy;
encoded_policy.Set("bool", "1");
encoded_policy.Set("int", "-123");
encoded_policy.Set("double", "456.78e9");
encoded_policy.Set("list", encoded_list);
encoded_policy.Set("dict", encoded_dict);
const std::wstring kPathSuffix =
kTestPolicyKey + std::wstring(L"\\3rdparty\\extensions\\string");
EXPECT_TRUE(InstallValue(base::Value(encoded_policy.Clone()),
HKEY_CURRENT_USER, kPathSuffix, kMandatory));
PolicyBundle expected;
expected.Get(ns).LoadFrom(policy.Clone(), POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, LoadIntegerEncodedValues) {
const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "int");
ASSERT_TRUE(RegisterSchema(
ns,
"{"
" \"type\": \"object\","
" \"properties\": {"
" \"bool\": { \"type\": \"boolean\" },"
" \"int\": { \"type\": \"integer\" },"
" \"double\": { \"type\": \"number\" }"
" }"
"}"));
base::Value::Dict encoded_policy;
encoded_policy.Set("bool", 1);
encoded_policy.Set("int", 123);
encoded_policy.Set("double", 456);
const std::wstring kPathSuffix =
kTestPolicyKey + std::wstring(L"\\3rdparty\\extensions\\int");
EXPECT_TRUE(InstallValue(base::Value(encoded_policy.Clone()),
HKEY_CURRENT_USER, kPathSuffix, kMandatory));
base::Value::Dict policy;
policy.Set("bool", true);
policy.Set("int", 123);
policy.Set("double", 456.0);
PolicyBundle expected;
expected.Get(ns).LoadFrom(policy.Clone(), POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, DefaultPropertySchemaType) {
// Build a schema for an "object" with a default schema for its properties.
// Note that the top-level object can't have "additionalProperties", so
// a "policy" property is used instead.
const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "test");
ASSERT_TRUE(RegisterSchema(
ns,
"{"
" \"type\": \"object\","
" \"properties\": {"
" \"policy\": {"
" \"type\": \"object\","
" \"properties\": {"
" \"special-int1\": { \"type\": \"integer\" },"
" \"special-int2\": { \"type\": \"integer\" }"
" },"
" \"additionalProperties\": { \"type\": \"number\" }"
" }"
" }"
"}"));
// Write some test values.
base::Value::Dict policy;
// These special values have a specific schema for them.
policy.Set("special-int1", 123);
policy.Set("special-int2", "-456");
// Other values default to be loaded as doubles.
policy.Set("double1", 789.0);
policy.Set("double2", "123.456e7");
policy.Set("invalid", "omg");
base::Value::Dict all_policies;
all_policies.Set("policy", policy.Clone());
const std::wstring kPathSuffix =
kTestPolicyKey + std::wstring(L"\\3rdparty\\extensions\\test");
EXPECT_TRUE(InstallValue(base::Value(all_policies.Clone()), HKEY_CURRENT_USER,
kPathSuffix, kMandatory));
base::Value::Dict expected_policy;
expected_policy.Set("special-int1", 123);
expected_policy.Set("special-int2", -456);
expected_policy.Set("double1", 789.0);
expected_policy.Set("double2", 123.456e7);
base::Value::Dict expected_policies;
expected_policies.Set("policy", expected_policy.Clone());
PolicyBundle expected;
expected.Get(ns).LoadFrom(expected_policies.Clone(), POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, AlternativePropertySchemaType) {
const char kTestSchema[] =
"{"
" \"type\": \"object\","
" \"properties\": {"
" \"policy 1\": { \"type\": \"integer\" },"
" \"policy 2\": { \"type\": \"integer\" }"
" }"
"}";
// Register two namespaces. One will be completely populated with all defined
// properties and the second will be only partially populated.
const PolicyNamespace ns_a(
POLICY_DOMAIN_EXTENSIONS, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
const PolicyNamespace ns_b(
POLICY_DOMAIN_EXTENSIONS, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
ASSERT_TRUE(RegisterSchema(ns_a, kTestSchema));
ASSERT_TRUE(RegisterSchema(ns_b, kTestSchema));
PolicyBundle expected;
base::Value::Dict expected_a;
expected_a.Set("policy 1", 3);
expected_a.Set("policy 2", 3);
expected.Get(ns_a).LoadFrom(expected_a.Clone(), POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM);
base::Value::Dict expected_b;
expected_b.Set("policy 1", 2);
expected.Get(ns_b).LoadFrom(expected_b.Clone(), POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM);
const std::wstring kPathSuffix =
kTestPolicyKey +
std::wstring(L"\\3rdparty\\extensions\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
EXPECT_TRUE(InstallValue(base::Value(expected_a.Clone()), HKEY_LOCAL_MACHINE,
kPathSuffix, kMandatory));
const std::wstring kPathSuffix2 =
kTestPolicyKey +
std::wstring(L"\\3rdparty\\extensions\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
EXPECT_TRUE(InstallValue(base::Value(expected_b.Clone()), HKEY_LOCAL_MACHINE,
kPathSuffix2, kMandatory));
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, LoadPrecedencePolicies) {
const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, std::string());
RegisterChromeSchema(chrome_ns);
// Merging of precedence policies is handled separately from all remaining
// policies. This ensures that all precedence policies are correctly loaded
// from the registry.
RegKey hklm_key(HKEY_LOCAL_MACHINE, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(hklm_key.Valid());
PolicyBundle expected;
hklm_key.WriteValue(
base::UTF8ToWide(key::kCloudPolicyOverridesPlatformPolicy).c_str(),
/*in_value=*/1);
hklm_key.WriteValue(
base::UTF8ToWide(key::kCloudUserPolicyOverridesCloudMachinePolicy)
.c_str(),
/*in_value=*/1);
expected.Get(chrome_ns).Set(
key::kCloudPolicyOverridesPlatformPolicy, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM, base::Value(true), nullptr);
expected.Get(chrome_ns).Set(
key::kCloudUserPolicyOverridesCloudMachinePolicy, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM, base::Value(true), nullptr);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, LoadExpandSzPolicies) {
constexpr char kTestEnvVar[] = "TEST_ENV_VAR";
constexpr char kTestEnvVarValue[] = "TEST_VALUE";
base::ScopedEnvironmentVariableOverride scoped_env(kTestEnvVar,
kTestEnvVarValue);
RegKey hklm_key(HKEY_LOCAL_MACHINE, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(hklm_key.Valid());
auto reg_value = base::UTF8ToWide(std::string("%") + kTestEnvVar + "%");
hklm_key.WriteValue(
base::UTF8ToWide(test_keys::kKeyString).c_str(), reg_value.c_str(),
static_cast<DWORD>(sizeof(reg_value[0]) * (reg_value.size() + 1)),
REG_EXPAND_SZ);
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
.Set(test_keys::kKeyString, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
POLICY_SOURCE_PLATFORM, base::Value(kTestEnvVarValue), nullptr);
EXPECT_TRUE(Matches(expected));
}
// Make sure environment variables aren't expanded for REG_SZ.
TEST_F(PolicyLoaderWinTest, LoadSzPoliciesWithEnvVar) {
constexpr char kTestEnvVar[] = "TEST_ENV_VAR";
constexpr char kTestEnvVarValue[] = "TEST_VALUE";
base::ScopedEnvironmentVariableOverride scoped_env(kTestEnvVar,
kTestEnvVarValue);
RegKey hklm_key(HKEY_LOCAL_MACHINE, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(hklm_key.Valid());
auto reg_value = std::string("%") + kTestEnvVar + "%";
hklm_key.WriteValue(base::UTF8ToWide(test_keys::kKeyString).c_str(),
base::UTF8ToWide(reg_value).c_str());
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
.Set(test_keys::kKeyString, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
POLICY_SOURCE_PLATFORM, base::Value(reg_value), nullptr);
EXPECT_TRUE(Matches(expected));
}
} // namespace policy