Track permissions granted to extensions in prefs

Adds a section to the extension preferences to store the recognized permissions that the user has granted the extension.
Ignores unknown permissions when installing extensions.
Disables extension and prompts user to re-enable when unknown permissions become recognized (e.g., through browser upgrade).
Allows extensions to remove a permission in one version, then add them back in the next without prompting the user.

BUG=42742
TEST=ExtensionsServiceTest, ExtensionPrefsGrantedPermissions, ExtensionTest

Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=67500

Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=67544

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@67616 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc
index e222c27..3adc05b4 100644
--- a/chrome/browser/extensions/extensions_service_unittest.cc
+++ b/chrome/browser/extensions/extensions_service_unittest.cc
@@ -72,6 +72,7 @@
 const char* const page_action = "obcimlgaoabeegjmmpldobjndiealpln";
 const char* const theme_crx = "iamefpfkojoapidjnbafmgkgncegbkad";
 const char* const theme2_crx = "pjpgmfcmabopnnfonnhmdjglfpjjfkbf";
+const char* const permissions_crx = "eagpmdpfmaekmmcejjbmjoecnejeiiin";
 
 struct ExtensionsOrder {
   bool operator()(const Extension* a, const Extension* b) {
@@ -98,6 +99,28 @@
   return ret_val;
 }
 
+static void AddPattern(ExtensionExtent* extent, const std::string& pattern) {
+  int schemes = URLPattern::SCHEME_ALL;
+  extent->AddPattern(URLPattern(schemes, pattern));
+}
+
+static void AssertEqualExtents(ExtensionExtent* extent1,
+                               ExtensionExtent* extent2) {
+  std::vector<URLPattern> patterns1 = extent1->patterns();
+  std::vector<URLPattern> patterns2 = extent2->patterns();
+  std::set<std::string> strings1;
+  EXPECT_EQ(patterns1.size(), patterns2.size());
+
+  for (size_t i = 0; i < patterns1.size(); ++i)
+    strings1.insert(patterns1.at(i).GetAsString());
+
+  std::set<std::string> strings2;
+  for (size_t i = 0; i < patterns2.size(); ++i)
+    strings2.insert(patterns2.at(i).GetAsString());
+
+  EXPECT_EQ(strings1, strings2);
+}
+
 }  // namespace
 
 class MockExtensionProvider : public ExternalExtensionProvider {
@@ -458,21 +481,39 @@
                             Extension::Location location);
 
   void PackAndInstallExtension(const FilePath& dir_path,
+                               const FilePath& pem_path,
                                bool should_succeed) {
     FilePath crx_path;
     ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &crx_path));
     crx_path = crx_path.AppendASCII("temp.crx");
-    FilePath pem_path = crx_path.DirName().AppendASCII("temp.pem");
+
+    // Use the existing pem key, if provided.
+    FilePath pem_output_path;
+    if (pem_path.value().empty()) {
+      pem_output_path = crx_path.DirName().AppendASCII("temp.pem");
+      ASSERT_TRUE(file_util::Delete(pem_output_path, false));
+    } else {
+      ASSERT_TRUE(file_util::PathExists(pem_path));
+    }
 
     ASSERT_TRUE(file_util::Delete(crx_path, false));
-    ASSERT_TRUE(file_util::Delete(pem_path, false));
+
     scoped_ptr<ExtensionCreator> creator(new ExtensionCreator());
-    ASSERT_TRUE(creator->Run(dir_path, crx_path, FilePath(), pem_path));
+    ASSERT_TRUE(creator->Run(dir_path,
+                             crx_path,
+                             pem_path,
+                             pem_output_path));
+
     ASSERT_TRUE(file_util::PathExists(crx_path));
 
     InstallExtension(crx_path, should_succeed);
   }
 
