Implement admin control (by policy) over which extensions the user can install and run. Currently we intercept in two locations: 

1) When the user tries to install an extension (includes error message). 
2) Every time the browser starts up and the currently installed extensions are loaded (silently skips the extension). 

I implemented a whitelist and a blacklist approach, calling it a allow/deny list to not clash with the global blacklist we have for extensions. A blacklist of '*' means all extensions are blacklisted. If an extension is on the blacklist it cannot be installed/loaded, unless it also appears on the whitelist. 

I also fleshed out the LIST_TYPE support for policy values, so that we don't have to use comma separated REG_SZ values for lists and can instead use ADM support for listboxes. 

BUG=47085
TEST=ExtensionsServiceTest.BlacklistedByPolicyWillNotInstall, ConfigurationPolicyProviderWinTest.TestExtensionInstall*

Review URL: http://codereview.chromium.org/3014053

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@55727 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index ddc1404..b24a02d 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -201,6 +201,12 @@
     return;
   }
 
+  if (!frontend_->extension_prefs()->IsExtensionAllowedByPolicy(
+      extension_->id())) {
+    ReportFailureFromUIThread("This extension is blacklisted by admin policy.");
+    return;
+  }
+
   GURL overlapping_url;
   Extension* overlapping_extension =
       frontend_->GetExtensionByOverlappingWebExtent(extension_->web_extent());
diff --git a/chrome/browser/extensions/extension_prefs.cc b/chrome/browser/extensions/extension_prefs.cc
index 1f4f3e8a9..f8dbc59 100644
--- a/chrome/browser/extensions/extension_prefs.cc
+++ b/chrome/browser/extensions/extension_prefs.cc
@@ -45,6 +45,13 @@
 // object read from the Preferences file, containing a list of toolstrip URLs.
 const wchar_t kExtensionShelf[] = L"extensions.shelf";
 
+// A preference that tracks admin policy regarding which extensions the user
+// can and can not install. This preference is a list object, containing
+// strings that list extension ids. Denylist can contain "*" meaning all
+// extensions.
+const wchar_t kExtensionInstallAllowList[] = L"extensions.install.allowlist";
+const wchar_t kExtensionInstallDenyList[] = L"extensions.install.denylist";
+
 // A preference that tracks browser action toolbar configuration. This is a list
 // object stored in the Preferences file. The extensions are stored by ID.
 const wchar_t kExtensionToolbar[] = L"extensions.toolbar";
@@ -233,6 +240,41 @@
   return ReadExtensionPrefBoolean(extension_id, kPrefBlacklist);
 }
 
