Implement CloudReportingEnabled policy

The policy force installs Chrome Reporting Extension and overrides any
other extension policies about extensions black list,
blcoked permissions, allowed/blocked runtime hosts,
minimum version requirment, allowed type and update url.

The policy does not override install source policy as it does not affect
force-installed extension.


Bug: 898673
Change-Id: I8f17d2073b200448d268a34dc07ab61ff65753d8
Reviewed-on: https://chromium-review.googlesource.com/c/1306233
Commit-Queue: Owen Min <[email protected]>
Reviewed-by: Devlin <[email protected]>
Cr-Commit-Position: refs/heads/master@{#604696}
diff --git a/chrome/browser/extensions/extension_management.cc b/chrome/browser/extensions/extension_management.cc
index b168e8da8..1eb1235 100644
--- a/chrome/browser/extensions/extension_management.cc
+++ b/chrome/browser/extensions/extension_management.cc
@@ -24,12 +24,15 @@
 #include "chrome/browser/extensions/standard_management_policy_provider.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/pref_names.h"
 #include "components/crx_file/id_util.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
 #include "extensions/browser/pref_names.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_urls.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/permissions/api_permission_set.h"
 #include "extensions/common/permissions/permission_set.h"
@@ -65,6 +68,10 @@
   pref_change_registrar_.Add(pref_names::kAllowedTypes, pref_change_callback);
   pref_change_registrar_.Add(pref_names::kExtensionManagement,
                              pref_change_callback);
+#if !defined(OS_CHROMEOS)
+  pref_change_registrar_.Add(prefs::kCloudReportingEnabled,
+                             pref_change_callback);
+#endif
   // Note that both |global_settings_| and |default_settings_| will be null
   // before first call to Refresh(), so in order to resolve this, Refresh() must
   // be called in the initialization of ExtensionManagement.
@@ -159,7 +166,13 @@
 }
 
 bool ExtensionManagement::IsAllowedManifestType(
-    Manifest::Type manifest_type) const {
+    Manifest::Type manifest_type,
+    const std::string& extension_id) const {
+  if (extension_id == extension_misc::kCloudReportingExtensionId &&
+      IsCloudReportingPolicyEnabled()) {
+    return true;
+  }
+
   if (!global_settings_->has_restricted_allowed_types)
     return true;
   const std::vector<Manifest::Type>& allowed_types =
@@ -169,6 +182,13 @@
 
 APIPermissionSet ExtensionManagement::GetBlockedAPIPermissions(
     const Extension* extension) const {
+  // The Chrome Reporting extension is sideloaded via the CloudReportingEnabled
+  // policy and is not subject to permission withholding.
+  if (extension->id() == extension_misc::kCloudReportingExtensionId &&
+      IsCloudReportingPolicyEnabled()) {
+    return APIPermissionSet();
+  }
+
   // Fetch per-extension blocked permissions setting.
   auto iter_id = settings_by_id_.find(extension->id());
 
@@ -441,12 +461,14 @@
       }
     }
   }
+
+  UpdateForcedCloudReportingExtension();
 }
 
 const base::Value* ExtensionManagement::LoadPreference(
     const char* pref_name,
     bool force_managed,
-    base::Value::Type expected_type) {
+    base::Value::Type expected_type) const {
   if (!pref_service_)
     return nullptr;
   const PrefService::Preference* pref =
@@ -507,6 +529,32 @@
   }
 }
 