+  void PackAndInstallExtension(const FilePath& dir_path,
+                               bool should_succeed) {
+      PackAndInstallExtension(dir_path, FilePath(), should_succeed);
+  }
+
   void InstallExtension(const FilePath& path,
                         bool should_succeed) {
     ASSERT_TRUE(file_util::PathExists(path));
@@ -654,6 +695,19 @@
     EXPECT_EQ(expected_val, val) << msg;
   }
 
+  void SetPref(const std::string& extension_id,
+               const std::string& pref_path,
+               Value* value,
+               const std::string& msg) {
+    const DictionaryValue* dict =
+        profile_->GetPrefs()->GetMutableDictionary("extensions.settings");
+    ASSERT_TRUE(dict != NULL) << msg;
+    DictionaryValue* pref = NULL;
+    ASSERT_TRUE(dict->GetDictionary(extension_id, &pref)) << msg;
+    EXPECT_TRUE(pref != NULL) << msg;
+    pref->Set(pref_path, value);
+  }
+
   void SetPrefInteg(const std::string& extension_id,
                     const std::string& pref_path,
                     int value) {
@@ -664,13 +718,46 @@
     msg += " = ";
     msg += base::IntToString(value);
 
+    SetPref(extension_id, pref_path, Value::CreateIntegerValue(value), msg);
+  }
+
+  void SetPrefBool(const std::string& extension_id,
+                   const std::string& pref_path,
+                   bool value) {
+    std::string msg = " while setting: ";
+    msg += extension_id + " " + pref_path;
+    msg += " = ";
+    msg += (value ? "true" : "false");
+
+    SetPref(extension_id, pref_path, Value::CreateBooleanValue(value), msg);
+  }
+
+  void ClearPref(const std::string& extension_id,
+                 const std::string& pref_path) {
+    std::string msg = " while clearing: ";
+    msg += extension_id + " " + pref_path;
+
     const DictionaryValue* dict =
         profile_->GetPrefs()->GetMutableDictionary("extensions.settings");
     ASSERT_TRUE(dict != NULL) << msg;
     DictionaryValue* pref = NULL;
     ASSERT_TRUE(dict->GetDictionary(extension_id, &pref)) << msg;
     EXPECT_TRUE(pref != NULL) << msg;
-    pref->SetInteger(pref_path, value);
+    pref->Remove(pref_path, NULL);
+  }
+
+  void SetPrefStringSet(const std::string& extension_id,
+                        const std::string& pref_path,
+                        const std::set<std::string>& value) {
+    std::string msg = " while setting: ";
+    msg += extension_id + " " + pref_path;
+
+    ListValue* list_value = new ListValue();
+    for (std::set<std::string>::const_iterator iter = value.begin();
+         iter != value.end(); ++iter)
+      list_value->Append(Value::CreateStringValue(*iter));
+
+    SetPref(extension_id, pref_path, list_value, msg);
   }
 
  protected:
@@ -1007,6 +1094,229 @@
   ExtensionErrorReporter::GetInstance()->ClearErrors();
 }
 