+bool ExtensionPrefs::IsExtensionAllowedByPolicy(
+    const std::string& extension_id) {
+  std::string string_value;
+
+  // Check the whitelist first.
+  const ListValue* whitelist = prefs_->GetList(kExtensionInstallAllowList);
+  if (whitelist) {
+    for (ListValue::const_iterator it = whitelist->begin();
+         it != whitelist->end(); ++it) {
+      if (!(*it)->GetAsString(&string_value))
+        LOG(WARNING) << "Failed to read whitelist string.";
+      else if (string_value == extension_id)
+        return true;
+    }
+  }
+
+  // Then check the blacklist (the admin blacklist, not the Google blacklist).
+  const ListValue* blacklist = prefs_->GetList(kExtensionInstallDenyList);
+  if (blacklist) {
+    for (ListValue::const_iterator it = blacklist->begin();
+         it != blacklist->end(); ++it) {
+      if (!(*it)->GetAsString(&string_value)) {
+        LOG(WARNING) << "Failed to read blacklist string.";
+      } else {
+        if (string_value == "*")
+          return false;  // Only whitelisted extensions are allowed.
+        if (string_value == extension_id)
+          return false;
+      }
+    }
+  }
+
+  return true;
+}
+
 bool ExtensionPrefs::DidExtensionEscalatePermissions(
     const std::string& extension_id) {
   return ReadExtensionPrefBoolean(extension_id,
@@ -247,7 +289,7 @@
 }
 
 void ExtensionPrefs::UpdateBlacklist(
-  const std::set<std::string>& blacklist_set) {
+    const std::set<std::string>& blacklist_set) {
   std::vector<std::string> remove_pref_ids;
   std::set<std::string> used_id_set;
   const DictionaryValue* extensions = prefs_->GetDictionary(kExtensionsPref);
@@ -785,4 +827,6 @@
   prefs->RegisterListPref(kExtensionToolbar);
   prefs->RegisterIntegerPref(prefs::kExtensionToolbarSize, -1);
   prefs->RegisterDictionaryPref(kExtensionsBlacklistUpdate);
+  prefs->RegisterListPref(kExtensionInstallAllowList);
+  prefs->RegisterListPref(kExtensionInstallDenyList);
 }
diff --git a/chrome/browser/extensions/extension_prefs.h b/chrome/browser/extensions/extension_prefs.h
index 3ab50ee7..f61cdc1 100644
--- a/chrome/browser/extensions/extension_prefs.h
+++ b/chrome/browser/extensions/extension_prefs.h
@@ -90,6 +90,10 @@
   // Based on extension id, checks prefs to see if it is blacklisted.
   bool IsExtensionBlacklisted(const std::string& id);
 
+  // Is the extension with |extension_id| allowed by policy (checking both
+  // whitelist and blacklist).
+  bool IsExtensionAllowedByPolicy(const std::string& extension_id);
+
   // Returns the last value set via SetLastPingDay. If there isn't such a
   // pref, the returned Time will return true for is_null().
   base::Time LastPingDay(const std::string& extension_id) const;
@@ -149,7 +153,6 @@
   PrefService* pref_service() const { return prefs_; }
 
  private:
-
   // Converts absolute paths in the pref to paths relative to the
   // install_directory_.
   void MakePathsRelative();
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index dcfc4813f..fde71aa6 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -567,7 +567,9 @@
                                                bool write_to_prefs) {
   std::string error;
   Extension* extension = NULL;
-  if (info.extension_manifest.get()) {
+  if (!extension_prefs_->IsExtensionAllowedByPolicy(info.extension_id)) {
+    error = errors::kDisabledByPolicy;
+  } else if (info.extension_manifest.get()) {
     scoped_ptr<Extension> tmp(new Extension(info.extension_path));
     bool require_key = info.extension_location != Extension::LOAD;
     if (tmp->InitFromValue(*info.extension_manifest, require_key, &error))
diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc
index 08f8c22..012e8d63 100644
--- a/chrome/browser/extensions/extensions_service_unittest.cc
+++ b/chrome/browser/extensions/extensions_service_unittest.cc
@@ -1523,6 +1523,37 @@
   EXPECT_NE(std::string(good0), loaded_[1]->id());
 }
 
+// Will not install extension blacklisted by policy.
+TEST_F(ExtensionsServiceTest, BlacklistedByPolicyWillNotInstall) {
+  InitializeEmptyExtensionsService();
+
+  ListValue* whitelist = prefs_->GetMutableList(
+      L"extensions.install.allowlist");
+  ListValue* blacklist = prefs_->GetMutableList(
+      L"extensions.install.denylist");
+  ASSERT_TRUE(whitelist != NULL && blacklist != NULL);
+
+  // Blacklist everything.
+  blacklist->Append(Value::CreateStringValue("*"));
+
+  // Blacklist prevents us from installing good_crx.
+  FilePath extensions_path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+  extensions_path = extensions_path.AppendASCII("extensions");
+  FilePath path = extensions_path.AppendASCII("good.crx");
+  service_->InstallExtension(path);
+  loop_.RunAllPending();
+  EXPECT_EQ(0u, service_->extensions()->size());
+
+  // Now whitelist this particular extension.
+  whitelist->Append(Value::CreateStringValue(good_crx));
+
+  // Ensure we can now install good_crx.
+  service_->InstallExtension(path);
+  loop_.RunAllPending();
+  EXPECT_EQ(1u, service_->extensions()->size());
+}
+
 // Tests disabling extensions
 TEST_F(ExtensionsServiceTest, DisableExtension) {
   InitializeEmptyExtensionsService();
diff --git a/chrome/browser/policy/configuration_policy_pref_store.cc b/chrome/browser/policy/configuration_policy_pref_store.cc
index b6fd1e6d..744ee70 100644
--- a/chrome/browser/policy/configuration_policy_pref_store.cc
+++ b/chrome/browser/policy/configuration_policy_pref_store.cc
@@ -43,6 +43,10 @@
       prefs::kMetricsReportingEnabled },
   { Value::TYPE_STRING, kPolicyApplicationLocale,
       prefs::kApplicationLocale},
+  { Value::TYPE_LIST, kPolicyExtensionInstallAllowList,
+      prefs::kExtensionInstallAllowList},
+  { Value::TYPE_LIST, kPolicyExtensionInstallDenyList,
+      prefs::kExtensionInstallDenyList},
 };
 
 const ConfigurationPolicyPrefStore::PolicyToPreferenceMapEntry
diff --git a/chrome/browser/policy/configuration_policy_provider.cc b/chrome/browser/policy/configuration_policy_provider.cc
index 99c61cf..748c3a2 100644
--- a/chrome/browser/policy/configuration_policy_provider.cc
+++ b/chrome/browser/policy/configuration_policy_provider.cc
@@ -50,6 +50,10 @@
       Value::TYPE_STRING, policy::key::kApplicationLocaleValue },
   { ConfigurationPolicyStore::kPolicySyncDisabled,
       Value::TYPE_BOOLEAN, policy::key::kSyncDisabled },