+void ExtensionManagement::UpdateForcedCloudReportingExtension() {
+  if (!IsCloudReportingPolicyEnabled())
+    return;
+
+  // Adds the Chrome Reporting extension to the force install list if
+  // CloudReportingEnabled policy is set to True. Overrides any existing setting
+  // for that extension from other policies.
+  internal::IndividualSettings* settings =
+      AccessById(extension_misc::kCloudReportingExtensionId);
+  settings->Reset();
+  settings->minimum_version_required.reset();
+  settings->installation_mode = INSTALLATION_FORCED;
+  settings->update_url = extension_urls::kChromeWebstoreUpdateURL;
+}
+
+bool ExtensionManagement::IsCloudReportingPolicyEnabled() const {
+#if !defined(OS_CHROMEOS)
+  const base::Value* policy_value =
+      LoadPreference(prefs::kCloudReportingEnabled,
+                     /* force_managed = */ true, base::Value::Type::BOOLEAN);
+  return policy_value && policy_value->GetBool();
+#else
+  return false;
+#endif
+}
+
 internal::IndividualSettings* ExtensionManagement::AccessById(
     const ExtensionId& id) {
   DCHECK(crx_file::id_util::IdIsValid(id)) << "Invalid ID: " << id;
diff --git a/chrome/browser/extensions/extension_management.h b/chrome/browser/extensions/extension_management.h
index 68accd1..7894fd13 100644
--- a/chrome/browser/extensions/extension_management.h
+++ b/chrome/browser/extensions/extension_management.h
@@ -108,9 +108,10 @@
   bool IsOffstoreInstallAllowed(const GURL& url,
                                 const GURL& referrer_url) const;
 
-  // Returns true if an extension with manifest type |manifest_type| is
-  // allowed to be installed.
-  bool IsAllowedManifestType(Manifest::Type manifest_type) const;
+  // Returns true if an extension with manifest type |manifest_type| and
+  // id |extension_id| is allowed to be installed.
+  bool IsAllowedManifestType(Manifest::Type manifest_type,
+                             const std::string& extension_id) const;
 
   // Returns the list of blocked API permissions for |extension|.
   APIPermissionSet GetBlockedAPIPermissions(const Extension* extension) const;
@@ -182,7 +183,7 @@
   // be loaded from or has the wrong type.
   const base::Value* LoadPreference(const char* pref_name,
                                     bool force_managed,
-                                    base::Value::Type expected_type);
+                                    base::Value::Type expected_type) const;
 
   void OnExtensionPrefChanged();
   void NotifyExtensionManagementPrefChanged();
@@ -195,6 +196,12 @@
   // Helper to update |extension_dict| for forced installs.
   void UpdateForcedExtensions(const base::DictionaryValue* extension_dict);
 
+  // Helper to update |settings_by_id_| for forced cloud reporting extension.
+  void UpdateForcedCloudReportingExtension();
+
+  // Returns true if cloud reporting policy is enabled.
+  bool IsCloudReportingPolicyEnabled() const;
+
   // Helper function to access |settings_by_id_| with |id| as key.
   // Adds a new IndividualSettings entry to |settings_by_id_| if none exists for
   // |id| yet.
diff --git a/chrome/browser/extensions/extension_management_unittest.cc b/chrome/browser/extensions/extension_management_unittest.cc
index 9e1efd134..2b3c4e0 100644
--- a/chrome/browser/extensions/extension_management_unittest.cc
+++ b/chrome/browser/extensions/extension_management_unittest.cc
@@ -15,10 +15,13 @@
 #include "chrome/browser/extensions/extension_management_test_util.h"
 #include "chrome/browser/extensions/external_policy_loader.h"
 #include "chrome/browser/extensions/standard_management_policy_provider.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "extensions/browser/pref_names.h"
+#include "extensions/common/extension_urls.h"
 #include "extensions/common/manifest.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/permissions/api_permission.h"
@@ -89,6 +92,7 @@
     "    \"installation_mode\": \"blocked\","
     "  },"
     "}";
+
 }  // namespace
 
 class ExtensionManagementServiceTest : public testing::Test {
@@ -188,6 +192,14 @@
     return extension_management_->GetPolicyBlockedHosts(extension.get());
   }
 
+  // Wrapper of ExtensionManagement::GetPolicyAllowedHosts, |id| is used
+  // to construct an Extension for testing.
+  URLPatternSet GetPolicyAllowedHosts(const std::string& id) {
+    scoped_refptr<const Extension> extension =
+        CreateExtension(Manifest::UNPACKED, "0.1", id, kNonExistingUpdateUrl);
+    return extension_management_->GetPolicyAllowedHosts(extension.get());
+  }
+
   // Wrapper of ExtensionManagement::BlockedInstallMessage, |id| is used
   // in case the message is extension specific.
   const std::string GetBlockedInstallMessage(const std::string& id) {
@@ -218,13 +230,6 @@
   }
 
  protected:
-  content::TestBrowserThreadBundle test_browser_thread_bundle_;
-
-  std::unique_ptr<TestingProfile> profile_;
-  sync_preferences::TestingPrefServiceSyncable* pref_service_;
-  std::unique_ptr<ExtensionManagement> extension_management_;
-
- private:
   // Create an extension with specified |location|, |version|, |id| and
   // |update_url|.
   scoped_refptr<const Extension> CreateExtension(
@@ -244,6 +249,12 @@
     CHECK(extension.get()) << error;
     return extension;
   }
