blob: 519cc52e4c920956b61d63ecf9d81ad3f421b214 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/policy_loader_win.h"
#include <windows.h>
#include <userenv.h>
#include <algorithm>
#include <cstring>
#include <functional>
#include <iterator>
#include <vector>
#include "base/base_paths.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_writer.h"
#include "base/path_service.h"
#include "base/process/process.h"
#include "base/strings/string16.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/sys_byteorder.h"
#include "base/win/registry.h"
#include "chrome/browser/policy/async_policy_provider.h"
#include "chrome/browser/policy/configuration_policy_provider_test.h"
#include "chrome/browser/policy/preg_parser_win.h"
#include "components/json_schema/json_schema_constants.h"
#include "components/policy/core/common/external_data_fetcher.h"
#include "components/policy/core/common/policy_bundle.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/schema_map.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace schema = json_schema_constants;
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 kSchema[] = L"schema";
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 string16& path,
const string16& 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.GetType()) {
case base::Value::TYPE_NULL:
return key.WriteValue(name.c_str(), L"") == ERROR_SUCCESS;
case base::Value::TYPE_BOOLEAN: {
bool bool_value;
if (!value.GetAsBoolean(&bool_value))
return false;
return key.WriteValue(name.c_str(), bool_value ? 1 : 0) == ERROR_SUCCESS;
}
case base::Value::TYPE_INTEGER: {
int int_value;
if (!value.GetAsInteger(&int_value))
return false;
return key.WriteValue(name.c_str(), int_value) == ERROR_SUCCESS;
}
case base::Value::TYPE_DOUBLE: {
double double_value;
if (!value.GetAsDouble(&double_value))
return false;
string16 str_value = UTF8ToUTF16(base::DoubleToString(double_value));
return key.WriteValue(name.c_str(), str_value.c_str()) == ERROR_SUCCESS;
}
case base::Value::TYPE_STRING: {
string16 str_value;
if (!value.GetAsString(&str_value))
return false;
return key.WriteValue(name.c_str(), str_value.c_str()) == ERROR_SUCCESS;
}
case base::Value::TYPE_DICTIONARY: {
const base::DictionaryValue* sub_dict = NULL;
if (!value.GetAsDictionary(&sub_dict))
return false;
for (base::DictionaryValue::Iterator it(*sub_dict);
!it.IsAtEnd(); it.Advance()) {
if (!InstallValue(it.value(), hive, path + kPathSep + name,
UTF8ToUTF16(it.key()))) {
return false;
}
}
return true;
}
case base::Value::TYPE_LIST: {
const base::ListValue* list = NULL;
if (!value.GetAsList(&list))
return false;
for (size_t i = 0; i < list->GetSize(); ++i) {
const base::Value* item;
if (!list->Get(i, &item))
return false;
if (!InstallValue(*item, hive, path + kPathSep + name,
base::UintToString16(i + 1))) {
return false;
}
}
return true;
}
case base::Value::TYPE_BINARY:
return false;
}
NOTREACHED();
return false;
}
// Builds a JSON schema that represents the types contained in |value|.
// Ownership is transferred to the caller.
base::DictionaryValue* BuildSchema(const base::Value& value) {
base::DictionaryValue* schema = new base::DictionaryValue();
switch (value.GetType()) {
case base::Value::TYPE_NULL:
schema->SetString(schema::kType, "null");
break;
case base::Value::TYPE_BOOLEAN:
schema->SetString(schema::kType, "boolean");
break;
case base::Value::TYPE_INTEGER:
schema->SetString(schema::kType, "integer");
break;
case base::Value::TYPE_DOUBLE:
schema->SetString(schema::kType, "number");
break;
case base::Value::TYPE_STRING:
schema->SetString(schema::kType, "string");
break;
case base::Value::TYPE_LIST: {
// Assumes every list element has the same type.
const base::ListValue* list = NULL;
if (value.GetAsList(&list) && !list->empty()) {
schema->SetString(schema::kType, "array");
schema->Set(schema::kItems, BuildSchema(**list->begin()));
}
break;
}
case base::Value::TYPE_DICTIONARY: {
const base::DictionaryValue* dict = NULL;
if (value.GetAsDictionary(&dict)) {
base::DictionaryValue* properties = new base::DictionaryValue();
for (base::DictionaryValue::Iterator it(*dict);
!it.IsAtEnd(); it.Advance()) {
properties->Set(it.key(), BuildSchema(it.value()));
}
schema->SetString(schema::kType, "object");
schema->Set(schema::kProperties, properties);
}
break;
}
case base::Value::TYPE_BINARY:
break;
}
return schema;
}
// Writes a JSON |schema| at the registry entry |name| at |path|
// in the given |hive|. Returns false on failure.
bool WriteSchema(const base::DictionaryValue& schema,
HKEY hive,
const string16& path,
const string16& name) {
std::string encoded;
base::JSONWriter::Write(&schema, &encoded);
if (encoded.empty())
return false;
string16 encoded16 = UTF8ToUTF16(encoded);
// 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());
return key.WriteValue(name.c_str(), encoded16.c_str()) == ERROR_SUCCESS;
}
// Builds a JSON schema for |value| and writes it at the registry entry |name|
// at |path| in the given |hive|. Returns false on failure.
bool InstallSchema(const base::Value& value,
HKEY hive,
const string16& path,
const string16& name) {
scoped_ptr<base::DictionaryValue> schema_dict(BuildSchema(value));
return WriteSchema(*schema_dict, hive, path, name);
}
// 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();
private:
void ActivateOverrides();
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_;
DISALLOW_COPY_AND_ASSIGN(ScopedGroupPolicyRegistrySandbox);
};
// A test harness that feeds policy via the Chrome GPO registry subtree.
class RegistryTestHarness : public PolicyProviderTestHarness,
public AppliedGPOListProvider {
public:
RegistryTestHarness(HKEY hive, PolicyScope scope);
virtual ~RegistryTestHarness();
// PolicyProviderTestHarness:
virtual void SetUp() OVERRIDE;
virtual ConfigurationPolicyProvider* CreateProvider(
SchemaRegistry* registry,
scoped_refptr<base::SequencedTaskRunner> task_runner) OVERRIDE;
virtual void InstallEmptyPolicy() OVERRIDE;
virtual void InstallStringPolicy(const std::string& policy_name,
const std::string& policy_value) OVERRIDE;
virtual void InstallIntegerPolicy(const std::string& policy_name,
int policy_value) OVERRIDE;
virtual void InstallBooleanPolicy(const std::string& policy_name,
bool policy_value) OVERRIDE;
virtual void InstallStringListPolicy(
const std::string& policy_name,
const base::ListValue* policy_value) OVERRIDE;
virtual void InstallDictionaryPolicy(
const std::string& policy_name,
const base::DictionaryValue* policy_value) OVERRIDE;
virtual void Install3rdPartyPolicy(
const base::DictionaryValue* policies) OVERRIDE;
// AppliedGPOListProvider:
virtual DWORD GetAppliedGPOList(DWORD flags,
LPCTSTR machine_name,
PSID sid_user,
GUID* extension_guid,
PGROUP_POLICY_OBJECT* gpo_list) OVERRIDE;
virtual BOOL FreeGPOList(PGROUP_POLICY_OBJECT gpo_list) 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_;
DISALLOW_COPY_AND_ASSIGN(RegistryTestHarness);
};
// A test harness that generates PReg files for the provider to read.
class PRegTestHarness : public PolicyProviderTestHarness,
public AppliedGPOListProvider {
public:
PRegTestHarness();
virtual ~PRegTestHarness();
// PolicyProviderTestHarness:
virtual void SetUp() OVERRIDE;
virtual ConfigurationPolicyProvider* CreateProvider(
SchemaRegistry* registry,
scoped_refptr<base::SequencedTaskRunner> task_runner) OVERRIDE;
virtual void InstallEmptyPolicy() OVERRIDE;
virtual void InstallStringPolicy(const std::string& policy_name,
const std::string& policy_value) OVERRIDE;
virtual void InstallIntegerPolicy(const std::string& policy_name,
int policy_value) OVERRIDE;
virtual void InstallBooleanPolicy(const std::string& policy_name,
bool policy_value) OVERRIDE;
virtual void InstallStringListPolicy(
const std::string& policy_name,
const base::ListValue* policy_value) OVERRIDE;
virtual void InstallDictionaryPolicy(
const std::string& policy_name,
const base::DictionaryValue* policy_value) OVERRIDE;
virtual void Install3rdPartyPolicy(
const base::DictionaryValue* policies) OVERRIDE;
// AppliedGPOListProvider:
virtual DWORD GetAppliedGPOList(DWORD flags,
LPCTSTR machine_name,
PSID sid_user,
GUID* extension_guid,
PGROUP_POLICY_OBJECT* gpo_list) OVERRIDE;
virtual BOOL FreeGPOList(PGROUP_POLICY_OBJECT gpo_list) OVERRIDE;
// Creates a harness instance.
static PolicyProviderTestHarness* Create();
private:
// Helper to append a string16 to an uint8 buffer.
static void AppendChars(std::vector<uint8>* buffer, const string16& chars);
// Appends a record with the given fields to the PReg file.
void AppendRecordToPRegFile(const string16& path,
const std::string& key,
DWORD type,
DWORD size,
uint8* data);
// Appends the given DWORD |value| for |path| + |key| to the PReg file.
void AppendDWORDToPRegFile(const string16& path,
const std::string& key,
DWORD value);
// Appends the given string |value| for |path| + |key| to the PReg file.
void AppendStringToPRegFile(const string16& path,
const std::string& key,
const std::string& value);
// Appends the given policy |value| for |path| + |key| to the PReg file,
// converting and recursing as necessary.
void AppendPolicyToPRegFile(const string16& path,
const std::string& key,
const base::Value* value);
base::ScopedTempDir temp_dir_;
base::FilePath preg_file_path_;
GROUP_POLICY_OBJECT gpo_;
DISALLOW_COPY_AND_ASSIGN(PRegTestHarness);
};
ScopedGroupPolicyRegistrySandbox::ScopedGroupPolicyRegistrySandbox() {
// 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_ = ASCIIToWide(base::StringPrintf(
"SOFTWARE\\chromium unittest %d",
base::Process::Current().pid()));
std::wstring hklm_key_name = key_name_ + L"\\HKLM";
std::wstring hkcu_key_name = key_name_ + L"\\HKCU";
// Create the subkeys to hold the overridden HKLM and HKCU
// policy settings.
temp_hklm_hive_key_.Create(HKEY_CURRENT_USER,
hklm_key_name.c_str(),
KEY_ALL_ACCESS);
temp_hkcu_hive_key_.Create(HKEY_CURRENT_USER,
hkcu_key_name.c_str(),
KEY_ALL_ACCESS);
ActivateOverrides();
}
ScopedGroupPolicyRegistrySandbox::~ScopedGroupPolicyRegistrySandbox() {
RemoveOverrides();
DeleteKeys();
}
void ScopedGroupPolicyRegistrySandbox::ActivateOverrides() {
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_LOCAL_MACHINE,
temp_hklm_hive_key_.Handle()));
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_CURRENT_USER,
temp_hkcu_hive_key_.Handle()));
}
void ScopedGroupPolicyRegistrySandbox::RemoveOverrides() {
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_LOCAL_MACHINE, 0));
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_CURRENT_USER, 0));
}
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), hive_(hive) {}
RegistryTestHarness::~RegistryTestHarness() {}
void RegistryTestHarness::SetUp() {}
ConfigurationPolicyProvider* RegistryTestHarness::CreateProvider(
SchemaRegistry* registry,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
scoped_ptr<AsyncPolicyLoader> loader(
new PolicyLoaderWin(task_runner, kTestPolicyKey, this));
return new AsyncPolicyProvider(registry, loader.Pass());
}
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(UTF8ToUTF16(policy_name).c_str(),
UTF8ToUTF16(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(UTF8ToUTF16(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(UTF8ToUTF16(policy_name).c_str(),
static_cast<DWORD>(policy_value));
}
void RegistryTestHarness::InstallStringListPolicy(
const std::string& policy_name,
const base::ListValue* policy_value) {
RegKey key(hive_,
(string16(kTestPolicyKey) + ASCIIToUTF16("\\") +
UTF8ToUTF16(policy_name)).c_str(),
KEY_ALL_ACCESS);
ASSERT_TRUE(key.Valid());
int index = 1;
for (base::ListValue::const_iterator element(policy_value->begin());
element != policy_value->end();
++element) {
std::string element_value;
if (!(*element)->GetAsString(&element_value))
continue;
std::string name(base::IntToString(index++));
key.WriteValue(UTF8ToUTF16(name).c_str(),
UTF8ToUTF16(element_value).c_str());
}
}
void RegistryTestHarness::InstallDictionaryPolicy(
const std::string& policy_name,
const base::DictionaryValue* policy_value) {
std::string json;
base::JSONWriter::Write(policy_value, &json);
RegKey key(hive_, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(key.Valid());
key.WriteValue(UTF8ToUTF16(policy_name).c_str(),
UTF8ToUTF16(json).c_str());
}
void RegistryTestHarness::Install3rdPartyPolicy(
const base::DictionaryValue* policies) {
// The first level entries are domains, and the second level entries map
// components to their policy.
const string16 kPathPrefix = string16(kTestPolicyKey) + kPathSep +
kThirdParty + kPathSep;
for (base::DictionaryValue::Iterator domain(*policies);
!domain.IsAtEnd(); domain.Advance()) {
const base::DictionaryValue* components = NULL;
if (!domain.value().GetAsDictionary(&components)) {
ADD_FAILURE();
continue;
}
for (base::DictionaryValue::Iterator component(*components);
!component.IsAtEnd(); component.Advance()) {
const string16 path = kPathPrefix +
UTF8ToUTF16(domain.key()) + kPathSep +
UTF8ToUTF16(component.key());
InstallValue(component.value(), hive_, path, kMandatory);
EXPECT_TRUE(InstallSchema(component.value(), hive_, path, kSchema));
}
}
}
DWORD RegistryTestHarness::GetAppliedGPOList(DWORD flags,
LPCTSTR machine_name,
PSID sid_user,
GUID* extension_guid,
PGROUP_POLICY_OBJECT* gpo_list) {
*gpo_list = NULL;
return ERROR_ACCESS_DENIED;
}
BOOL RegistryTestHarness::FreeGPOList(PGROUP_POLICY_OBJECT gpo_list) {
return TRUE;
}
// 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);
}
PRegTestHarness::PRegTestHarness()
: PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE) {}
PRegTestHarness::~PRegTestHarness() {}
void PRegTestHarness::SetUp() {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
preg_file_path_ = temp_dir_.path().Append(PolicyLoaderWin::kPRegFileName);
ASSERT_TRUE(file_util::WriteFile(preg_file_path_,
preg_parser::kPRegFileHeader,
arraysize(preg_parser::kPRegFileHeader)));
memset(&gpo_, 0, sizeof(GROUP_POLICY_OBJECT));
gpo_.lpFileSysPath = const_cast<wchar_t*>(temp_dir_.path().value().c_str());
}
ConfigurationPolicyProvider* PRegTestHarness::CreateProvider(
SchemaRegistry* registry,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
scoped_ptr<AsyncPolicyLoader> loader(
new PolicyLoaderWin(task_runner, kTestPolicyKey, this));
return new AsyncPolicyProvider(registry, loader.Pass());
}
void PRegTestHarness::InstallEmptyPolicy() {}
void PRegTestHarness::InstallStringPolicy(const std::string& policy_name,
const std::string& policy_value) {
AppendStringToPRegFile(kTestPolicyKey, policy_name, policy_value);
}
void PRegTestHarness::InstallIntegerPolicy(const std::string& policy_name,
int policy_value) {
AppendDWORDToPRegFile(kTestPolicyKey, policy_name, policy_value);
}
void PRegTestHarness::InstallBooleanPolicy(const std::string& policy_name,
bool policy_value) {
AppendDWORDToPRegFile(kTestPolicyKey, policy_name, policy_value);
}
void PRegTestHarness::InstallStringListPolicy(
const std::string& policy_name,
const base::ListValue* policy_value) {
AppendPolicyToPRegFile(kTestPolicyKey, policy_name, policy_value);
}
void PRegTestHarness::InstallDictionaryPolicy(
const std::string& policy_name,
const base::DictionaryValue* policy_value) {
std::string json;
base::JSONWriter::Write(policy_value, &json);
AppendStringToPRegFile(kTestPolicyKey, policy_name, json);
}
void PRegTestHarness::Install3rdPartyPolicy(
const base::DictionaryValue* policies) {
// The first level entries are domains, and the second level entries map
// components to their policy.
const string16 kPathPrefix = string16(kTestPolicyKey) + kPathSep +
kThirdParty + kPathSep;
for (base::DictionaryValue::Iterator domain(*policies);
!domain.IsAtEnd(); domain.Advance()) {
const base::DictionaryValue* components = NULL;
if (!domain.value().GetAsDictionary(&components)) {
ADD_FAILURE();
continue;
}
const string16 domain_path = kPathPrefix + UTF8ToUTF16(domain.key());
for (base::DictionaryValue::Iterator component(*components);
!component.IsAtEnd(); component.Advance()) {
const string16 component_path =
domain_path + kPathSep + UTF8ToUTF16(component.key());
AppendPolicyToPRegFile(component_path, UTF16ToUTF8(kMandatory),
&component.value());
scoped_ptr<base::DictionaryValue> schema_dict(
BuildSchema(component.value()));
std::string schema_json;
base::JSONWriter::Write(schema_dict.get(), &schema_json);
if (!schema_json.empty()) {
AppendStringToPRegFile(component_path, UTF16ToUTF8(kSchema),
schema_json);
}
}
}
}
DWORD PRegTestHarness::GetAppliedGPOList(DWORD flags,
LPCTSTR machine_name,
PSID sid_user,
GUID* extension_guid,
PGROUP_POLICY_OBJECT* gpo_list) {
*gpo_list = flags & GPO_LIST_FLAG_MACHINE ? &gpo_ : NULL;
return ERROR_SUCCESS;
}
BOOL PRegTestHarness::FreeGPOList(PGROUP_POLICY_OBJECT gpo_list) {
return TRUE;
}
// static
PolicyProviderTestHarness* PRegTestHarness::Create() {
return new PRegTestHarness();
}
// static
void PRegTestHarness::AppendChars(std::vector<uint8>* buffer,
const string16& chars) {
for (string16::const_iterator c(chars.begin()); c != chars.end(); ++c) {
buffer->push_back(*c & 0xff);
buffer->push_back((*c >> 8) & 0xff);
}
}
void PRegTestHarness::AppendRecordToPRegFile(const string16& path,
const std::string& key,
DWORD type,
DWORD size,
uint8* data) {
std::vector<uint8> buffer;
AppendChars(&buffer, L"[");
AppendChars(&buffer, path);
AppendChars(&buffer, string16(L"\0;", 2));
AppendChars(&buffer, UTF8ToUTF16(key));
AppendChars(&buffer, string16(L"\0;", 2));
type = base::ByteSwapToLE32(type);
uint8* type_data = reinterpret_cast<uint8*>(&type);
buffer.insert(buffer.end(), type_data, type_data + sizeof(DWORD));
AppendChars(&buffer, L";");
size = base::ByteSwapToLE32(size);
uint8* size_data = reinterpret_cast<uint8*>(&size);
buffer.insert(buffer.end(), size_data, size_data + sizeof(DWORD));
AppendChars(&buffer, L";");
buffer.insert(buffer.end(), data, data + size);
AppendChars(&buffer, L"]");
ASSERT_EQ(buffer.size(),
file_util::AppendToFile(
preg_file_path_,
reinterpret_cast<const char*>(vector_as_array(&buffer)),
buffer.size()));
}
void PRegTestHarness::AppendDWORDToPRegFile(const string16& path,
const std::string& key,
DWORD value) {
value = base::ByteSwapToLE32(value);
AppendRecordToPRegFile(path, key, REG_DWORD, sizeof(DWORD),
reinterpret_cast<uint8*>(&value));
}
void PRegTestHarness::AppendStringToPRegFile(const string16& path,
const std::string& key,
const std::string& value) {
string16 string16_value(UTF8ToUTF16(value));
std::vector<char16> data;
std::transform(string16_value.begin(), string16_value.end(),
std::back_inserter(data), std::ptr_fun(base::ByteSwapToLE16));
data.push_back(base::ByteSwapToLE16(L'\0'));
AppendRecordToPRegFile(path, key, REG_SZ, data.size() * sizeof(char16),
reinterpret_cast<uint8*>(vector_as_array(&data)));
}
void PRegTestHarness::AppendPolicyToPRegFile(const string16& path,
const std::string& key,
const base::Value* value) {
switch (value->GetType()) {
case base::Value::TYPE_BOOLEAN: {
bool boolean_value = false;
ASSERT_TRUE(value->GetAsBoolean(&boolean_value));
AppendDWORDToPRegFile(path, key, boolean_value);
break;
}
case base::Value::TYPE_INTEGER: {
int int_value = 0;
ASSERT_TRUE(value->GetAsInteger(&int_value));
AppendDWORDToPRegFile(path, key, int_value);
break;
}
case base::Value::TYPE_DOUBLE: {
double double_value = 0;
ASSERT_TRUE(value->GetAsDouble(&double_value));
AppendStringToPRegFile(path, key, base::DoubleToString(double_value));
break;
}
case base::Value::TYPE_STRING: {
std::string string_value;
ASSERT_TRUE(value->GetAsString(&string_value));
AppendStringToPRegFile(path, key, string_value);
break;
}
case base::Value::TYPE_DICTIONARY: {
string16 subpath = path + kPathSep + UTF8ToUTF16(key);
const base::DictionaryValue* dict = NULL;
ASSERT_TRUE(value->GetAsDictionary(&dict));
for (base::DictionaryValue::Iterator entry(*dict); !entry.IsAtEnd();
entry.Advance()) {
AppendPolicyToPRegFile(subpath, entry.key(), &entry.value());
}
break;
}
case base::Value::TYPE_LIST: {
string16 subpath = path + kPathSep + UTF8ToUTF16(key);
const base::ListValue* list = NULL;
ASSERT_TRUE(value->GetAsList(&list));
for (size_t i = 0; i < list->GetSize(); ++i) {
const base::Value* entry = NULL;
ASSERT_TRUE(list->Get(i, &entry));
AppendPolicyToPRegFile(subpath, base::IntToString(i + 1), entry);
}
break;
}
case base::Value::TYPE_BINARY:
case base::Value::TYPE_NULL: {
ADD_FAILURE();
break;
}
}
}
} // namespace
// Instantiate abstract test case for basic policy reading tests.
INSTANTIATE_TEST_CASE_P(
PolicyProviderWinTest,
ConfigurationPolicyProviderTest,
testing::Values(RegistryTestHarness::CreateHKCU,
RegistryTestHarness::CreateHKLM,
PRegTestHarness::Create));
// Instantiate abstract test case for 3rd party policy reading tests.
INSTANTIATE_TEST_CASE_P(
ThirdPartyPolicyProviderWinTest,
Configuration3rdPartyPolicyProviderTest,
testing::Values(RegistryTestHarness::CreateHKCU,
RegistryTestHarness::CreateHKLM,
PRegTestHarness::Create));
// Test cases for windows policy provider specific functionality.
class PolicyLoaderWinTest : public PolicyTestBase,
public AppliedGPOListProvider {
protected:
// The policy key this tests places data under. This must match the data
// files in chrome/test/data/policy/gpo.
static const char16 kTestPolicyKey[];
PolicyLoaderWinTest()
: gpo_list_(NULL),
gpo_list_status_(ERROR_ACCESS_DENIED) {}
virtual ~PolicyLoaderWinTest() {}
virtual void SetUp() OVERRIDE {
PolicyTestBase::SetUp();
ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir_));
test_data_dir_ = test_data_dir_.AppendASCII("chrome")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("policy")
.AppendASCII("gpo");
// Unknown components will be filtered out. Register their names with an
// invalid schema to avoid that.
ComponentMap components;
components["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = Schema();
components["bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"] = Schema();
components["int"] = Schema();
components["merge"] = Schema();
components["string"] = Schema();
components["test"] = Schema();
schema_registry_.RegisterComponents(POLICY_DOMAIN_EXTENSIONS, components);
}
// AppliedGPOListProvider:
virtual DWORD GetAppliedGPOList(DWORD flags,
LPCTSTR machine_name,
PSID sid_user,
GUID* extension_guid,
PGROUP_POLICY_OBJECT* gpo_list) OVERRIDE {
*gpo_list = gpo_list_;
return gpo_list_status_;
}
virtual BOOL FreeGPOList(PGROUP_POLICY_OBJECT gpo_list) OVERRIDE {
return TRUE;
}
void InitGPO(GROUP_POLICY_OBJECT* gpo,
DWORD options,
const base::FilePath& path,
GROUP_POLICY_OBJECT* next,
GROUP_POLICY_OBJECT* prev) {
memset(gpo, 0, sizeof(GROUP_POLICY_OBJECT));
gpo->dwOptions = options;
gpo->lpFileSysPath = const_cast<wchar_t*>(path.value().c_str());
gpo->pNext = next;
gpo->pPrev = prev;
}
bool Matches(const PolicyBundle& expected) {
PolicyLoaderWin loader(loop_.message_loop_proxy(), kTestPolicyKey, this);
scoped_ptr<PolicyBundle> loaded(
loader.InitialLoad(schema_registry_.schema_map()));
return loaded->Equals(expected);
}
void InstallRegistrySentinel() {
RegKey hklm_key(HKEY_CURRENT_USER, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(hklm_key.Valid());
hklm_key.WriteValue(
UTF8ToUTF16(test_keys::kKeyString).c_str(),
UTF8ToUTF16("registry").c_str());
}
bool MatchesRegistrySentinel() {
base::DictionaryValue expected_policy;
expected_policy.SetString(test_keys::kKeyString, "registry");
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
.LoadFrom(&expected_policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
return Matches(expected);
}
bool MatchesTestBundle() {
base::DictionaryValue expected_policy;
expected_policy.SetBoolean(test_keys::kKeyBoolean, true);
expected_policy.SetString(test_keys::kKeyString, "GPO");
expected_policy.SetInteger(test_keys::kKeyInteger, 42);
scoped_ptr<base::ListValue> list(new base::ListValue());
list->AppendString("GPO 1");
list->AppendString("GPO 2");
expected_policy.Set(test_keys::kKeyStringList, list.release());
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
.LoadFrom(&expected_policy, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_MACHINE);
return Matches(expected);
}
ScopedGroupPolicyRegistrySandbox registry_sandbox_;
PGROUP_POLICY_OBJECT gpo_list_;
DWORD gpo_list_status_;
base::FilePath test_data_dir_;
};
const char16 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(UTF8ToUTF16(test_keys::kKeyString).c_str(),
UTF8ToUTF16("hklm").c_str());
RegKey hkcu_key(HKEY_CURRENT_USER, kTestPolicyKey, KEY_ALL_ACCESS);
ASSERT_TRUE(hkcu_key.Valid());
hkcu_key.WriteValue(UTF8ToUTF16(test_keys::kKeyString).c_str(),
UTF8ToUTF16("hkcu").c_str());
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
.Set(test_keys::kKeyString,
POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_MACHINE,
base::Value::CreateStringValue("hklm"), NULL);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, Load3rdPartyWithoutSchema) {
base::DictionaryValue dict;
dict.SetString("str", "string value");
dict.SetInteger("int", 123);
dict.Set("subdict", dict.DeepCopy());
dict.Set("subsubdict", dict.DeepCopy());
dict.Set("subsubsubdict", dict.DeepCopy());
base::DictionaryValue policy_dict;
policy_dict.Set("extensions.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.policy",
dict.DeepCopy());
policy_dict.Set("extensions.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.policy",
dict.DeepCopy());
EXPECT_TRUE(InstallValue(policy_dict, HKEY_LOCAL_MACHINE,
kTestPolicyKey, kThirdParty));
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
.LoadFrom(&dict, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
expected.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"))
.LoadFrom(&dict, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
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 string16 kPathSuffix =
kTestPolicyKey + ASCIIToUTF16("\\3rdparty\\extensions\\merge");
const char kUserMandatory[] = "user-mandatory";
const char kUserRecommended[] = "user-recommended";
const char kMachineMandatory[] = "machine-mandatory";
const char kMachineRecommended[] = "machine-recommended";
base::DictionaryValue policy;
policy.SetString("a", kMachineMandatory);
EXPECT_TRUE(InstallValue(policy, HKEY_LOCAL_MACHINE,
kPathSuffix, kMandatory));
policy.SetString("a", kUserMandatory);
policy.SetString("b", kUserMandatory);
EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER,
kPathSuffix, kMandatory));
policy.SetString("a", kMachineRecommended);
policy.SetString("b", kMachineRecommended);
policy.SetString("c", kMachineRecommended);
EXPECT_TRUE(InstallValue(policy, HKEY_LOCAL_MACHINE,
kPathSuffix, kRecommended));
policy.SetString("a", kUserRecommended);
policy.SetString("b", kUserRecommended);
policy.SetString("c", kUserRecommended);
policy.SetString("d", kUserRecommended);
EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER,
kPathSuffix, kRecommended));
PolicyBundle expected;
PolicyMap& expected_policy =
expected.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "merge"));
expected_policy.Set("a", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
base::Value::CreateStringValue(kMachineMandatory), NULL);
expected_policy.Set("b", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
base::Value::CreateStringValue(kUserMandatory), NULL);
expected_policy.Set("c", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
base::Value::CreateStringValue(kMachineRecommended),
NULL);
expected_policy.Set("d", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
base::Value::CreateStringValue(kUserRecommended), NULL);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, LoadStringEncodedValues) {
// Create a dictionary with all the types that can be stored encoded in a
// string, to pass to InstallSchema(). Also build an equivalent dictionary
// with the encoded values, to pass to InstallValue().
base::DictionaryValue policy;
policy.Set("null", base::Value::CreateNullValue());
policy.SetBoolean("bool", true);
policy.SetInteger("int", -123);
policy.SetDouble("double", 456.78e9);
base::ListValue list;
list.Append(policy.DeepCopy());
list.Append(policy.DeepCopy());
policy.Set("list", list.DeepCopy());
// 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.DeepCopy());
std::string encoded_list;
base::JSONWriter::Write(&list, &encoded_list);
ASSERT_FALSE(encoded_list.empty());
base::DictionaryValue encoded_policy;
encoded_policy.SetString("null", "");
encoded_policy.SetString("bool", "1");
encoded_policy.SetString("int", "-123");
encoded_policy.SetString("double", "456.78e9");
encoded_policy.SetString("list", encoded_list);
encoded_policy.SetString("dict", encoded_dict);
const string16 kPathSuffix =
kTestPolicyKey + ASCIIToUTF16("\\3rdparty\\extensions\\string");
EXPECT_TRUE(InstallSchema(policy, HKEY_CURRENT_USER, kPathSuffix, kSchema));
EXPECT_TRUE(
InstallValue(encoded_policy, HKEY_CURRENT_USER, kPathSuffix, kMandatory));
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "string"))
.LoadFrom(&policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, LoadIntegerEncodedValues) {
base::DictionaryValue policy;
policy.SetBoolean("bool", true);
policy.SetInteger("int", 123);
policy.SetDouble("double", 456.0);
base::DictionaryValue encoded_policy;
encoded_policy.SetInteger("bool", 1);
encoded_policy.SetInteger("int", 123);
encoded_policy.SetInteger("double", 456);
const string16 kPathSuffix =
kTestPolicyKey + ASCIIToUTF16("\\3rdparty\\extensions\\int");
EXPECT_TRUE(InstallSchema(policy, HKEY_CURRENT_USER, kPathSuffix, kSchema));
EXPECT_TRUE(
InstallValue(encoded_policy, HKEY_CURRENT_USER, kPathSuffix, kMandatory));
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "int"))
.LoadFrom(&policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, DefaultPropertySchemaType) {
// Build a schema for an "object" with a default schema for its properties.
base::DictionaryValue default_schema;
default_schema.SetString(schema::kType, "number");
base::DictionaryValue integer_schema;
integer_schema.SetString(schema::kType, "integer");
base::DictionaryValue properties;
properties.Set("special-int1", integer_schema.DeepCopy());
properties.Set("special-int2", integer_schema.DeepCopy());
base::DictionaryValue schema;
schema.SetString(schema::kType, "object");
schema.Set(schema::kProperties, properties.DeepCopy());
schema.Set(schema::kAdditionalProperties, default_schema.DeepCopy());
const string16 kPathSuffix =
kTestPolicyKey + ASCIIToUTF16("\\3rdparty\\extensions\\test");
EXPECT_TRUE(WriteSchema(schema, HKEY_CURRENT_USER, kPathSuffix, kSchema));
// Write some test values.
base::DictionaryValue policy;
// These special values have a specific schema for them.
policy.SetInteger("special-int1", 123);
policy.SetString("special-int2", "-456");
// Other values default to be loaded as doubles.
policy.SetInteger("double1", 789.0);
policy.SetString("double2", "123.456e7");
policy.SetString("invalid", "omg");
EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER, kPathSuffix, kMandatory));
base::DictionaryValue expected_policy;
expected_policy.SetInteger("special-int1", 123);
expected_policy.SetInteger("special-int2", -456);
expected_policy.SetDouble("double1", 789.0);
expected_policy.SetDouble("double2", 123.456e7);
expected_policy.Set("invalid", base::Value::CreateNullValue());
PolicyBundle expected;
expected.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "test"))
.LoadFrom(&expected_policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, AppliedPolicyNotPresent) {
InstallRegistrySentinel();
gpo_list_ = NULL;
gpo_list_status_ = ERROR_SUCCESS;
PolicyBundle empty;
EXPECT_TRUE(Matches(empty));
}
TEST_F(PolicyLoaderWinTest, AppliedPolicyEmpty) {
InstallRegistrySentinel();
base::FilePath gpo_dir(test_data_dir_.AppendASCII("empty"));
GROUP_POLICY_OBJECT gpo;
InitGPO(&gpo, 0, gpo_dir, NULL, NULL);
gpo_list_ = &gpo;
gpo_list_status_ = ERROR_SUCCESS;
PolicyBundle empty;
EXPECT_TRUE(Matches(empty));
}
TEST_F(PolicyLoaderWinTest, AppliedPolicyNonExistingFile) {
InstallRegistrySentinel();
GROUP_POLICY_OBJECT gpo;
InitGPO(&gpo, 0, test_data_dir_, NULL, NULL);
gpo_list_ = &gpo;
gpo_list_status_ = ERROR_SUCCESS;
EXPECT_TRUE(MatchesRegistrySentinel());
}
TEST_F(PolicyLoaderWinTest, AppliedPolicyBadPath) {
InstallRegistrySentinel();
base::FilePath gpo_dir(test_data_dir_.AppendASCII("bad"));
GROUP_POLICY_OBJECT gpo;
InitGPO(&gpo, 0, gpo_dir, NULL, NULL);
gpo_list_ = &gpo;
gpo_list_status_ = ERROR_SUCCESS;
EXPECT_TRUE(MatchesRegistrySentinel());
}
TEST_F(PolicyLoaderWinTest, AppliedPolicyPresent) {
InstallRegistrySentinel();
base::FilePath gpo_dir(test_data_dir_.AppendASCII("test1"));
GROUP_POLICY_OBJECT gpo;
InitGPO(&gpo, 0, gpo_dir, NULL, NULL);
gpo_list_ = &gpo;
gpo_list_status_ = ERROR_SUCCESS;
EXPECT_TRUE(MatchesTestBundle());
}
TEST_F(PolicyLoaderWinTest, AppliedPolicyMerged) {
InstallRegistrySentinel();
base::FilePath gpo1_dir(test_data_dir_.AppendASCII("test2"));
base::FilePath gpo2_dir(test_data_dir_.AppendASCII("test1"));
GROUP_POLICY_OBJECT gpo1;
GROUP_POLICY_OBJECT gpo2;
InitGPO(&gpo1, 0, gpo1_dir, &gpo2, NULL);
InitGPO(&gpo2, 0, gpo2_dir, NULL, &gpo1);
gpo_list_ = &gpo1;
gpo_list_status_ = ERROR_SUCCESS;
EXPECT_TRUE(MatchesTestBundle());
}
TEST_F(PolicyLoaderWinTest, AppliedPolicyDisabled) {
InstallRegistrySentinel();
base::FilePath gpo1_dir(test_data_dir_.AppendASCII("test1"));
base::FilePath gpo2_dir(test_data_dir_.AppendASCII("test2"));
GROUP_POLICY_OBJECT gpo1;
GROUP_POLICY_OBJECT gpo2;
InitGPO(&gpo1, 0, gpo1_dir, &gpo2, NULL);
InitGPO(&gpo2, GPO_FLAG_DISABLE, gpo2_dir, NULL, &gpo1);
gpo_list_ = &gpo1;
gpo_list_status_ = ERROR_SUCCESS;
EXPECT_TRUE(MatchesTestBundle());
}
TEST_F(PolicyLoaderWinTest, AppliedPolicyForcedPolicy) {
InstallRegistrySentinel();
base::FilePath gpo1_dir(test_data_dir_.AppendASCII("test1"));
base::FilePath gpo2_dir(test_data_dir_.AppendASCII("test2"));
GROUP_POLICY_OBJECT gpo1;
GROUP_POLICY_OBJECT gpo2;
InitGPO(&gpo1, GPO_FLAG_FORCE, gpo1_dir, &gpo2, NULL);
InitGPO(&gpo2, 0, gpo2_dir, NULL, &gpo1);
gpo_list_ = &gpo1;
gpo_list_status_ = ERROR_SUCCESS;
EXPECT_TRUE(MatchesTestBundle());
}
TEST_F(PolicyLoaderWinTest, AppliedPolicyUNCPath) {
InstallRegistrySentinel();
base::FilePath gpo_dir(test_data_dir_.AppendASCII("test1"));
base::FilePath unc_path(L"\\\\some_share\\GPO");
GROUP_POLICY_OBJECT gpo1;
GROUP_POLICY_OBJECT gpo2;
InitGPO(&gpo1, 0, gpo_dir, &gpo2, NULL);
InitGPO(&gpo2, 0, unc_path, NULL, &gpo1);
gpo_list_ = &gpo1;
gpo_list_status_ = ERROR_SUCCESS;
EXPECT_TRUE(MatchesRegistrySentinel());
}
TEST_F(PolicyLoaderWinTest, LoadExtensionPolicyAlternativeSpelling) {
base::FilePath gpo_dir(
test_data_dir_.AppendASCII("extension_alternative_spelling"));
GROUP_POLICY_OBJECT gpo;
InitGPO(&gpo, 0, gpo_dir, NULL, NULL);
gpo_list_ = &gpo;
gpo_list_status_ = ERROR_SUCCESS;
PolicyBundle expected;
base::DictionaryValue expected_a;
expected_a.SetInteger("policy 1", 3);
expected_a.SetInteger("policy 2", 3);
expected.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
.LoadFrom(&expected_a, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
base::DictionaryValue expected_b;
expected_b.SetInteger("policy 1", 2);
expected.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"))
.LoadFrom(&expected_b, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
EXPECT_TRUE(Matches(expected));
}
} // namespace policy