+  { ConfigurationPolicyStore::kPolicyExtensionInstallAllowList,
+      Value::TYPE_LIST, policy::key::kExtensionInstallAllowList },
+  { ConfigurationPolicyStore::kPolicyExtensionInstallDenyList,
+      Value::TYPE_LIST, policy::key::kExtensionInstallDenyList },
 };
 
 }  // namespace
diff --git a/chrome/browser/policy/configuration_policy_provider_win.cc b/chrome/browser/policy/configuration_policy_provider_win.cc
index 4085b140..eb6d92fd 100644
--- a/chrome/browser/policy/configuration_policy_provider_win.cc
+++ b/chrome/browser/policy/configuration_policy_provider_win.cc
@@ -12,7 +12,9 @@
 #include "base/object_watcher.h"
 #include "base/registry.h"
 #include "base/scoped_ptr.h"
+#include "base/string_number_conversions.h"
 #include "base/string_piece.h"
+#include "base/string_util.h"
 #include "base/sys_string_conversions.h"
 #include "base/utf_string_conversions.h"
 #include "base/values.h"
@@ -56,53 +58,66 @@
 }
 
 bool ConfigurationPolicyProviderWin::GetRegistryPolicyString(
-    const wchar_t* value_name, string16* result) {
+    const string16& name, int index, string16* result) {
   DWORD value_size = 0;
   DWORD key_type = 0;
   scoped_array<uint8> buffer;
-  RegKey hkcu_policy_key(HKEY_LOCAL_MACHINE, policy::kRegistrySubKey);
-  if (hkcu_policy_key.ReadValue(value_name, 0, &value_size, &key_type)) {
-    if (key_type != REG_SZ)
-      return false;
-    // According to the Microsoft documentation, the string
-    // buffer may not be explicitly 0-terminated. Allocate a
-    // slightly larger buffer and prefill to zeros to guarantee
-    // the 0-termination.
-    buffer.reset(new uint8[value_size + 2]);
-    memset(buffer.get(), 0, value_size + 2);
-    hkcu_policy_key.ReadValue(value_name, buffer.get(), &value_size);
-  } else {
-    RegKey hklm_policy_key(HKEY_CURRENT_USER, policy::kRegistrySubKey);
-    if (hklm_policy_key.ReadValue(value_name, 0, &value_size, &key_type)) {
-      if (key_type != REG_SZ)
-        return false;
-      // According to the Microsoft documentation, the string
-      // buffer may not be explicitly 0-terminated. Allocate a
-      // slightly larger buffer and prefill to zeros to guarantee
-      // the 0-termination.
-      buffer.reset(new uint8[value_size + 2]);
-      memset(buffer.get(), 0, value_size + 2);
-      hklm_policy_key.ReadValue(value_name, buffer.get(), &value_size);
-    } else {
+  RegKey policy_key;
+  string16 location = string16(policy::kRegistrySubKey);
+  string16 value_name = name;
+
+  if (index > 0) {
+    // This is a list value, treat |name| as a subkey.
+    location += ASCIIToUTF16("\\") + name;
+    value_name = base::IntToString16(index);
+  }
+
+  // First try the global policy.
+  if (!policy_key.Open(HKEY_LOCAL_MACHINE, location.c_str()) ||
+      !policy_key.ReadValue(value_name.c_str(), 0, &value_size, &key_type)) {
+    policy_key.Close();
+    // Fall back on user-specific policy.
+    if (!policy_key.Open(HKEY_CURRENT_USER, location.c_str()) ||
+        !policy_key.ReadValue(value_name.c_str(), 0, &value_size, &key_type)) {
       return false;
     }
   }
 
+  if (key_type != REG_SZ)
+    return false;
+
+  // According to the Microsoft documentation, the string
+  // buffer may not be explicitly 0-terminated. Allocate a
+  // slightly larger buffer and pre-fill to zeros to guarantee
+  // the 0-termination.
+  buffer.reset(new uint8[value_size + 2]);
+  memset(buffer.get(), 0, value_size + 2);
+  policy_key.ReadValue(value_name.c_str(), buffer.get(), &value_size);
   result->assign(reinterpret_cast<const wchar_t*>(buffer.get()));
   return true;
 }
 
+
+bool ConfigurationPolicyProviderWin::GetRegistryPolicyStringList(
+    const string16& key, ListValue* result) {
+  int index = 0;
+  string16 policy_string;
+  while (GetRegistryPolicyString(key, ++index,  &policy_string))
+    result->Append(Value::CreateStringValue(policy_string));
+  return true;
+}
+
 bool ConfigurationPolicyProviderWin::GetRegistryPolicyBoolean(
-    const wchar_t* value_name, bool* result) {
+    const string16& value_name, bool* result) {
   DWORD value;
   RegKey hkcu_policy_key(HKEY_LOCAL_MACHINE, policy::kRegistrySubKey);
-  if (hkcu_policy_key.ReadValueDW(value_name, &value)) {
+  if (hkcu_policy_key.ReadValueDW(value_name.c_str(), &value)) {
     *result = value != 0;
     return true;
   }
 
   RegKey hklm_policy_key(HKEY_CURRENT_USER, policy::kRegistrySubKey);
-  if (hklm_policy_key.ReadValueDW(value_name, &value)) {
+  if (hklm_policy_key.ReadValueDW(value_name.c_str(), &value)) {
     *result = value != 0;
     return true;
   }
@@ -110,16 +125,16 @@
 }
 
 bool ConfigurationPolicyProviderWin::GetRegistryPolicyInteger(
-    const wchar_t* value_name, uint32* result) {
+    const string16& value_name, uint32* result) {
   DWORD value;
   RegKey hkcu_policy_key(HKEY_LOCAL_MACHINE, policy::kRegistrySubKey);
-  if (hkcu_policy_key.ReadValueDW(value_name, &value)) {
+  if (hkcu_policy_key.ReadValueDW(value_name.c_str(), &value)) {
     *result = value;
     return true;
   }
 
   RegKey hklm_policy_key(HKEY_CURRENT_USER, policy::kRegistrySubKey);
-  if (hklm_policy_key.ReadValueDW(value_name, &value)) {
+  if (hklm_policy_key.ReadValueDW(value_name.c_str(), &value)) {
     *result = value;
     return true;
   }
@@ -133,29 +148,37 @@
   for (PolicyValueMap::const_iterator current = mapping->begin();
        current != mapping->end(); ++current) {
     std::wstring name = UTF8ToWide(current->name);
-    std::wstring string_value;
-    uint32 int_value;
-    bool bool_value;
     switch (current->value_type) {
-      case Value::TYPE_STRING:
-        if (GetRegistryPolicyString(name.c_str(), &string_value)) {
-          store->Apply(
-              current->policy_type,
-              Value::CreateStringValue(string_value));
+      case Value::TYPE_STRING: {
+        std::wstring string_value;
+        if (GetRegistryPolicyString(name.c_str(), -1, &string_value)) {
+          store->Apply(current->policy_type,
+                       Value::CreateStringValue(string_value));
         }
         break;
-      case Value::TYPE_BOOLEAN:
+      }
+      case Value::TYPE_LIST: {
+        scoped_ptr<ListValue> list_value(new ListValue);
+        if (GetRegistryPolicyStringList(name.c_str(), list_value.get()))
+          store->Apply(current->policy_type, list_value.release());
+        break;
+      }
+      case Value::TYPE_BOOLEAN: {
+        bool bool_value;
         if (GetRegistryPolicyBoolean(name.c_str(), &bool_value)) {
           store->Apply(current->policy_type,
                        Value::CreateBooleanValue(bool_value));
         }
         break;
-      case Value::TYPE_INTEGER:
+      }
+      case Value::TYPE_INTEGER: {
+        uint32 int_value;
         if (GetRegistryPolicyInteger(name.c_str(), &int_value)) {
           store->Apply(current->policy_type,
                        Value::CreateIntegerValue(int_value));
         }
         break;
+      }
       default:
         NOTREACHED();
         return false;
diff --git a/chrome/browser/policy/configuration_policy_provider_win.h b/chrome/browser/policy/configuration_policy_provider_win.h
index e2ebbdb..e197e9d0 100644
--- a/chrome/browser/policy/configuration_policy_provider_win.h
+++ b/chrome/browser/policy/configuration_policy_provider_win.h
@@ -51,11 +51,22 @@
  private:
   scoped_ptr<GroupPolicyChangeWatcher> watcher_;
 
-  // Methods to perfrom type-specific policy lookups in the registry.
+  // Methods to perform type-specific policy lookups in the registry.
   // HKLM is checked first, then HKCU.
-  bool GetRegistryPolicyString(const wchar_t* value_name, string16* result);
-  bool GetRegistryPolicyBoolean(const wchar_t* value_name, bool* result);
-  bool GetRegistryPolicyInteger(const wchar_t* value_name, uint32* result);
+
+  // Reads a string registry value |name| and puts the resulting string in
+  // |result|. If |index| > 0, |name| is the name of a subkey and the value
+  // read is named |index|. Note: A subkey is used for lists to work around
+  // a problem with the Group Policy Editor, where one list value overwrites
+  // another if they appear under the same key (even if they have different
+  // names).
+  bool GetRegistryPolicyString(const string16& name,
+                               int index,
+                               string16* result);
+  // Gets a list value contained under |key| one level below the policy root.
+  bool GetRegistryPolicyStringList(const string16& key, ListValue* result);
+  bool GetRegistryPolicyBoolean(const string16& value_name, bool* result);
+  bool GetRegistryPolicyInteger(const string16& value_name, uint32* result);
 };
 
 #endif  // CHROME_BROWSER_POLICY_CONFIGURATION_POLICY_PROVIDER_WIN_H_
diff --git a/chrome/browser/policy/configuration_policy_provider_win_unittest.cc b/chrome/browser/policy/configuration_policy_provider_win_unittest.cc
index 221ef83..1614a59f 100644
--- a/chrome/browser/policy/configuration_policy_provider_win_unittest.cc
+++ b/chrome/browser/policy/configuration_policy_provider_win_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/registry.h"
 #include "base/scoped_ptr.h"
 #include "base/stl_util-inl.h"
+#include "base/string_number_conversions.h"
 #include "base/string_piece.h"
 #include "base/utf_string_conversions.h"
 #include "chrome/browser/policy/configuration_policy_provider_win.h"
@@ -40,6 +41,8 @@
   void SetBooleanPolicy(ConfigurationPolicyStore::PolicyType type,
                         HKEY hive, bool value);
   void SetCookiesMode(HKEY hive, uint32 value);
+  void AllowExtension(HKEY hive, int index, const wchar_t* id);
+  void DenyExtension(HKEY hive, int index, const wchar_t* id);
 
   typedef std::vector<PolicyValueMapEntry> PolicyValueMap;
   static const PolicyValueMap* PolicyValueMapping() {
@@ -79,6 +82,26 @@
       5));
 }
 
+void TestConfigurationPolicyProviderWin::AllowExtension(HKEY hive,
+                                                        int index,
+                                                        const wchar_t* id) {
+  RegKey key(hive,
+             (string16(policy::kRegistrySubKey) + ASCIIToUTF16("\\") +
+                 ASCIIToUTF16(policy::key::kExtensionInstallAllowList)).c_str(),
+             KEY_ALL_ACCESS);
+  EXPECT_TRUE(key.WriteValue(base::IntToString16(index).c_str(), id));
+}
+
+void TestConfigurationPolicyProviderWin::DenyExtension(HKEY hive,
+                                                       int index,
+                                                       const wchar_t* id) {
+  RegKey key(hive,
+             (string16(policy::kRegistrySubKey) + ASCIIToUTF16("\\") +
+                 ASCIIToUTF16(policy::key::kExtensionInstallDenyList)).c_str(),
+             KEY_ALL_ACCESS);
+  EXPECT_TRUE(key.WriteValue(base::IntToString16(index).c_str(), id));
+}
+
 void TestConfigurationPolicyProviderWin::SetBooleanPolicy(
     ConfigurationPolicyStore::PolicyType type,
     HKEY hive,
@@ -284,6 +307,89 @@
   TestBooleanPolicy(ConfigurationPolicyStore::kPolicyHomepageIsNewTabPage);
 }
 
+TEST_F(ConfigurationPolicyProviderWinTest, TestExtensionInstallWhitelistHKCU) {
+  MockConfigurationPolicyStore store;
+  TestConfigurationPolicyProviderWin provider;
+  provider.AllowExtension(HKEY_CURRENT_USER, 1, L"abc");
+  provider.AllowExtension(HKEY_CURRENT_USER, 2, L"def");
+  provider.Provide(&store);
+
+  const MockConfigurationPolicyStore::PolicyMap& map(store.policy_map());
+  MockConfigurationPolicyStore::PolicyMap::const_iterator i =
+      map.find(ConfigurationPolicyStore::kPolicyExtensionInstallAllowList);
+  ASSERT_TRUE(i != map.end());
+  ASSERT_TRUE(i->second->IsType(Value::TYPE_LIST));
+  ListValue* value = reinterpret_cast<ListValue*>(i->second);
+  std::string str_value;
+  EXPECT_EQ(2, value->GetSize());
+  EXPECT_TRUE(value->GetString(0, &str_value));
+  EXPECT_STREQ("abc", str_value.c_str());
+  EXPECT_TRUE(value->GetString(1, &str_value));
+  EXPECT_STREQ("def", str_value.c_str());
+}
+
+TEST_F(ConfigurationPolicyProviderWinTest, TestExtensionInstallWhitelistHKLM) {
+  MockConfigurationPolicyStore store;
+  TestConfigurationPolicyProviderWin provider;
+  provider.AllowExtension(HKEY_LOCAL_MACHINE, 1, L"abc");
+  provider.AllowExtension(HKEY_LOCAL_MACHINE, 2, L"def");
+  provider.Provide(&store);
+
+  const MockConfigurationPolicyStore::PolicyMap& map(store.policy_map());
+  MockConfigurationPolicyStore::PolicyMap::const_iterator i =
+      map.find(ConfigurationPolicyStore::kPolicyExtensionInstallAllowList);
+  ASSERT_TRUE(i != map.end());
+  ASSERT_TRUE(i->second->IsType(Value::TYPE_LIST));
+  ListValue* value = reinterpret_cast<ListValue*>(i->second);
+  std::string str_value;
+  EXPECT_EQ(2, value->GetSize());
+  EXPECT_TRUE(value->GetString(0, &str_value));
+  EXPECT_STREQ("abc", str_value.c_str());
+  EXPECT_TRUE(value->GetString(1, &str_value));
+  EXPECT_STREQ("def", str_value.c_str());
+}
+
+TEST_F(ConfigurationPolicyProviderWinTest,
+       TestExtensionInstallWhitelistHKLMOverHKCU) {
+  MockConfigurationPolicyStore store;
+  TestConfigurationPolicyProviderWin provider;
+  provider.AllowExtension(HKEY_CURRENT_USER, 1, L"abc");
+  provider.AllowExtension(HKEY_LOCAL_MACHINE, 1, L"def");
+  provider.Provide(&store);
+
+  const MockConfigurationPolicyStore::PolicyMap& map(store.policy_map());
+  MockConfigurationPolicyStore::PolicyMap::const_iterator i =
+      map.find(ConfigurationPolicyStore::kPolicyExtensionInstallAllowList);
+  ASSERT_TRUE(i != map.end());
+  ASSERT_TRUE(i->second->IsType(Value::TYPE_LIST));
+  ListValue* value = reinterpret_cast<ListValue*>(i->second);
+  std::string str_value;
+  EXPECT_EQ(1, value->GetSize());
+  EXPECT_TRUE(value->GetString(0, &str_value));
+  EXPECT_STREQ("def", str_value.c_str());
+}
+
+TEST_F(ConfigurationPolicyProviderWinTest, TestExtensionInstallBlacklistHKLM) {
+  MockConfigurationPolicyStore store;
+  TestConfigurationPolicyProviderWin provider;
+  provider.DenyExtension(HKEY_LOCAL_MACHINE, 1, L"abc");
+  provider.DenyExtension(HKEY_LOCAL_MACHINE, 2, L"def");
+  provider.Provide(&store);
+
+  const MockConfigurationPolicyStore::PolicyMap& map(store.policy_map());
+  MockConfigurationPolicyStore::PolicyMap::const_iterator i =
+      map.find(ConfigurationPolicyStore::kPolicyExtensionInstallDenyList);
+  ASSERT_TRUE(i != map.end());
+  ASSERT_TRUE(i->second->IsType(Value::TYPE_LIST));
+  ListValue* value = reinterpret_cast<ListValue*>(i->second);
+  std::string str_value;
+  EXPECT_EQ(2, value->GetSize());
+  EXPECT_TRUE(value->GetString(0, &str_value));
+  EXPECT_STREQ("abc", str_value.c_str());
+  EXPECT_TRUE(value->GetString(1, &str_value));
+  EXPECT_STREQ("def", str_value.c_str());
+}
+
 TEST_F(ConfigurationPolicyProviderWinTest,
     TestPolicyAlternateErrorPagesEnabled) {
   TestBooleanPolicy(
diff --git a/chrome/browser/policy/configuration_policy_store.h b/chrome/browser/policy/configuration_policy_store.h
index 80d9a56..4f876fec 100644
--- a/chrome/browser/policy/configuration_policy_store.h
+++ b/chrome/browser/policy/configuration_policy_store.h
@@ -32,6 +32,8 @@
     kPolicyPasswordManagerEnabled,
     kPolicySyncDisabled,
     kPolicyApplicationLocale,
+    kPolicyExtensionInstallAllowList,
+    kPolicyExtensionInstallDenyList,
 
     // A policy for allowing administrators to forcibly disable
     // specific plugins. This policy is a comma-separated list of
@@ -45,7 +47,7 @@
   static const int kPolicyManuallyConfiguredProxyMode = 2;
   static const int kPolicyUseSystemProxyMode = 3;
 
-  // A |ConfigurationPolicyProvider| specifes the value of a policy setting
+  // A |ConfigurationPolicyProvider| specifies the value of a policy setting
   // through a call to |Apply|.
   // The configuration policy pref store takes over the ownership of |value|.
   virtual void Apply(PolicyType policy, Value* value) = 0;
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index 4001c9b..ce4d74f 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 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.
 
@@ -92,6 +92,8 @@
     "The extensions gallery cannot be scripted.";
 const char* kChromeVersionTooLow =
     "This extension requires * version * or greater.";
+const char* kDisabledByPolicy =
+    "This extension has been disabled by your administrator.";
 const char* kDevToolsExperimental =
     "You must request the 'experimental' permission in order to use the"
     " DevTools API.";
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index 4cd9779..bc85b3c 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 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.
 
@@ -101,6 +101,7 @@
   extern const char* kInvalidCssList;
   extern const char* kInvalidDefaultLocale;
   extern const char* kInvalidDescription;
+  extern const char* kDisabledByPolicy;
   extern const char* kInvalidDevToolsPage;
   extern const char* kInvalidGlob;
   extern const char* kInvalidGlobList;
diff --git a/chrome/common/policy_constants.cc b/chrome/common/policy_constants.cc
index 7c0b5b4..8f56108 100644
--- a/chrome/common/policy_constants.cc
+++ b/chrome/common/policy_constants.cc
@@ -31,6 +31,8 @@
 const char kDisabledPluginsList[] = "DisabledPluginsList";
 const char kApplicationLocaleValue[] = "ApplicationLocaleValue";
 const char kSyncDisabled[] = "SyncDisabled";
+const char kExtensionInstallAllowList[] = "ExtensionInstallWhitelist";
+const char kExtensionInstallDenyList[] = "ExtensionInstallBlacklist";
 
 }  // namespace key
 
diff --git a/chrome/common/policy_constants.h b/chrome/common/policy_constants.h
index 37107db..591a9f1 100644
--- a/chrome/common/policy_constants.h
+++ b/chrome/common/policy_constants.h
@@ -33,6 +33,8 @@
 extern const char kDisabledPluginsList[];
 extern const char kApplicationLocaleValue[];
 extern const char kSyncDisabled[];
+extern const char kExtensionInstallAllowList[];
+extern const char kExtensionInstallDenyList[];
 
 }  // namespace key
 
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index cd47625..4a3d79c 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -814,6 +814,14 @@
 const char kBrowserActionContainerWidth[] =
     "extensions.browseractions.container.width";
 
+// A whitelist of extension the user can install. This is controlled by the
+// administrator.
+const char kExtensionInstallAllowList[] = "extensions.install.allowlist";
+// A blacklist, containing extensions the user cannot install.  This is
+// controlled by the administrator. This list should not be confused with
+// the extension blacklist, which is Google controlled.
+const char kExtensionInstallDenyList[] = "extensions.install.denylist";
+
 // Time of the last, and next scheduled, extensions auto-update checks.
 const char kLastExtensionsUpdateCheck[] = "extensions.autoupdate.last_check";
 const char kNextExtensionsUpdateCheck[] = "extensions.autoupdate.next_check";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 61e87bf..1e20e76 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -299,8 +299,8 @@
 extern const char kShowExtensionShelf[];
 extern const char kBrowserActionContainerWidth[];
 
-extern const char kLastExtensionsUpdateCheck[];
-extern const char kNextExtensionsUpdateCheck[];
+extern const char kExtensionInstallAllowList[];
+extern const char kExtensionInstallDenyList[];
 
 extern const char kExtensionBlacklistUpdateVersion[];