+
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
+
+  std::unique_ptr<TestingProfile> profile_;
+  sync_preferences::TestingPrefServiceSyncable* pref_service_;
+  std::unique_ptr<ExtensionManagement> extension_management_;
 };
 
 class ExtensionAdminPolicyTest : public ExtensionManagementServiceTest {
@@ -794,6 +805,124 @@
       extension_management_->IsInstallationExplicitlyAllowed(not_specified));
 }
 
+#if !defined(OS_CHROMEOS)
+TEST_F(ExtensionManagementServiceTest, CloudReportingEnabledPolicy) {
+  // Enables the policy put the extension into forced list.
+  SetPref(true, prefs::kCloudReportingEnabled,
+          std::make_unique<base::Value>(true));
+  CheckAutomaticallyInstalledUpdateUrl(
+      extension_misc::kCloudReportingExtensionId,
+      extension_urls::kChromeWebstoreUpdateURL);
+  EXPECT_EQ(
+      ExtensionManagement::INSTALLATION_FORCED,
+      GetInstallationModeById(extension_misc::kCloudReportingExtensionId));
+
+  // Disabling the policy should remove the extension from the forced list.
+  RemovePref(true, prefs::kCloudReportingEnabled);
+  EXPECT_EQ(
+      ExtensionManagement::INSTALLATION_ALLOWED,
+      GetInstallationModeById(extension_misc::kCloudReportingExtensionId));
+
+  // Recommended policy does not force install the policy.
+  pref_service_->SetRecommendedPref(prefs::kCloudReportingEnabled,
+                                    std::make_unique<base::Value>(true));
+  EXPECT_EQ(
+      ExtensionManagement::INSTALLATION_ALLOWED,
+      GetInstallationModeById(extension_misc::kCloudReportingExtensionId));
+}
+
+TEST_F(ExtensionManagementServiceTest,
+       CloudReportingEnabledPolicyOverridesBlacklist) {
+  base::ListValue denied_list_pref;
+  denied_list_pref.AppendString(extension_misc::kCloudReportingExtensionId);
+  SetPref(true, pref_names::kInstallDenyList,
+          denied_list_pref.CreateDeepCopy());
+  EXPECT_EQ(
+      ExtensionManagement::INSTALLATION_BLOCKED,
+      GetInstallationModeById(extension_misc::kCloudReportingExtensionId));
+  SetPref(true, prefs::kCloudReportingEnabled,
+          std::make_unique<base::Value>(true));
+  EXPECT_EQ(
+      ExtensionManagement::INSTALLATION_FORCED,
+      GetInstallationModeById(extension_misc::kCloudReportingExtensionId));
+}
+
+TEST_F(ExtensionManagementServiceTest,
+       CloudReportingEnabledPolicyOverridesAllowedTypes) {
+  scoped_refptr<const Extension> extension =
+      CreateExtension(Manifest::EXTERNAL_POLICY, "1.0",
+                      extension_misc::kCloudReportingExtensionId,
+                      extension_urls::kChromeWebstoreUpdateURL);
+  StandardManagementPolicyProvider provider(extension_management_.get());
+
+  base::ListValue allowed_type_pref;
+  base::string16 error;
+  allowed_type_pref.AppendInteger(Manifest::TYPE_THEME);
+  SetPref(true, pref_names::kAllowedTypes, allowed_type_pref.CreateDeepCopy());
+  EXPECT_FALSE(provider.UserMayLoad(extension.get(), &error));
+
+  SetPref(true, prefs::kCloudReportingEnabled,
+          std::make_unique<base::Value>(true));
+
+  EXPECT_TRUE(provider.UserMayLoad(extension.get(), nullptr));
+}
+
+TEST_F(ExtensionManagementServiceTest,
+       CloudReportingenabledOverridesExtensionSettings) {
+  SetExampleDictPref(R"({
+        "kigjhoekjcpdfjpimbdjegmgecmlicaf": {
+          "installation_mode": "allowed",
+          "blocked_permissions": ["bookmarks"],
+          "minimum_version_required": "100.0",
+          "runtime_blocked_hosts": ["https://a.com"],
+          "runtime_allowed_hosts": ["https://b.com"],
+          "update_url": "http://example.com/update_url",
+        },
+        "update_url:https://clients2.google.com/service/update2/crx": {
+          "blocked_permissions": ["downloads"],
+        },
+        "update_url:http://example.com/update_url": {
+          "blocked_permissions": ["downloads"],
+        }
+      })");
+
+  EXPECT_EQ(
+      ExtensionManagement::INSTALLATION_ALLOWED,
+      GetInstallationModeById(extension_misc::kCloudReportingExtensionId));
+  EXPECT_EQ(2u,
+            GetBlockedAPIPermissions(extension_misc::kCloudReportingExtensionId,
+                                     extension_urls::kChromeWebstoreUpdateURL)
+                .size());
+  EXPECT_FALSE(
+      CheckMinimumVersion(extension_misc::kCloudReportingExtensionId, "99.0"));
+  EXPECT_EQ(
+      1u,
+      GetPolicyBlockedHosts(extension_misc::kCloudReportingExtensionId).size());
+  EXPECT_EQ(
+      1u,
+      GetPolicyAllowedHosts(extension_misc::kCloudReportingExtensionId).size());
+
+  SetPref(true, prefs::kCloudReportingEnabled,
+          std::make_unique<base::Value>(true));
+  CheckAutomaticallyInstalledUpdateUrl(
+      extension_misc::kCloudReportingExtensionId,
+      extension_urls::kChromeWebstoreUpdateURL);
+  EXPECT_EQ(
+      ExtensionManagement::INSTALLATION_FORCED,
+      GetInstallationModeById(extension_misc::kCloudReportingExtensionId));
+  EXPECT_TRUE(
+      GetBlockedAPIPermissions(extension_misc::kCloudReportingExtensionId,
+                               extension_urls::kChromeWebstoreUpdateURL)
+          .empty());
+  EXPECT_TRUE(
+      CheckMinimumVersion(extension_misc::kCloudReportingExtensionId, "99.0"));
+  EXPECT_TRUE(GetPolicyBlockedHosts(extension_misc::kCloudReportingExtensionId)
+                  .is_empty());
+  EXPECT_TRUE(GetPolicyAllowedHosts(extension_misc::kCloudReportingExtensionId)
+                  .is_empty());
+}
+#endif
+
 // Tests the flag value indicating that extensions are blacklisted by default.
 TEST_F(ExtensionAdminPolicyTest, BlacklistedByDefault) {
   EXPECT_FALSE(BlacklistedByDefault(NULL));
diff --git a/chrome/browser/extensions/standard_management_policy_provider.cc b/chrome/browser/extensions/standard_management_policy_provider.cc
index 4e09531..ba136d4 100644
--- a/chrome/browser/extensions/standard_management_policy_provider.cc
+++ b/chrome/browser/extensions/standard_management_policy_provider.cc
@@ -118,7 +118,8 @@
     case Manifest::TYPE_LEGACY_PACKAGED_APP:
     case Manifest::TYPE_PLATFORM_APP:
     case Manifest::TYPE_SHARED_MODULE: {
-      if (!settings_->IsAllowedManifestType(extension->GetType()))
+      if (!settings_->IsAllowedManifestType(extension->GetType(),
+                                            extension->id()))
         return ReturnLoadError(extension, error);
       break;
     }
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index 9c7c828..65afef42 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -44,6 +44,7 @@
 const char kTextEditorAppId[] = "mmfbcljfglbokpmkimbfghdkjmjhdgbg";
 const char kInAppPaymentsSupportAppId[] = "nmmhkkegccagdldgiimedpiccmgmieda";
 const char kMediaRouterStableExtensionId[] = "pkedcjkdefgpdelpbcmbmeomcjbeemfm";
+const char kCloudReportingExtensionId[] = "kigjhoekjcpdfjpimbdjegmgecmlicaf";
 
 #if defined(OS_CHROMEOS)
 const char kAssessmentAssistantExtensionId[] =
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index b5dec13..fbedb89 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -105,6 +105,9 @@
 // The extension id of the stable media router extension.
 extern const char kMediaRouterStableExtensionId[];
 
+// The extension id of the Chrome Reporting extension.
+extern const char kCloudReportingExtensionId[];
+
 // The buckets used for app launches.
 enum AppLaunchBucket {
   // Launch from NTP apps section while maximized.