Index: chrome/browser/policy/policy_loader_win.cc |
diff --git a/chrome/browser/policy/policy_loader_win.cc b/chrome/browser/policy/policy_loader_win.cc |
index df5754eda04dc3be261e6b4daacb9f2a13303d80..262de182c0bab4dbddb6e01522dc920e352b2f7a 100644 |
--- a/chrome/browser/policy/policy_loader_win.cc |
+++ b/chrome/browser/policy/policy_loader_win.cc |
@@ -34,11 +34,23 @@ namespace policy { |
namespace { |
+// Path separator for registry keys. |
+const wchar_t kPathSep[] = L"\\"; |
+ |
// Suffix of kRegistryMandatorySubKey where 3rd party policies are stored. |
const char k3rdPartyPolicySubKey[] = "\\3rdparty\\"; |
-// Path separator for registry keys. |
-const wchar_t kPathSep[] = L"\\"; |
+// Registry value that contains a component's schema. |
+const wchar_t kSchema[] = L"schema"; |
+ |
+// Entry in a JSON schema that contains the expected type of an entry. |
+const char kType[] = "type"; |
+ |
+// Entry in a JSON schema that describes the expected properties of an object. |
+const char kProperties[] = "properties"; |
+ |
+// Entry in a JSON schema that describes the expected type of array elements. |
+const char kItems[] = "items"; |
// Map of registry hives to their corresponding policy scope, in decreasing |
// order of priority. |
@@ -133,9 +145,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 +159,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 +175,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 +189,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 +203,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 +228,183 @@ 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; |
+} |
+ |
+// Converts string |value| to another |type|, if possible. |
+base::Value* ConvertComponentStringValue(const string16& value, |
+ base::Value::Type type) { |
+ switch (type) { |
+ case base::Value::TYPE_DICTIONARY: |
+ return base::JSONReader::Read(UTF16ToUTF8(value)); |
+ |
+ case base::Value::TYPE_NULL: |
+ return base::Value::CreateNullValue(); |
+ |
+ 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); |
+ |
Mattias Nissler (ping if slow)
2012/06/27 12:07:17
what about TYPE_LIST and TYPE_INTEGER? Should they
Joao da Silva
2012/06/27 14:31:15
Sure, done. Also added a conversion to TYPE_BOOLEA
|
+ default: |
Mattias Nissler (ping if slow)
2012/06/27 12:07:17
I'd list the remaining enum constants here and rem
Joao da Silva
2012/06/27 14:31:15
Done.
|
+ DLOG(WARNING) << "Cannot convert REG_SZ entry to type " << type; |
+ 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_DOUBLE: |
+ return base::Value::CreateDoubleValue(value); |
+ |
+ case base::Value::TYPE_INTEGER: |
+ return base::Value::CreateIntegerValue(value); |
+ |
+ default: |
Mattias Nissler (ping if slow)
2012/06/27 12:07:17
same comment regarding remaining constants, defaul
Joao da Silva
2012/06/27 14:31:15
Done.
|
+ DLOG(WARNING) << "Cannot convert REG_DWORD entry to type " << type; |
+ 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's |
+// elements. 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; |
+ |
+ 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; |
+ base::DictionaryValue* sub_schema = NULL; |
+ if (type == base::Value::TYPE_DICTIONARY) { |
+ value = ReadComponentDictionaryValue(hive, path + kPathSep + name, |
+ GetEntry(schema, kProperties)); |
+ } else if (type == base::Value::TYPE_LIST) { |
+ value = ReadComponentListValue(hive, path + kPathSep + name, |
+ GetEntry(schema, kItems)); |
+ } 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 +413,44 @@ 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 = GetEntry(schema, name); |
Mattias Nissler (ping if slow)
2012/06/27 12:07:17
What about the case of a dictionary with arbitrary
Joao da Silva
2012/06/27 14:31:15
Great idea, and json-schema even has something for
|
+ base::Value::Type type = GetType(sub_schema, GetDefaultFor(it.Type())); |
+ base::Value* value = |
+ ReadComponentSimpleValue(&key, name16, it.Type(), type); |
Mattias Nissler (ping if slow)
2012/06/27 12:07:17
Why can't this be an encoded dictionary or list?
Joao da Silva
2012/06/27 14:31:15
Because we're using a RegistryValueIterator, so th
|
+ 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 = GetEntry(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) { |
+ sub_schema = GetEntry(sub_schema, kProperties); |
+ value = ReadComponentDictionaryValue(hive, sub_path, sub_schema); |
+ } else if (type == base::Value::TYPE_LIST) { |
+ sub_schema = GetEntry(sub_schema, kItems); |
+ 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 +458,38 @@ 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; |
+ } |
+ base::DictionaryValue* properties = GetEntry(dict, kProperties); |
+ if (!properties) |
+ return NULL; |
+ value.release(); |
+ return properties; |
+} |
+ |
} // namespace |
PolicyLoaderWin::PolicyLoaderWin(const PolicyDefinitionList* policy_list) |
@@ -323,23 +541,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: |
@@ -390,12 +608,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( |