+// This tests that the granted permissions preferences are correctly set when
+// installing an extension.
+TEST_F(ExtensionsServiceTest, GrantedPermissions) {
+  InitializeEmptyExtensionsService();
+  FilePath path;
+  FilePath pem_path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+  path = path.AppendASCII("extensions")
+      .AppendASCII("permissions");
+
+  pem_path = path.AppendASCII("unknown.pem");
+  path = path.AppendASCII("unknown");
+
+  ASSERT_TRUE(file_util::PathExists(pem_path));
+  ASSERT_TRUE(file_util::PathExists(path));
+
+  ExtensionPrefs* prefs = service_->extension_prefs();
+
+  std::set<std::string> expected_api_perms;
+  std::set<std::string> known_api_perms;
+  bool full_access;
+  ExtensionExtent expected_host_perms;
+  ExtensionExtent known_host_perms;
+
+  // Make sure there aren't any granted permissions before the
+  // extension is installed.
+  EXPECT_FALSE(prefs->GetGrantedPermissions(
+      permissions_crx, &full_access, &known_api_perms, &known_host_perms));
+  EXPECT_TRUE(known_api_perms.empty());
+  EXPECT_TRUE(known_host_perms.is_empty());
+
+  PackAndInstallExtension(path, pem_path, true);
+
+  EXPECT_EQ(0u, GetErrors().size());
+  ASSERT_EQ(1u, service_->extensions()->size());
+  std::string extension_id = service_->extensions()->at(0)->id();
+  EXPECT_EQ(permissions_crx, extension_id);
+
+
+  // Verify that the valid API permissions have been recognized.
+  expected_api_perms.insert("tabs");
+
+  AddPattern(&expected_host_perms, "http://*.google.com/*");
+  AddPattern(&expected_host_perms, "https://*.google.com/*");
+  AddPattern(&expected_host_perms, "http://www.example.com/*");
+
+  EXPECT_TRUE(prefs->GetGrantedPermissions(extension_id,
+                                           &full_access,
+                                           &known_api_perms,
+                                           &known_host_perms));
+
+  EXPECT_EQ(expected_api_perms, known_api_perms);
+  EXPECT_FALSE(full_access);
+  AssertEqualExtents(&expected_host_perms, &known_host_perms);
+}
+
+#if !defined(OS_CHROMEOS)
+// Tests that the granted permissions full_access bit gets set correctly when
+// an extension contains an NPAPI plugin. Don't run this test on Chrome OS
+// since they don't support plugins.
+TEST_F(ExtensionsServiceTest, GrantedFullAccessPermissions) {
+  InitializeEmptyExtensionsService();
+
+  FilePath path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+  path = path.AppendASCII("extensions")
+      .AppendASCII("good")
+      .AppendASCII("Extensions")
+      .AppendASCII(good1)
+      .AppendASCII("2");
+
+  ASSERT_TRUE(file_util::PathExists(path));
+
+  PackAndInstallExtension(path, true);
+
+  EXPECT_EQ(0u, GetErrors().size());
+  EXPECT_EQ(1u, service_->extensions()->size());
+  const Extension* extension = service_->extensions()->at(0);
+  std::string extension_id = extension->id();
+  ExtensionPrefs* prefs = service_->extension_prefs();
+
+  bool full_access;
+  std::set<std::string> api_permissions;
+  ExtensionExtent host_permissions;
+  EXPECT_TRUE(prefs->GetGrantedPermissions(
+      extension_id, &full_access, &api_permissions, &host_permissions));
+
+  EXPECT_TRUE(full_access);
+  EXPECT_TRUE(api_permissions.empty());
+  EXPECT_TRUE(host_permissions.is_empty());
+}
+#endif
+
+// Tests that the extension is disabled when permissions are missing from
+// the extension's granted permissions preferences. (This simulates updating
+// the browser to a version which recognizes more permissions).
+TEST_F(ExtensionsServiceTest, GrantedAPIAndHostPermissions) {
+  InitializeEmptyExtensionsService();
+
+  FilePath path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+  path = path.AppendASCII("extensions")
+      .AppendASCII("permissions")
+      .AppendASCII("unknown");
+
+  ASSERT_TRUE(file_util::PathExists(path));
+
+  PackAndInstallExtension(path, true);
+
+  EXPECT_EQ(0u, GetErrors().size());
+  EXPECT_EQ(1u, service_->extensions()->size());
+  const Extension* extension = service_->extensions()->at(0);
+  std::string extension_id = extension->id();
+
+  ExtensionPrefs* prefs = service_->extension_prefs();
+
+  std::set<std::string> expected_api_permissions;
+  ExtensionExtent expected_host_permissions;
+
+  expected_api_permissions.insert("tabs");
+  AddPattern(&expected_host_permissions, "http://*.google.com/*");
+  AddPattern(&expected_host_permissions, "https://*.google.com/*");
+  AddPattern(&expected_host_permissions, "http://www.example.com/*");
+
+  std::set<std::string> api_permissions;
+  std::set<std::string> host_permissions;
+
+  // Test that the extension is disabled when an API permission is missing from
+  // the extension's granted api permissions preference. (This simulates
+  // updating the browser to a version which recognizes a new API permission).
+  SetPrefStringSet(extension_id, "granted_permissions.api", api_permissions);
+
+  service_->ReloadExtensions();
+
+  EXPECT_EQ(1u, service_->disabled_extensions()->size());
+  extension = service_->disabled_extensions()->at(0);
+
+  ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::DISABLED);
+  ASSERT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+  // Now grant and re-enable the extension, making sure the prefs are updated.
+  service_->GrantPermissionsAndEnableExtension(extension);
+
+  ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::ENABLED);
+  ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+  std::set<std::string> current_api_permissions;
+  ExtensionExtent current_host_permissions;
+  bool current_full_access;
+
+  ASSERT_TRUE(prefs->GetGrantedPermissions(extension_id,
+                                           &current_full_access,
+                                           &current_api_permissions,
+                                           &current_host_permissions));
+
+  ASSERT_FALSE(current_full_access);
+  ASSERT_EQ(expected_api_permissions, current_api_permissions);
+  AssertEqualExtents(&expected_host_permissions, &current_host_permissions);
+
+  // Tests that the extension is disabled when a host permission is missing from
+  // the extension's granted host permissions preference. (This simulates
+  // updating the browser to a version which recognizes additional host
+  // permissions).
+  api_permissions.clear();
+  host_permissions.clear();
+  current_api_permissions.clear();
+  current_host_permissions.ClearPaths();
+
+  api_permissions.insert("tabs");
+  host_permissions.insert("http://*.google.com/*");
+  host_permissions.insert("https://*.google.com/*");
+
+  SetPrefStringSet(extension_id, "granted_permissions.api", api_permissions);
+  SetPrefStringSet(extension_id, "granted_permissions.host", host_permissions);
+
+  service_->ReloadExtensions();
+
+  EXPECT_EQ(1u, service_->disabled_extensions()->size());
+  extension = service_->disabled_extensions()->at(0);
+
+  ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::DISABLED);
+  ASSERT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+  // Now grant and re-enable the extension, making sure the prefs are updated.
+  service_->GrantPermissionsAndEnableExtension(extension);
+
+  ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::ENABLED);
+  ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+  ASSERT_TRUE(prefs->GetGrantedPermissions(extension_id,
+                                           &current_full_access,
+                                           &current_api_permissions,
+                                           &current_host_permissions));
+
+  ASSERT_FALSE(current_full_access);
+  ASSERT_EQ(expected_api_permissions, current_api_permissions);
+  AssertEqualExtents(&expected_host_permissions, &current_host_permissions);
+
+  // Tests that the granted permissions preferences are initialized when
+  // migrating from the old pref schema.
+  current_api_permissions.clear();
+  current_host_permissions.ClearPaths();
+
+  ClearPref(extension_id, "granted_permissions");
+
+  service_->ReloadExtensions();
+
+  EXPECT_EQ(1u, service_->extensions()->size());
+  extension = service_->extensions()->at(0);
+
+  ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::ENABLED);
+  ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+  ASSERT_TRUE(prefs->GetGrantedPermissions(extension_id,
+                                           &current_full_access,
+                                           &current_api_permissions,
+                                           &current_host_permissions));
+
+  ASSERT_FALSE(current_full_access);
+  ASSERT_EQ(expected_api_permissions, current_api_permissions);
+  AssertEqualExtents(&expected_host_permissions, &current_host_permissions);
+}
+
 // Test Packaging and installing an extension.
 TEST_F(ExtensionsServiceTest, PackExtension) {
   InitializeEmptyExtensionsService();