diff options
author | joaodasilva@chromium.org <joaodasilva@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-28 11:22:06 +0000 |
---|---|---|
committer | joaodasilva@chromium.org <joaodasilva@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-28 11:22:06 +0000 |
commit | 00591dd79fb7232c54b24d64c84e978b3944858a (patch) | |
tree | da3e4b51c2f8570d40ababe92e11c454c6a29ee9 /chrome/browser/policy | |
parent | da9011c6377f05411065d9ed49b7a8cee9a0c66f (diff) | |
download | chromium_src-00591dd79fb7232c54b24d64c84e978b3944858a.zip chromium_src-00591dd79fb7232c54b24d64c84e978b3944858a.tar.gz chromium_src-00591dd79fb7232c54b24d64c84e978b3944858a.tar.bz2 |
Use a schema to decode 3rd party policy on windows, when present.
BUG=108994
TEST=unit_tests green
Review URL: https://chromiumcodereview.appspot.com/10656046
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@144703 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/policy')
-rw-r--r-- | chrome/browser/policy/config_dir_policy_loader_unittest.cc | 83 | ||||
-rw-r--r-- | chrome/browser/policy/configuration_policy_provider_test.cc | 67 | ||||
-rw-r--r-- | chrome/browser/policy/configuration_policy_provider_test.h | 17 | ||||
-rw-r--r-- | chrome/browser/policy/policy_loader_mac_unittest.cc | 3 | ||||
-rw-r--r-- | chrome/browser/policy/policy_loader_win.cc | 368 | ||||
-rw-r--r-- | chrome/browser/policy/policy_loader_win.h | 26 | ||||
-rw-r--r-- | chrome/browser/policy/policy_loader_win_unittest.cc | 366 |
7 files changed, 758 insertions, 172 deletions
diff --git a/chrome/browser/policy/config_dir_policy_loader_unittest.cc b/chrome/browser/policy/config_dir_policy_loader_unittest.cc index 6c996d8..a2e7b91 100644 --- a/chrome/browser/policy/config_dir_policy_loader_unittest.cc +++ b/chrome/browser/policy/config_dir_policy_loader_unittest.cc @@ -45,6 +45,8 @@ class TestHarness : public PolicyProviderTestHarness { virtual void InstallDictionaryPolicy( const std::string& policy_name, const base::DictionaryValue* policy_value) OVERRIDE; + virtual void Install3rdPartyPolicy( + const base::DictionaryValue* policies) OVERRIDE; const FilePath& test_dir() { return test_dir_.path(); } @@ -52,16 +54,22 @@ class TestHarness : public PolicyProviderTestHarness { void WriteConfigFile(const base::DictionaryValue& dict, const std::string& file_name); + // Returns a unique name for a policy file. Each subsequent call returns a new + // name that comes lexicographically after the previous one. + std::string NextConfigFileName(); + static PolicyProviderTestHarness* Create(); private: ScopedTempDir test_dir_; + int next_policy_file_index_; DISALLOW_COPY_AND_ASSIGN(TestHarness); }; TestHarness::TestHarness() - : PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE) {} + : PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE), + next_policy_file_index_(100) {} TestHarness::~TestHarness() {} @@ -78,35 +86,35 @@ ConfigurationPolicyProvider* TestHarness::CreateProvider( void TestHarness::InstallEmptyPolicy() { base::DictionaryValue dict; - WriteConfigFile(dict, "policy"); + WriteConfigFile(dict, NextConfigFileName()); } void TestHarness::InstallStringPolicy(const std::string& policy_name, const std::string& policy_value) { base::DictionaryValue dict; dict.SetString(policy_name, policy_value); - WriteConfigFile(dict, "policy"); + WriteConfigFile(dict, NextConfigFileName()); } void TestHarness::InstallIntegerPolicy(const std::string& policy_name, int policy_value) { base::DictionaryValue dict; dict.SetInteger(policy_name, policy_value); - WriteConfigFile(dict, "policy"); + WriteConfigFile(dict, NextConfigFileName()); } void TestHarness::InstallBooleanPolicy(const std::string& policy_name, bool policy_value) { base::DictionaryValue dict; dict.SetBoolean(policy_name, policy_value); - WriteConfigFile(dict, "policy"); + WriteConfigFile(dict, NextConfigFileName()); } void TestHarness::InstallStringListPolicy(const std::string& policy_name, const base::ListValue* policy_value) { base::DictionaryValue dict; dict.Set(policy_name, policy_value->DeepCopy()); - WriteConfigFile(dict, "policy"); + WriteConfigFile(dict, NextConfigFileName()); } void TestHarness::InstallDictionaryPolicy( @@ -114,7 +122,13 @@ void TestHarness::InstallDictionaryPolicy( const base::DictionaryValue* policy_value) { base::DictionaryValue dict; dict.Set(policy_name, policy_value->DeepCopy()); - WriteConfigFile(dict, "policy"); + WriteConfigFile(dict, NextConfigFileName()); +} + +void TestHarness::Install3rdPartyPolicy(const base::DictionaryValue* policies) { + base::DictionaryValue dict; + dict.Set("3rdparty", policies->DeepCopy()); + WriteConfigFile(dict, NextConfigFileName()); } void TestHarness::WriteConfigFile(const base::DictionaryValue& dict, @@ -129,6 +143,11 @@ void TestHarness::WriteConfigFile(const base::DictionaryValue& dict, file_util::WriteFile(file_path, data.c_str(), data.size())); } +std::string TestHarness::NextConfigFileName() { + EXPECT_LE(next_policy_file_index_, 999); + return std::string("policy") + base::IntToString(next_policy_file_index_++); +} + // static PolicyProviderTestHarness* TestHarness::Create() { return new TestHarness(); @@ -142,6 +161,12 @@ INSTANTIATE_TEST_CASE_P( ConfigurationPolicyProviderTest, testing::Values(TestHarness::Create)); +// Instantiate abstract test case for 3rd party policy reading tests. +INSTANTIATE_TEST_CASE_P( + ConfigDir3rdPartyPolicyLoaderTest, + Configuration3rdPartyPolicyProviderTest, + testing::Values(TestHarness::Create)); + // Some tests that exercise special functionality in ConfigDirPolicyLoader. class ConfigDirPolicyLoaderTest : public PolicyTestBase { protected: @@ -200,48 +225,4 @@ TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsMergePrefs) { EXPECT_TRUE(bundle->Equals(expected_bundle)); } -// Tests loading of policy for 3rd parties. -TEST_F(ConfigDirPolicyLoaderTest, Load3rdParty) { - base::DictionaryValue policy_dict; - policy_dict.SetBoolean("bool", true); - policy_dict.SetString("str", "string value"); - policy_dict.SetDouble("double", 123.456); - policy_dict.SetInteger("int", 789); - - base::ListValue* list = new base::ListValue(); - for (int i = 0; i < 5; ++i) { - base::DictionaryValue* dict = new base::DictionaryValue(); - dict->SetInteger("subdictindex", i); - dict->Set("subdict", policy_dict.DeepCopy()); - list->Append(dict); - } - policy_dict.Set("list", list); - - base::DictionaryValue json_dict; - // Merge |policy_dict|, which will become the chrome policies. - json_dict.MergeDictionary(&policy_dict); - json_dict.Set("3rdparty.extensions.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - policy_dict.DeepCopy()); - json_dict.Set("3rdparty.extensions.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - policy_dict.DeepCopy()); - - harness_.WriteConfigFile(json_dict, "policy.json"); - ConfigDirPolicyLoader loader(harness_.test_dir(), POLICY_SCOPE_USER); - scoped_ptr<PolicyBundle> bundle(loader.Load()); - ASSERT_TRUE(bundle.get()); - PolicyMap expected_policy; - expected_policy.LoadFrom(&policy_dict, - POLICY_LEVEL_MANDATORY, - POLICY_SCOPE_USER); - PolicyBundle expected_bundle; - expected_bundle.Get(POLICY_DOMAIN_CHROME, "").CopyFrom(expected_policy); - expected_bundle.Get(POLICY_DOMAIN_EXTENSIONS, - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") - .CopyFrom(expected_policy); - expected_bundle.Get(POLICY_DOMAIN_EXTENSIONS, - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") - .CopyFrom(expected_policy); - EXPECT_TRUE(bundle->Equals(expected_bundle)); -} - } // namespace policy diff --git a/chrome/browser/policy/configuration_policy_provider_test.cc b/chrome/browser/policy/configuration_policy_provider_test.cc index c6c9ab1..5e6d3a4 100644 --- a/chrome/browser/policy/configuration_policy_provider_test.cc +++ b/chrome/browser/policy/configuration_policy_provider_test.cc @@ -66,6 +66,11 @@ PolicyScope PolicyProviderTestHarness::policy_scope() const { return scope_; } +void PolicyProviderTestHarness::Install3rdPartyPolicy( + const base::DictionaryValue* policies) { + FAIL(); +} + ConfigurationPolicyProviderTest::ConfigurationPolicyProviderTest() {} ConfigurationPolicyProviderTest::~ConfigurationPolicyProviderTest() {} @@ -256,4 +261,66 @@ TEST(ConfigurationPolicyProviderTest, FixDeprecatedPolicies) { EXPECT_TRUE(provider.policies().Equals(expected_bundle)); } +Configuration3rdPartyPolicyProviderTest:: + Configuration3rdPartyPolicyProviderTest() {} + +Configuration3rdPartyPolicyProviderTest:: + ~Configuration3rdPartyPolicyProviderTest() {} + +TEST_P(Configuration3rdPartyPolicyProviderTest, Load3rdParty) { + base::DictionaryValue policy_dict; + policy_dict.SetBoolean("bool", true); + policy_dict.SetDouble("double", 123.456); + policy_dict.SetInteger("int", 789); + policy_dict.SetString("str", "string value"); + + base::ListValue* list = new base::ListValue(); + for (int i = 0; i < 2; ++i) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("subdictindex", i); + dict->Set("subdict", policy_dict.DeepCopy()); + list->Append(dict); + } + policy_dict.Set("list", list); + policy_dict.Set("dict", policy_dict.DeepCopy()); + + // Install these policies as a Chrome policy. + test_harness_->InstallDictionaryPolicy( + test_policy_definitions::kKeyDictionary, &policy_dict); + // Install them as 3rd party policies too. + base::DictionaryValue policy_3rdparty; + policy_3rdparty.Set("extensions.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + policy_dict.DeepCopy()); + policy_3rdparty.Set("extensions.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + policy_dict.DeepCopy()); + // Install invalid 3rd party policies that shouldn't be loaded. These also + // help detecting memory leaks in the code paths that detect invalid input. + policy_3rdparty.Set("invalid-domain.component", policy_dict.DeepCopy()); + policy_3rdparty.Set("extensions.cccccccccccccccccccccccccccccccc", + base::Value::CreateStringValue("invalid-value")); + test_harness_->Install3rdPartyPolicy(&policy_3rdparty); + + provider_->RefreshPolicies(); + loop_.RunAllPending(); + + PolicyMap expected_policy; + expected_policy.Set(test_policy_definitions::kKeyDictionary, + test_harness_->policy_level(), + test_harness_->policy_scope(), + policy_dict.DeepCopy()); + PolicyBundle expected_bundle; + expected_bundle.Get(POLICY_DOMAIN_CHROME, "").CopyFrom(expected_policy); + expected_policy.Clear(); + expected_policy.LoadFrom(&policy_dict, + test_harness_->policy_level(), + test_harness_->policy_scope()); + expected_bundle.Get(POLICY_DOMAIN_EXTENSIONS, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + .CopyFrom(expected_policy); + expected_bundle.Get(POLICY_DOMAIN_EXTENSIONS, + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") + .CopyFrom(expected_policy); + EXPECT_TRUE(provider_->policies().Equals(expected_bundle)); +} + } // namespace policy diff --git a/chrome/browser/policy/configuration_policy_provider_test.h b/chrome/browser/policy/configuration_policy_provider_test.h index a24519c..8b330f1 100644 --- a/chrome/browser/policy/configuration_policy_provider_test.h +++ b/chrome/browser/policy/configuration_policy_provider_test.h @@ -97,6 +97,10 @@ class PolicyProviderTestHarness { const std::string& policy_name, const base::DictionaryValue* policy_value) = 0; + // Not every provider supports installing 3rd party policy. Those who do + // should override this method; the default just makes the test fail. + virtual void Install3rdPartyPolicy(const base::DictionaryValue* policies); + private: PolicyLevel level_; PolicyScope scope_; @@ -133,6 +137,19 @@ class ConfigurationPolicyProviderTest DISALLOW_COPY_AND_ASSIGN(ConfigurationPolicyProviderTest); }; +// An extension of ConfigurationPolicyProviderTest that also tests loading of +// 3rd party policy. Policy provider implementations that support loading of +// 3rd party policy should also instantiate these tests. +class Configuration3rdPartyPolicyProviderTest + : public ConfigurationPolicyProviderTest { + protected: + Configuration3rdPartyPolicyProviderTest(); + virtual ~Configuration3rdPartyPolicyProviderTest(); + + private: + DISALLOW_COPY_AND_ASSIGN(Configuration3rdPartyPolicyProviderTest); +}; + } // namespace policy #endif // CHROME_BROWSER_POLICY_CONFIGURATION_POLICY_PROVIDER_TEST_H_ diff --git a/chrome/browser/policy/policy_loader_mac_unittest.cc b/chrome/browser/policy/policy_loader_mac_unittest.cc index 4d90a3e..32006d7 100644 --- a/chrome/browser/policy/policy_loader_mac_unittest.cc +++ b/chrome/browser/policy/policy_loader_mac_unittest.cc @@ -220,6 +220,9 @@ INSTANTIATE_TEST_CASE_P( ConfigurationPolicyProviderTest, testing::Values(TestHarness::Create)); +// TODO(joaodasilva): instantiate Configuration3rdPartyPolicyProviderTest too +// once the mac loader supports 3rd party policy. http://crbug.com/108995 + // Special test cases for some mac preferences details. class PolicyLoaderMacTest : public PolicyTestBase { protected: diff --git a/chrome/browser/policy/policy_loader_win.cc b/chrome/browser/policy/policy_loader_win.cc index df5754e..36a44fe 100644 --- a/chrome/browser/policy/policy_loader_win.cc +++ b/chrome/browser/policy/policy_loader_win.cc @@ -29,16 +29,23 @@ using base::win::RegKey; using base::win::RegistryKeyIterator; using base::win::RegistryValueIterator; +using namespace policy::registry_constants; namespace policy { -namespace { - -// Suffix of kRegistryMandatorySubKey where 3rd party policies are stored. -const char k3rdPartyPolicySubKey[] = "\\3rdparty\\"; +namespace registry_constants { + 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 char kType[] = "type"; + const char kProperties[] = "properties"; + const char kAdditionalProperties[] = "additionalProperties"; + const char kItems[] = "items"; +} // namespace registry_constants -// Path separator for registry keys. -const wchar_t kPathSep[] = L"\\"; +namespace { // Map of registry hives to their corresponding policy scope, in decreasing // order of priority. @@ -80,7 +87,7 @@ bool LoadHighestPriorityKey(const string16& key_path, for (size_t h = 0; h < arraysize(kHives); ++h) { string16 path(kKeyPaths[k].path); if (!key_path.empty()) - path += ASCIIToUTF16("\\") + key_path; + path += kPathSep + key_path; key->Open(kHives[h].hive, path.c_str(), KEY_READ); if (!key->Valid()) continue; @@ -133,9 +140,9 @@ bool ReadRegistryInteger(RegKey* key, // Returns the Value for a Chrome string policy named |name|, or NULL if // it wasn't found. The caller owns the returned value. -base::Value* ReadStringValue(const string16& name, - PolicyLevel* level, - PolicyScope* scope) { +base::Value* ReadChromeStringValue(const string16& name, + PolicyLevel* level, + PolicyScope* scope) { RegKey key; if (!LoadHighestPriorityKey(string16(), name, &key, level, scope)) return NULL; @@ -147,9 +154,9 @@ base::Value* ReadStringValue(const string16& name, // Returns the Value for a Chrome string list policy named |name|, // or NULL if it wasn't found. The caller owns the returned value. -base::Value* ReadStringListValue(const string16& name, - PolicyLevel* level, - PolicyScope* scope) { +base::Value* ReadChromeStringListValue(const string16& name, + PolicyLevel* level, + PolicyScope* scope) { RegKey key; if (!LoadHighestPriorityKey(name, string16(), &key, level, scope)) return NULL; @@ -163,9 +170,9 @@ base::Value* ReadStringListValue(const string16& name, // Returns the Value for a Chrome boolean policy named |name|, // or NULL if it wasn't found. The caller owns the returned value. -base::Value* ReadBooleanValue(const string16& name, - PolicyLevel* level, - PolicyScope* scope) { +base::Value* ReadChromeBooleanValue(const string16& name, + PolicyLevel* level, + PolicyScope* scope) { RegKey key; if (!LoadHighestPriorityKey(string16(), name, &key, level, scope)) return NULL; @@ -177,9 +184,9 @@ base::Value* ReadBooleanValue(const string16& name, // Returns the Value for a Chrome integer policy named |name|, // or NULL if it wasn't found. The caller owns the returned value. -base::Value* ReadIntegerValue(const string16& name, - PolicyLevel* level, - PolicyScope* scope) { +base::Value* ReadChromeIntegerValue(const string16& name, + PolicyLevel* level, + PolicyScope* scope) { RegKey key; if (!LoadHighestPriorityKey(string16(), name, &key, level, scope)) return NULL; @@ -191,9 +198,9 @@ base::Value* ReadIntegerValue(const string16& name, // Returns the Value for a Chrome dictionary policy named |name|, // or NULL if it wasn't found. The caller owns the returned value. -base::Value* ReadDictionaryValue(const string16& name, - PolicyLevel* level, - PolicyScope* scope) { +base::Value* ReadChromeDictionaryValue(const string16& name, + PolicyLevel* level, + PolicyScope* scope) { // Dictionaries are encoded as JSON strings on Windows. // // A dictionary could be stored as a subkey, with each of its entries @@ -216,10 +223,219 @@ base::Value* ReadDictionaryValue(const string16& name, return base::JSONReader::Read(UTF16ToUTF8(value)); } -// Loads the dictionary at |path| in the given |hive|. Ownership is transferred -// to the caller. -base::DictionaryValue* ReadRegistryDictionaryValue(HKEY hive, - const string16& path) { +// Returns the Value type described in |schema|, or |default_type| if not found. +base::Value::Type GetType(const base::DictionaryValue* schema, + base::Value::Type default_type) { + // JSON-schema types to base::Value::Type mapping. + static const struct { + // JSON schema type. + const char* schema_type; + // Correspondent value type. + base::Value::Type value_type; + } kSchemaToValueTypeMap[] = { + { "array", base::Value::TYPE_LIST }, + { "boolean", base::Value::TYPE_BOOLEAN }, + { "integer", base::Value::TYPE_INTEGER }, + { "null", base::Value::TYPE_NULL }, + { "number", base::Value::TYPE_DOUBLE }, + { "object", base::Value::TYPE_DICTIONARY }, + { "string", base::Value::TYPE_STRING }, + }; + + if (!schema) + return default_type; + std::string type; + if (!schema->GetString(kType, &type)) + return default_type; + for (size_t i = 0; i < arraysize(kSchemaToValueTypeMap); ++i) { + if (type == kSchemaToValueTypeMap[i].schema_type) + return kSchemaToValueTypeMap[i].value_type; + } + return default_type; +} + +// Returns the default type for registry entries of |reg_type|, when there is +// no schema defined type for a policy. +base::Value::Type GetDefaultFor(DWORD reg_type) { + return reg_type == REG_DWORD ? base::Value::TYPE_INTEGER : + base::Value::TYPE_STRING; +} + +// Returns the entry with key |name| in |dictionary| (can be NULL), or NULL. +base::DictionaryValue* GetEntry(const base::DictionaryValue* dictionary, + const std::string& name) { + if (!dictionary) + return NULL; + base::DictionaryValue* entry = NULL; + dictionary->GetDictionary(name, &entry); + return entry; +} + +// Returns the schema for property |name| given the |schema| of an object. +// Returns the "additionalProperties" schema if no specific schema for +// |name| is present. Returns NULL if no schema is found. +base::DictionaryValue* GetSchemaFor(const base::DictionaryValue* schema, + const std::string& name) { + base::DictionaryValue* properties = GetEntry(schema, kProperties); + base::DictionaryValue* sub_schema = GetEntry(properties, name); + if (sub_schema) + return sub_schema; + // "additionalProperties" can be a boolean, but that case is ignored. + return GetEntry(schema, kAdditionalProperties); +} + +// Converts string |value| to another |type|, if possible. +base::Value* ConvertComponentStringValue(const string16& value, + base::Value::Type type) { + switch (type) { + case base::Value::TYPE_NULL: + return base::Value::CreateNullValue(); + + case base::Value::TYPE_BOOLEAN: { + int int_value; + if (base::StringToInt(value, &int_value)) + return base::Value::CreateBooleanValue(int_value != 0); + return NULL; + } + + case base::Value::TYPE_INTEGER: { + int int_value; + if (base::StringToInt(value, &int_value)) + return base::Value::CreateIntegerValue(int_value); + return NULL; + } + + case base::Value::TYPE_DOUBLE: { + double double_value; + if (base::StringToDouble(UTF16ToUTF8(value), &double_value)) + return base::Value::CreateDoubleValue(double_value); + DLOG(WARNING) << "Failed to read policy value as double: " << value; + return NULL; + } + + case base::Value::TYPE_STRING: + return base::Value::CreateStringValue(value); + + case base::Value::TYPE_DICTIONARY: + case base::Value::TYPE_LIST: + return base::JSONReader::Read(UTF16ToUTF8(value)); + + case base::Value::TYPE_BINARY: + DLOG(WARNING) << "Cannot convert REG_SZ entry to type " << type; + return NULL; + } + NOTREACHED(); + return NULL; +} + +// Converts an integer |value| to another |type|, if possible. +base::Value* ConvertComponentIntegerValue(uint32 value, + base::Value::Type type) { + switch (type) { + case base::Value::TYPE_BOOLEAN: + return base::Value::CreateBooleanValue(value != 0); + + case base::Value::TYPE_INTEGER: + return base::Value::CreateIntegerValue(value); + + case base::Value::TYPE_DOUBLE: + return base::Value::CreateDoubleValue(value); + + case base::Value::TYPE_NULL: + case base::Value::TYPE_STRING: + case base::Value::TYPE_BINARY: + case base::Value::TYPE_DICTIONARY: + case base::Value::TYPE_LIST: + DLOG(WARNING) << "Cannot convert REG_DWORD entry to type " << type; + return NULL; + } + NOTREACHED(); + return NULL; +} + +// Reads a simple (non-Dictionary, non-Array) value from the registry |key| +// named |name| with registry type |reg_type| as a value of type |type|. +// Returns NULL if the value could not be loaded or converted. +base::Value* ReadComponentSimpleValue(RegKey* key, + const string16& name, + DWORD reg_type, + base::Value::Type type) { + switch (reg_type) { + case REG_SZ: { + string16 value; + if (ReadRegistryString(key, name, &value)) + return ConvertComponentStringValue(value, type); + break; + } + + case REG_DWORD: { + uint32 value; + if (ReadRegistryInteger(key, name, &value)) + return ConvertComponentIntegerValue(value, type); + break; + } + + default: + DLOG(WARNING) << "Registry type not supported for key " << name; + break; + } + NOTREACHED(); + return NULL; +} + +// Forward declaration for ReadComponentListValue(). +base::DictionaryValue* ReadComponentDictionaryValue( + HKEY hive, + const string16& path, + const base::DictionaryValue* schema); + +// Loads the list at |path| in the given |hive|. |schema| is a JSON schema +// (http://json-schema.org/) that describes the expected type of the list. +// Ownership of the result is transferred to the caller. +base::ListValue* ReadComponentListValue(HKEY hive, + const string16& path, + const base::DictionaryValue* schema) { + // The sub-elements are indexed from 1 to N. They can be represented as + // registry values or registry keys though; use |schema| first to try to + // determine the right type, and if that fails default to STRING. + + RegKey key(hive, path.c_str(), KEY_READ); + if (!key.Valid()) + return NULL; + + // Get the schema for list items. + schema = GetEntry(schema, kItems); + base::Value::Type type = GetType(schema, base::Value::TYPE_STRING); + base::ListValue* list = new base::ListValue(); + for (int i = 1; ; ++i) { + string16 name = base::IntToString16(i); + base::Value* value = NULL; + if (type == base::Value::TYPE_DICTIONARY) { + value = + ReadComponentDictionaryValue(hive, path + kPathSep + name, schema); + } else if (type == base::Value::TYPE_LIST) { + value = ReadComponentListValue(hive, path + kPathSep + name, schema); + } else { + DWORD reg_type; + key.ReadValue(name.c_str(), NULL, NULL, ®_type); + value = ReadComponentSimpleValue(&key, name, reg_type, type); + } + if (!value) + break; + list->Append(value); + } + return list; +} + +// Loads the dictionary at |path| in the given |hive|. |schema| is a JSON +// schema (http://json-schema.org/) that describes the expected types for the +// dictionary entries. When the type for a certain entry isn't described in the +// schema, a default conversion takes place. |schema| can be NULL. +// Ownership of the result is transferred to the caller. +base::DictionaryValue* ReadComponentDictionaryValue( + HKEY hive, + const string16& path, + const base::DictionaryValue* schema) { // A "value" in the registry is like a file in a filesystem, and a "key" is // like a directory, that contains other "values" and "keys". // Unfortunately it is possible to have a name both as a "value" and a "key". @@ -228,43 +444,42 @@ base::DictionaryValue* ReadRegistryDictionaryValue(HKEY hive, // First iterate over all the "values" in |path| and convert them; then // recurse into each "key" in |path| and convert them as dictionaries. - base::DictionaryValue* dict = new base::DictionaryValue(); RegKey key(hive, path.c_str(), KEY_READ); + if (!key.Valid()) + return NULL; + base::DictionaryValue* dict = new base::DictionaryValue(); for (RegistryValueIterator it(hive, path.c_str()); it.Valid(); ++it) { - string16 name(it.Name()); - switch (it.Type()) { - case REG_SZ: { - string16 value; - if (ReadRegistryString(&key, name, &value)) - dict->SetString(UTF16ToUTF8(name), value); - break; - } - - case REG_DWORD: { - uint32 value; - if (ReadRegistryInteger(&key, name, &value)) - dict->SetInteger(UTF16ToUTF8(name), value); - break; - } - - default: - // TODO(joaodasilva): use a schema to determine the correct types. - LOG(WARNING) << "Ignoring registry value with unsupported type: " - << path << kPathSep << name; - } + string16 name16(it.Name()); + std::string name(UTF16ToUTF8(name16)); + const base::DictionaryValue* sub_schema = GetSchemaFor(schema, name); + base::Value::Type type = GetType(sub_schema, GetDefaultFor(it.Type())); + base::Value* value = + ReadComponentSimpleValue(&key, name16, it.Type(), type); + if (value) + dict->Set(name, value); } for (RegistryKeyIterator it(hive, path.c_str()); it.Valid(); ++it) { string16 name16(it.Name()); std::string name(UTF16ToUTF8(name16)); if (dict->HasKey(name)) { - LOG(WARNING) << "Ignoring registry key because a value exists with the " - "same name: " << path << kPathSep << name; + DLOG(WARNING) << "Ignoring registry key because a value exists with the " + "same name: " << path << kPathSep << name; continue; } - base::DictionaryValue* value = - ReadRegistryDictionaryValue(hive, path + kPathSep + name16); + + base::DictionaryValue* sub_schema = GetSchemaFor(schema, name); + base::Value::Type type = GetType(sub_schema, base::Value::TYPE_DICTIONARY); + base::Value* value = NULL; + const string16 sub_path = path + kPathSep + name16; + if (type == base::Value::TYPE_DICTIONARY) { + value = ReadComponentDictionaryValue(hive, sub_path, sub_schema); + } else if (type == base::Value::TYPE_LIST) { + value = ReadComponentListValue(hive, sub_path, sub_schema); + } else { + DLOG(WARNING) << "Can't read a simple type in registry key at " << path; + } if (value) dict->Set(name, value); } @@ -272,6 +487,35 @@ base::DictionaryValue* ReadRegistryDictionaryValue(HKEY hive, return dict; } +// Reads a JSON schema from the given |registry_value|, at the given +// |registry_key| in |hive|. |registry_value| must be a string (REG_SZ), and +// is decoded as JSON data. Returns NULL on failure. Ownership is transferred +// to the caller. +base::DictionaryValue* ReadRegistrySchema(HKEY hive, + const string16& registry_key, + const string16& registry_value) { + RegKey key(hive, registry_key.c_str(), KEY_READ); + string16 schema; + if (!ReadRegistryString(&key, registry_value, &schema)) + return NULL; + // A JSON schema is represented in JSON too. + scoped_ptr<base::Value> value(base::JSONReader::Read(UTF16ToUTF8(schema))); + if (!value.get()) + return NULL; + base::DictionaryValue* dict = NULL; + if (!value->GetAsDictionary(&dict)) + return NULL; + // The top-level entry must be an object, and each of its properties maps + // a policy name to its schema. + if (GetType(dict, base::Value::TYPE_DICTIONARY) != + base::Value::TYPE_DICTIONARY) { + DLOG(WARNING) << "schema top-level type isn't \"object\""; + return NULL; + } + value.release(); + return dict; +} + } // namespace PolicyLoaderWin::PolicyLoaderWin(const PolicyDefinitionList* policy_list) @@ -323,23 +567,23 @@ void PolicyLoaderWin::LoadChromePolicy(PolicyMap* chrome_policies) { switch (current->value_type) { case base::Value::TYPE_STRING: - value = ReadStringValue(name, &level, &scope); + value = ReadChromeStringValue(name, &level, &scope); break; case base::Value::TYPE_LIST: - value = ReadStringListValue(name, &level, &scope); + value = ReadChromeStringListValue(name, &level, &scope); break; case base::Value::TYPE_BOOLEAN: - value = ReadBooleanValue(name, &level, &scope); + value = ReadChromeBooleanValue(name, &level, &scope); break; case base::Value::TYPE_INTEGER: - value = ReadIntegerValue(name, &level, &scope); + value = ReadChromeIntegerValue(name, &level, &scope); break; case base::Value::TYPE_DICTIONARY: - value = ReadDictionaryValue(name, &level, &scope); + value = ReadChromeDictionaryValue(name, &level, &scope); break; default: @@ -374,8 +618,8 @@ void PolicyLoaderWin::Load3rdPartyPolicies(PolicyBundle* bundle) { }; // Path where policies for components are stored. - const string16 kPathPrefix = - kRegistryMandatorySubKey + ASCIIToUTF16(k3rdPartyPolicySubKey); + const string16 kPathPrefix = string16(kRegistryMandatorySubKey) + kPathSep + + kThirdParty + kPathSep; for (size_t h = 0; h < arraysize(kHives); ++h) { HKEY hkey = kHives[h].hive; @@ -390,12 +634,16 @@ void PolicyLoaderWin::Load3rdPartyPolicies(PolicyBundle* bundle) { string16 component(domain_iterator.Name()); string16 component_path = domain_path + kPathSep + component; + // Load the schema for this component's policy, if present. + scoped_ptr<base::DictionaryValue> schema( + ReadRegistrySchema(hkey, component_path, kSchema)); + for (size_t k = 0; k < arraysize(kKeyPaths); ++k) { string16 path = component_path + kPathSep + ASCIIToUTF16(kKeyPaths[k].path); scoped_ptr<base::DictionaryValue> dictionary( - ReadRegistryDictionaryValue(hkey, path)); + ReadComponentDictionaryValue(hkey, path, schema.get())); if (dictionary.get()) { PolicyMap policies; policies.LoadFrom( diff --git a/chrome/browser/policy/policy_loader_win.h b/chrome/browser/policy/policy_loader_win.h index a90dd1a..94c9965 100644 --- a/chrome/browser/policy/policy_loader_win.h +++ b/chrome/browser/policy/policy_loader_win.h @@ -50,6 +50,32 @@ class PolicyLoaderWin : public AsyncPolicyLoader, DISALLOW_COPY_AND_ASSIGN(PolicyLoaderWin); }; +// Constants shared with tests. +namespace registry_constants { + // Path separator for registry keys. + extern const wchar_t kPathSep[]; + // Registry key within Chrome's key that contains 3rd party policy. + extern const wchar_t kThirdParty[]; + // Registry key within an extension's namespace that contains mandatory + // policy. + extern const wchar_t kMandatory[]; + // Registry key within an extension's namespace that contains recommended + // policy. + extern const wchar_t kRecommended[]; + // Registry key within an extension's namespace that contains the policy + // schema. + extern const wchar_t kSchema[]; + // Key in a JSON schema that indicates the expected type. + extern const char kType[]; + // Key in a JSON schema that indicates the expected properties of an object. + extern const char kProperties[]; + // Key in a JSON schema that indicates the default schema for object + // properties. + extern const char kAdditionalProperties[]; + // Key in a JSON schema that indicates the expected type of list items. + extern const char kItems[]; +} // namespace registry_constants + } // namespace policy #endif // CHROME_BROWSER_POLICY_POLICY_LOADER_WIN_H_ diff --git a/chrome/browser/policy/policy_loader_win_unittest.cc b/chrome/browser/policy/policy_loader_win_unittest.cc index 888fa8a..9173c26 100644 --- a/chrome/browser/policy/policy_loader_win_unittest.cc +++ b/chrome/browser/policy/policy_loader_win_unittest.cc @@ -9,6 +9,7 @@ #include "base/json/json_writer.h" #include "base/string16.h" #include "base/string_number_conversions.h" +#include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/win/registry.h" #include "chrome/browser/policy/async_policy_provider.h" @@ -19,6 +20,7 @@ #include "testing/gtest/include/gtest/gtest.h" using base::win::RegKey; +using namespace policy::registry_constants; namespace policy { @@ -30,54 +32,162 @@ const wchar_t kUnitTestMachineOverrideSubKey[] = const wchar_t kUnitTestUserOverrideSubKey[] = L"SOFTWARE\\Chromium Unit Tests\\HKCU Override"; -// Installs |dict| at the given |path|, in the given |hive|. Currently only -// string, int and dictionary types are converted; other types cause a failure. -// Returns false if there was any failure, and true if |dict| was successfully -// written. -// TODO(joaodasilva): generate a schema for |dict| too, so that all types can -// be retrieved. -bool InstallDictionary(const base::DictionaryValue& dict, - HKEY hive, - const string16& path) { +// 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); - const string16 kPathSep = ASCIIToUTF16("\\"); - - for (base::DictionaryValue::Iterator it(dict); it.HasNext(); it.Advance()) { - string16 name(UTF8ToUTF16(it.key())); - switch (it.value().GetType()) { - case base::Value::TYPE_STRING: { - string16 value; - if (!it.value().GetAsString(&value)) - return false; - if (key.WriteValue(name.c_str(), value.c_str()) != ERROR_SUCCESS) + 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.HasNext(); it.Advance()) { + if (!InstallValue(it.value(), hive, path + kPathSep + name, + UTF8ToUTF16(it.key()))) { return false; - break; + } } + return true; + } - case base::Value::TYPE_INTEGER: { - int value; - if (!it.value().GetAsInteger(&value)) + 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) { + base::Value* item; + if (!list->Get(i, &item)) return false; - if (key.WriteValue(name.c_str(), value) != ERROR_SUCCESS) + if (!InstallValue(*item, hive, path + kPathSep + name, + base::UintToString16(i + 1))) { return false; - break; + } } + return true; + } - case base::Value::TYPE_DICTIONARY: { - const base::DictionaryValue* sub_dict = NULL; - if (!it.value().GetAsDictionary(&sub_dict)) - return false; - if (!InstallDictionary(*sub_dict, hive, path + kPathSep + name)) - return false; - break; + 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(kType, "null"); + break; + case base::Value::TYPE_BOOLEAN: + schema->SetString(kType, "boolean"); + break; + case base::Value::TYPE_INTEGER: + schema->SetString(kType, "integer"); + break; + case base::Value::TYPE_DOUBLE: + schema->SetString(kType, "number"); + break; + case base::Value::TYPE_STRING: + schema->SetString(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(kType, "array"); + schema->Set(kItems, BuildSchema(**list->begin())); } + break; + } - default: - return false; + 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.HasNext(); it.Advance()) { + properties->Set(it.key(), BuildSchema(it.value())); + } + schema->SetString(kType, "object"); + schema->Set(kProperties, properties); + } + break; } + + case base::Value::TYPE_BINARY: + break; } - return true; + 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); + 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 @@ -130,6 +240,8 @@ class TestHarness : public PolicyProviderTestHarness { virtual void InstallDictionaryPolicy( const std::string& policy_name, const base::DictionaryValue* policy_value) OVERRIDE; + virtual void Install3rdPartyPolicy( + const base::DictionaryValue* policies) OVERRIDE; // Creates a harness instance that will install policy in HKCU or HKLM, // respectively. @@ -247,6 +359,30 @@ void TestHarness::InstallDictionaryPolicy( UTF8ToUTF16(json).c_str()); } +void TestHarness::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(kRegistryMandatorySubKey) + kPathSep + + kThirdParty + kPathSep; + for (base::DictionaryValue::Iterator domain(*policies); + domain.HasNext(); domain.Advance()) { + const base::DictionaryValue* components = NULL; + if (!domain.value().GetAsDictionary(&components)) { + ADD_FAILURE(); + continue; + } + for (base::DictionaryValue::Iterator component(*components); + component.HasNext(); component.Advance()) { + const string16 path = string16(kRegistryMandatorySubKey) + kPathSep + + kThirdParty + kPathSep + + UTF8ToUTF16(domain.key()) + kPathSep + + UTF8ToUTF16(component.key()); + InstallValue(component.value(), hive_, path, kMandatory); + EXPECT_TRUE(InstallSchema(component.value(), hive_, path, kSchema)); + } + } +} + // static PolicyProviderTestHarness* TestHarness::CreateHKCU() { return new TestHarness(HKEY_CURRENT_USER, POLICY_SCOPE_USER); @@ -265,12 +401,24 @@ INSTANTIATE_TEST_CASE_P( ConfigurationPolicyProviderTest, testing::Values(TestHarness::CreateHKCU, TestHarness::CreateHKLM)); +// Instantiate abstract test case for 3rd party policy reading tests. +INSTANTIATE_TEST_CASE_P( + ThirdPartyPolicyProviderWinTest, + Configuration3rdPartyPolicyProviderTest, + testing::Values(TestHarness::CreateHKCU, TestHarness::CreateHKLM)); + // Test cases for windows policy provider specific functionality. class PolicyLoaderWinTest : public PolicyTestBase { protected: PolicyLoaderWinTest() {} virtual ~PolicyLoaderWinTest() {} + bool Matches(const PolicyBundle& expected) { + PolicyLoaderWin loader(&test_policy_definitions::kList); + scoped_ptr<PolicyBundle> loaded(loader.Load()); + return loaded->Equals(expected); + } + ScopedGroupPolicyRegistrySandbox registry_sandbox_; }; @@ -282,21 +430,16 @@ TEST_F(PolicyLoaderWinTest, HKLMOverHKCU) { hkcu_key.WriteValue(UTF8ToUTF16(test_policy_definitions::kKeyString).c_str(), UTF8ToUTF16("hkcu").c_str()); - PolicyLoaderWin loader(&test_policy_definitions::kList); - scoped_ptr<PolicyBundle> bundle(loader.Load()); - - PolicyBundle expected_bundle; - expected_bundle.Get(POLICY_DOMAIN_CHROME, "") + PolicyBundle expected; + expected.Get(POLICY_DOMAIN_CHROME, "") .Set(test_policy_definitions::kKeyString, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, base::Value::CreateStringValue("hklm")); - EXPECT_TRUE(bundle->Equals(expected_bundle)); + EXPECT_TRUE(Matches(expected)); } -// TODO(joaodasilva): share tests for 3rd party policy with -// ConfigDirPolicyProvider once PolicyLoaderWin is able to load all types. -TEST_F(PolicyLoaderWinTest, Load3rdParty) { +TEST_F(PolicyLoaderWinTest, Load3rdPartyWithoutSchema) { base::DictionaryValue dict; dict.SetString("str", "string value"); dict.SetInteger("int", 123); @@ -305,22 +448,19 @@ TEST_F(PolicyLoaderWinTest, Load3rdParty) { dict.Set("subsubsubdict", dict.DeepCopy()); base::DictionaryValue policy_dict; - policy_dict.Set("3rdparty.extensions.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.policy", + policy_dict.Set("extensions.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.policy", dict.DeepCopy()); - policy_dict.Set("3rdparty.extensions.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.policy", + policy_dict.Set("extensions.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.policy", dict.DeepCopy()); - EXPECT_TRUE(InstallDictionary(policy_dict, HKEY_LOCAL_MACHINE, - kRegistryMandatorySubKey)); + EXPECT_TRUE(InstallValue(policy_dict, HKEY_LOCAL_MACHINE, + kRegistryMandatorySubKey, kThirdParty)); PolicyBundle expected; expected.Get(POLICY_DOMAIN_EXTENSIONS, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") .LoadFrom(&dict, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE); expected.Get(POLICY_DOMAIN_EXTENSIONS, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") .LoadFrom(&dict, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE); - - PolicyLoaderWin loader(&test_policy_definitions::kList); - scoped_ptr<PolicyBundle> loaded(loader.Load()); - EXPECT_TRUE(loaded->Equals(expected)); + EXPECT_TRUE(Matches(expected)); } TEST_F(PolicyLoaderWinTest, Merge3rdPartyPolicies) { @@ -329,8 +469,6 @@ TEST_F(PolicyLoaderWinTest, Merge3rdPartyPolicies) { const string16 kPathSuffix = kRegistryMandatorySubKey + ASCIIToUTF16("\\3rdparty\\extensions\\merge"); - const string16 kMandatoryPath = kPathSuffix + ASCIIToUTF16("\\policy"); - const string16 kRecommendedPath = kPathSuffix + ASCIIToUTF16("\\recommended"); const char kUserMandatory[] = "user-mandatory"; const char kUserRecommended[] = "user-recommended"; @@ -339,19 +477,23 @@ TEST_F(PolicyLoaderWinTest, Merge3rdPartyPolicies) { base::DictionaryValue policy; policy.SetString("a", kMachineMandatory); - EXPECT_TRUE(InstallDictionary(policy, HKEY_LOCAL_MACHINE, kMandatoryPath)); + EXPECT_TRUE(InstallValue(policy, HKEY_LOCAL_MACHINE, + kPathSuffix, kMandatory)); policy.SetString("a", kUserMandatory); policy.SetString("b", kUserMandatory); - EXPECT_TRUE(InstallDictionary(policy, HKEY_CURRENT_USER, kMandatoryPath)); + EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER, + kPathSuffix, kMandatory)); policy.SetString("a", kMachineRecommended); policy.SetString("b", kMachineRecommended); policy.SetString("c", kMachineRecommended); - EXPECT_TRUE(InstallDictionary(policy, HKEY_LOCAL_MACHINE, kRecommendedPath)); + 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(InstallDictionary(policy, HKEY_CURRENT_USER, kRecommendedPath)); + EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER, + kPathSuffix, kRecommended)); PolicyBundle expected; PolicyMap& expected_policy = expected.Get(POLICY_DOMAIN_EXTENSIONS, "merge"); @@ -363,10 +505,112 @@ TEST_F(PolicyLoaderWinTest, Merge3rdPartyPolicies) { base::Value::CreateStringValue(kMachineRecommended)); expected_policy.Set("d", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER, base::Value::CreateStringValue(kUserRecommended)); + 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 = + kRegistryMandatorySubKey + 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(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 = + kRegistryMandatorySubKey + ASCIIToUTF16("\\3rdparty\\extensions\\int"); + EXPECT_TRUE(InstallSchema(policy, HKEY_CURRENT_USER, kPathSuffix, kSchema)); + EXPECT_TRUE( + InstallValue(encoded_policy, HKEY_CURRENT_USER, kPathSuffix, kMandatory)); - PolicyLoaderWin loader(&test_policy_definitions::kList); - scoped_ptr<PolicyBundle> loaded(loader.Load()); - EXPECT_TRUE(loaded->Equals(expected)); + PolicyBundle expected; + expected.Get(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(kType, "number"); + base::DictionaryValue integer_schema; + integer_schema.SetString(kType, "integer"); + base::DictionaryValue properties; + properties.Set("special-int1", integer_schema.DeepCopy()); + properties.Set("special-int2", integer_schema.DeepCopy()); + base::DictionaryValue schema; + schema.SetString(kType, "object"); + schema.Set(kProperties, properties.DeepCopy()); + schema.Set(kAdditionalProperties, default_schema.DeepCopy()); + + const string16 kPathSuffix = + kRegistryMandatorySubKey + 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); + PolicyBundle expected; + expected.Get(POLICY_DOMAIN_EXTENSIONS, "test") + .LoadFrom(&expected_policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER); + EXPECT_TRUE(Matches(expected)); } } // namespace policy |