text: // Copyright 2015 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.
  
  #include <utility>
  
  #include "base/callback_helpers.h"
  #include "base/strings/stringprintf.h"
  #include "chrome/browser/extensions/chrome_test_extension_loader.h"
  #include "chrome/browser/extensions/extension_service.h"
  #include "chrome/browser/extensions/extension_service_test_with_install.h"
  #include "chrome/browser/extensions/extension_util.h"
  #include "chrome/browser/extensions/permissions_test_util.h"
  #include "chrome/browser/extensions/permissions_updater.h"
  #include "chrome/browser/extensions/scripting_permissions_modifier.h"
  #include "chrome/test/base/testing_profile.h"
  #include "components/crx_file/id_util.h"
  #include "extensions/browser/extension_prefs.h"
  #include "extensions/browser/extension_registry.h"
  #include "extensions/browser/test_extension_registry_observer.h"
  #include "extensions/common/extension.h"
  #include "extensions/common/extension_builder.h"
  #include "extensions/common/extensions_client.h"
  #include "extensions/common/manifest_handlers/permissions_parser.h"
  #include "extensions/common/permissions/permission_set.h"
  #include "extensions/common/permissions/permissions_data.h"
  #include "extensions/common/url_pattern.h"
  #include "extensions/common/url_pattern_set.h"
  #include "extensions/common/user_script.h"
  #include "extensions/common/value_builder.h"
  #include "extensions/test/test_extension_dir.h"
  #include "testing/gmock/include/gmock/gmock.h"
  
  using extensions::mojom::ManifestLocation;
  
  namespace extensions {
  
  namespace {
  
  using permissions_test_util::GetPatternsAsStrings;
  
  std::vector<std::string> GetEffectivePatternsAsStrings(
      const Extension& extension) {
    return GetPatternsAsStrings(
        extension.permissions_data()->active_permissions().effective_hosts());
  }
  
  std::vector<std::string> GetScriptablePatternsAsStrings(
      const Extension& extension) {
    return GetPatternsAsStrings(
        extension.permissions_data()->active_permissions().scriptable_hosts());
  }
  
  std::vector<std::string> GetExplicitPatternsAsStrings(
      const Extension& extension) {
    return GetPatternsAsStrings(
        extension.permissions_data()->active_permissions().explicit_hosts());
  }
  
  void InitializeExtensionPermissions(Profile* profile,
                                      const Extension& extension) {
    PermissionsUpdater updater(profile);
    updater.InitializePermissions(&extension);
    updater.GrantActivePermissions(&extension);
  }
  
  void CheckActiveHostPermissions(
      const Extension& extension,
      const std::vector<std::string>& explicit_hosts,
      const std::vector<std::string>& scriptable_hosts) {
    EXPECT_THAT(GetExplicitPatternsAsStrings(extension),
                testing::UnorderedElementsAreArray(explicit_hosts));
    EXPECT_THAT(GetScriptablePatternsAsStrings(extension),
                testing::UnorderedElementsAreArray(scriptable_hosts));
  }
  
  void CheckWithheldHostPermissions(
      const Extension& extension,
      const std::vector<std::string>& explicit_hosts,
      const std::vector<std::string>& scriptable_hosts) {
    const PermissionsData* permissions_data = extension.permissions_data();
    EXPECT_THAT(GetPatternsAsStrings(
                    permissions_data->withheld_permissions().explicit_hosts()),
                testing::UnorderedElementsAreArray(explicit_hosts));
    EXPECT_THAT(GetPatternsAsStrings(
                    permissions_data->withheld_permissions().scriptable_hosts()),
                testing::UnorderedElementsAreArray(scriptable_hosts));
  }
  
  using ScriptingPermissionsModifierUnitTest = ExtensionServiceTestWithInstall;
  
  }  // namespace
  
  TEST_F(ScriptingPermissionsModifierUnitTest, GrantAndWithholdHostPermissions) {
    InitializeEmptyExtensionService();
  
    std::vector<std::string> test_cases[] = {
        {"http://www.google.com/*"},
        {"http://*/*"},
        {"<all_urls>"},
        {"http://*.com/*"},
        {"http://google.com/*", "<all_urls>"},
    };
  
    for (const auto& test_case : test_cases) {
      std::string test_case_name = base::JoinString(test_case, ",");
      SCOPED_TRACE(test_case_name);
      scoped_refptr<const Extension> extension =
          ExtensionBuilder(test_case_name)
              .AddPermissions(test_case)
              .AddContentScript("foo.js", test_case)
              .SetLocation(ManifestLocation::kInternal)
              .Build();
  
      PermissionsUpdater(profile()).InitializePermissions(extension.get());
  
      ScriptingPermissionsModifier modifier(profile(), extension);
      ASSERT_TRUE(modifier.CanAffectExtension());
  
      // By default, all permissions are granted.
      {
        SCOPED_TRACE("Initial state");
        CheckActiveHostPermissions(*extension, test_case, test_case);
        CheckWithheldHostPermissions(*extension, {}, {});
      }
  
      // Then, withhold host permissions.
      modifier.SetWithholdHostPermissions(true);
      {
        SCOPED_TRACE("After setting to withhold");
        CheckActiveHostPermissions(*extension, {}, {});
        CheckWithheldHostPermissions(*extension, test_case, test_case);
      }
  
      // Finally, re-grant the withheld permissions.
      modifier.SetWithholdHostPermissions(false);
  
      // We should be back to our initial state - all requested permissions are
      // granted.
      {
        SCOPED_TRACE("After setting to not withhold");
        CheckActiveHostPermissions(*extension, test_case, test_case);
        CheckWithheldHostPermissions(*extension, {}, {});
      }
    }
  }
  
  // Tests that with the creation flag present, requested host permissions are
  // withheld on installation, but still allow for individual permissions to be
  // granted, or all permissions be set back to not being withheld by default.
  TEST_F(ScriptingPermissionsModifierUnitTest, WithholdHostPermissionsOnInstall) {
    InitializeEmptyExtensionService();
  
    constexpr char kHostGoogle[] = "https://google.com/*";
    constexpr char kHostChromium[] = "https://chromium.org/*";
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("a")
            .AddPermissions({kHostGoogle, kHostChromium})
            .AddContentScript("foo.js", {kHostGoogle})
            .SetLocation(ManifestLocation::kInternal)
            .AddFlags(Extension::WITHHOLD_PERMISSIONS)
            .Build();
  
    // Initialize the permissions and have the prefs built and stored.
    PermissionsUpdater(profile()).InitializePermissions(extension.get());
    ExtensionPrefs::Get(profile())->OnExtensionInstalled(
        extension.get(), Extension::State::ENABLED, syncer::StringOrdinal(), "");
  
    ScriptingPermissionsModifier modifier(profile(), extension);
    ASSERT_TRUE(modifier.CanAffectExtension());
  
    // With the flag present, permissions should have been withheld.
    {
      SCOPED_TRACE("Initial state");
      CheckActiveHostPermissions(*extension, {}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle, kHostChromium},
                                   {kHostGoogle});
    }
  
    // Grant one of the permissions manually.
    modifier.GrantHostPermission(GURL(kHostChromium));
  
    {
      SCOPED_TRACE("After granting single");
      CheckActiveHostPermissions(*extension, {kHostChromium}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle}, {kHostGoogle});
    }
  
    // Finally, re-grant the withheld permissions.
    modifier.SetWithholdHostPermissions(false);
  
    // All requested permissions should now be granted granted.
    {
      SCOPED_TRACE("After setting to not withhold");
      CheckActiveHostPermissions(*extension, {kHostGoogle, kHostChromium},
                                 {kHostGoogle});
      CheckWithheldHostPermissions(*extension, {}, {});
    }
  }
  
  // Tests that reloading an extension after withholding host permissions on
  // installation retains the correct state and any changes that have been made
  // since installation.
  TEST_F(ScriptingPermissionsModifierUnitTest,
         WithholdOnInstallPreservedOnReload) {
    InitializeEmptyExtensionService();
  
    constexpr char kHostGoogle[] = "https://google.com/*";
    constexpr char kHostChromium[] = "https://chromium.org/*";
    TestExtensionDir test_extension_dir;
    test_extension_dir.WriteManifest(
        R"({
             "name": "foo",
             "manifest_version": 2,
             "version": "1",
             "permissions": ["https://google.com/*", "https://chromium.org/*"]
           })");
    ChromeTestExtensionLoader loader(profile());
    loader.add_creation_flag(Extension::WITHHOLD_PERMISSIONS);
    loader.set_pack_extension(true);
    scoped_refptr<const Extension> extension =
        loader.LoadExtension(test_extension_dir.UnpackedPath());
    // Cache the ID, since the extension will be invalidated across reloads.
    ExtensionId extension_id = extension->id();
  
    auto reload_extension = [this, &extension_id]() {
      TestExtensionRegistryObserver observer(ExtensionRegistry::Get(profile()));
      service()->ReloadExtension(extension_id);
      return observer.WaitForExtensionLoaded();
    };
  
    // Permissions start withheld due to creation flag and remain withheld after
    // reload.
    {
      SCOPED_TRACE("Initial state");
      CheckActiveHostPermissions(*extension, {}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
    }
  
    {
      SCOPED_TRACE("Reload after initial state");
      extension = reload_extension();
      CheckActiveHostPermissions(*extension, {}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
    }
  
    // Grant one of the permissions and check it persists after reload.
    ScriptingPermissionsModifier(profile(), extension)
        .GrantHostPermission(GURL(kHostGoogle));
    {
      SCOPED_TRACE("Granting single");
      CheckActiveHostPermissions(*extension, {kHostGoogle}, {});
      CheckWithheldHostPermissions(*extension, {kHostChromium}, {});
    }
  
    {
      SCOPED_TRACE("Reload after granting single");
      extension = reload_extension();
      CheckActiveHostPermissions(*extension, {kHostGoogle}, {});
      CheckWithheldHostPermissions(*extension, {kHostChromium}, {});
    }
  
    // Set permissions not to be withheld at all and check it persists after
    // reload.
    ScriptingPermissionsModifier(profile(), extension)
        .SetWithholdHostPermissions(false);
    {
      SCOPED_TRACE("Setting to not withhold");
      CheckActiveHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
      CheckWithheldHostPermissions(*extension, {}, {});
    }
  
    {
      SCOPED_TRACE("Reload after setting to not withhold");
      extension = reload_extension();
      CheckActiveHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
      CheckWithheldHostPermissions(*extension, {}, {});
    }
  
    // Finally, set permissions to be withheld again and check it persists after
    // reload.
    ScriptingPermissionsModifier(profile(), extension)
        .SetWithholdHostPermissions(true);
    {
      SCOPED_TRACE("Setting back to withhold");
      CheckActiveHostPermissions(*extension, {}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
    }
  
    {
      SCOPED_TRACE("Reload after setting back to withhold");
      extension = reload_extension();
      CheckActiveHostPermissions(*extension, {}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
    }
  }
  
  // Tests that updating an extension after withholding host permissions on
  // installation retains the correct state and any changes that have been made
  // since installation.
  TEST_F(ScriptingPermissionsModifierUnitTest,
         WithholdOnInstallPreservedOnUpdate) {
    InitializeEmptyExtensionService();
  
    constexpr char kHostGoogle[] = "https://google.com/*";
    constexpr char kHostChromium[] = "https://chromium.org/*";
    TestExtensionDir test_extension_dir;
    constexpr char kManifestTemplate[] =
        R"({
             "name": "foo",
             "manifest_version": 2,
             "version": "%s",
             "permissions": ["https://google.com/*", "https://chromium.org/*"]
           })";
  
    test_extension_dir.WriteManifest(base::StringPrintf(kManifestTemplate, "1"));
    // We need to use a pem file here for consistent update IDs.
    const base::FilePath pem_path =
        data_dir().AppendASCII("permissions/update.pem");
    scoped_refptr<const Extension> extension = PackAndInstallCRX(
        test_extension_dir.UnpackedPath(), pem_path, INSTALL_NEW,
        Extension::WITHHOLD_PERMISSIONS, mojom::ManifestLocation::kInternal);
    // Cache the ID, since the extension will be invalidated across updates.
    ExtensionId extension_id = extension->id();
    // Hold onto references for the extension dirs so they don't get deleted
    // outside the lambda.
    std::vector<std::unique_ptr<TestExtensionDir>> extension_dirs;
  
    auto update_extension = [this, &extension_id, &pem_path, &kManifestTemplate,
                             &extension_dirs](const char* version) {
      auto update_version = std::make_unique<TestExtensionDir>();
      update_version->WriteManifest(
          base::StringPrintf(kManifestTemplate, version));
      PackCRXAndUpdateExtension(extension_id, update_version->UnpackedPath(),
                                pem_path, ENABLED);
      scoped_refptr<const Extension> updated_extension =
          registry()->GetInstalledExtension(extension_id);
  
      EXPECT_EQ(version, updated_extension->version().GetString());
      extension_dirs.push_back(std::move(update_version));
      return updated_extension;
    };
  
    // Permissions start withheld due to creation flag and remain withheld after
    // update.
    {
      SCOPED_TRACE("Initial state");
      CheckActiveHostPermissions(*extension, {}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
    }
  
    {
      SCOPED_TRACE("Update after initial state");
      extension = update_extension("2");
      CheckActiveHostPermissions(*extension, {}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
    }
  
    // Grant one of the permissions and check it persists after update.
    ScriptingPermissionsModifier(profile(), extension)
        .GrantHostPermission(GURL(kHostGoogle));
    {
      SCOPED_TRACE("Granting single");
      CheckActiveHostPermissions(*extension, {kHostGoogle}, {});
      CheckWithheldHostPermissions(*extension, {kHostChromium}, {});
    }
  
    {
      SCOPED_TRACE("Update after granting single");
      extension = update_extension("3");
      CheckActiveHostPermissions(*extension, {kHostGoogle}, {});
      CheckWithheldHostPermissions(*extension, {kHostChromium}, {});
    }
  
    // Set permissions not to be withheld at all and check it persists after
    // update.
    ScriptingPermissionsModifier(profile(), extension)
        .SetWithholdHostPermissions(false);
    {
      SCOPED_TRACE("Setting to not withhold");
      CheckActiveHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
      CheckWithheldHostPermissions(*extension, {}, {});
    }
  
    {
      SCOPED_TRACE("Update after setting to not withhold");
      extension = update_extension("4");
      CheckActiveHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
      CheckWithheldHostPermissions(*extension, {}, {});
    }
  
    // Finally, set permissions to be withheld again and check it persists after
    // update.
    ScriptingPermissionsModifier(profile(), extension)
        .SetWithholdHostPermissions(true);
    {
      SCOPED_TRACE("Setting back to withhold");
      CheckActiveHostPermissions(*extension, {}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
    }
  
    {
      SCOPED_TRACE("Update after setting back to withhold");
      extension = update_extension("5");
      CheckActiveHostPermissions(*extension, {}, {});
      CheckWithheldHostPermissions(*extension, {kHostGoogle, kHostChromium}, {});
    }
  }
  
  TEST_F(ScriptingPermissionsModifierUnitTest, SwitchBehavior) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("a")
            .AddPermission(URLPattern::kAllUrlsPattern)
            .AddContentScript("foo.js", {URLPattern::kAllUrlsPattern})
            .SetLocation(ManifestLocation::kInternal)
            .Build();
    PermissionsUpdater updater(profile());
    updater.InitializePermissions(extension.get());
    const PermissionsData* permissions_data = extension->permissions_data();
  
    // By default, the extension should have all its permissions.
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension),
                testing::UnorderedElementsAre(URLPattern::kAllUrlsPattern));
    EXPECT_TRUE(
        permissions_data->withheld_permissions().effective_hosts().is_empty());
    ScriptingPermissionsModifier modifier(profile(), extension);
    EXPECT_FALSE(modifier.HasWithheldHostPermissions());
  
    // Revoke access.
    modifier.SetWithholdHostPermissions(true);
    EXPECT_TRUE(modifier.HasWithheldHostPermissions());
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
    EXPECT_THAT(GetPatternsAsStrings(
                    permissions_data->withheld_permissions().effective_hosts()),
                testing::UnorderedElementsAre(URLPattern::kAllUrlsPattern));
  }
  
  TEST_F(ScriptingPermissionsModifierUnitTest, GrantHostPermission) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("extension")
            .AddPermission(URLPattern::kAllUrlsPattern)
            .AddContentScript("foo.js", {URLPattern::kAllUrlsPattern})
            .SetLocation(ManifestLocation::kInternal)
            .Build();
    PermissionsUpdater(profile()).InitializePermissions(extension.get());
  
    ScriptingPermissionsModifier modifier(profile(), extension);
    modifier.SetWithholdHostPermissions(true);
  
    const GURL kUrl("https://www.google.com/");
    const GURL kUrl2("https://www.chromium.org/");
    EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl));
    EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl2));
  
    const PermissionsData* permissions_data = extension->permissions_data();
    auto get_page_access = [&permissions_data](const GURL& url) {
      return permissions_data->GetPageAccess(url, 0, nullptr);
    };
  
    EXPECT_EQ(PermissionsData::PageAccess::kWithheld, get_page_access(kUrl));
    EXPECT_EQ(PermissionsData::PageAccess::kWithheld, get_page_access(kUrl2));
  
    ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
    {
      std::unique_ptr<const PermissionSet> permissions =
          prefs->GetRuntimeGrantedPermissions(extension->id());
      EXPECT_FALSE(permissions->effective_hosts().MatchesURL(kUrl));
      EXPECT_FALSE(permissions->effective_hosts().MatchesURL(kUrl2));
    }
  
    modifier.GrantHostPermission(kUrl);
    EXPECT_TRUE(modifier.HasGrantedHostPermission(kUrl));
    EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl2));
    EXPECT_EQ(PermissionsData::PageAccess::kAllowed, get_page_access(kUrl));
    EXPECT_EQ(PermissionsData::PageAccess::kWithheld, get_page_access(kUrl2));
    {
      std::unique_ptr<const PermissionSet> permissions =
          prefs->GetRuntimeGrantedPermissions(extension->id());
      EXPECT_TRUE(permissions->effective_hosts().MatchesURL(kUrl));
      EXPECT_FALSE(permissions->effective_hosts().MatchesURL(kUrl2));
    }
  
    modifier.RemoveGrantedHostPermission(kUrl);
    EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl));
    EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl2));
    EXPECT_EQ(PermissionsData::PageAccess::kWithheld, get_page_access(kUrl));
    EXPECT_EQ(PermissionsData::PageAccess::kWithheld, get_page_access(kUrl2));
    {
      std::unique_ptr<const PermissionSet> permissions =
          prefs->GetRuntimeGrantedPermissions(extension->id());
      EXPECT_FALSE(permissions->effective_hosts().MatchesURL(kUrl));
      EXPECT_FALSE(permissions->effective_hosts().MatchesURL(kUrl2));
    }
  }
  
  TEST_F(ScriptingPermissionsModifierUnitTest, CanAffectExtensionByLocation) {
    InitializeEmptyExtensionService();
  
    struct {
      ManifestLocation location;
      bool can_be_affected;
    } test_cases[] = {
        {ManifestLocation::kInternal, true},
        {ManifestLocation::kExternalPref, true},
        {ManifestLocation::kUnpacked, true},
        {ManifestLocation::kExternalPolicyDownload, false},
        {ManifestLocation::kComponent, false},
    };
  
    for (const auto& test_case : test_cases) {
      scoped_refptr<const Extension> extension =
          ExtensionBuilder("test")
              .SetLocation(test_case.location)
              .AddPermission("<all_urls>")
              .Build();
      EXPECT_EQ(test_case.can_be_affected,
                ScriptingPermissionsModifier(profile(), extension.get())
                    .CanAffectExtension())
          << test_case.location;
    }
  }
  
  TEST_F(ScriptingPermissionsModifierUnitTest,
         ExtensionsInitializedWithSavedRuntimeGrantedHostPermissionsAcrossLoad) {
    InitializeEmptyExtensionService();
  
    const GURL kExampleCom("https://example.com/");
    const GURL kChromiumOrg("https://chromium.org/");
    const URLPatternSet kExampleComPatternSet({URLPattern(
        Extension::kValidHostPermissionSchemes, "https://example.com/")});
  
    TestExtensionDir test_extension_dir;
    test_extension_dir.WriteManifest(
        R"({
             "name": "foo",
             "manifest_version": 2,
             "version": "1",
             "permissions": ["<all_urls>"]
           })");
    ChromeTestExtensionLoader loader(profile());
    loader.set_grant_permissions(true);
    scoped_refptr<const Extension> extension =
        loader.LoadExtension(test_extension_dir.UnpackedPath());
  
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .MatchesURL(kExampleCom));
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .MatchesURL(kChromiumOrg));
  
    ScriptingPermissionsModifier(profile(), extension)
        .SetWithholdHostPermissions(true);
    EXPECT_FALSE(extension->permissions_data()
                     ->active_permissions()
                     .explicit_hosts()
                     .MatchesURL(kExampleCom));
    EXPECT_FALSE(extension->permissions_data()
                     ->active_permissions()
                     .explicit_hosts()
                     .MatchesURL(kChromiumOrg));
  
    ScriptingPermissionsModifier(profile(), extension)
        .GrantHostPermission(kExampleCom);
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .MatchesURL(kExampleCom));
    EXPECT_FALSE(extension->permissions_data()
                     ->active_permissions()
                     .explicit_hosts()
                     .MatchesURL(kChromiumOrg));
  
    {
      TestExtensionRegistryObserver observer(ExtensionRegistry::Get(profile()));
      service()->ReloadExtension(extension->id());
      extension = observer.WaitForExtensionLoaded();
    }
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .MatchesURL(kExampleCom));
    EXPECT_FALSE(extension->permissions_data()
                     ->active_permissions()
                     .explicit_hosts()
                     .MatchesURL(kChromiumOrg));
  }
  
  // Test ScriptingPermissionsModifier::RemoveAllGrantedHostPermissions() revokes
  // hosts granted through the ScriptingPermissionsModifier.
  TEST_F(ScriptingPermissionsModifierUnitTest,
         RemoveAllGrantedHostPermissions_GrantedHosts) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("test").AddPermission("<all_urls>").Build();
    ScriptingPermissionsModifier modifier(profile(), extension.get());
  
    modifier.SetWithholdHostPermissions(true);
  
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
  
    modifier.GrantHostPermission(GURL("https://example.com"));
    modifier.GrantHostPermission(GURL("https://chromium.org"));
  
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension),
                testing::UnorderedElementsAre("https://example.com/*",
                                              "https://chromium.org/*"));
  
    modifier.RemoveAllGrantedHostPermissions();
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
  }
  
  // Test ScriptingPermissionsModifier::RemoveAllGrantedHostPermissions() revokes
  // hosts granted through the ScriptingPermissionsModifier for extensions that
  // don't request <all_urls>.
  TEST_F(ScriptingPermissionsModifierUnitTest,
         RemoveAllGrantedHostPermissions_GrantedHostsForNonAllUrlsExtension) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("test")
            .AddPermissions({"https://example.com/*", "https://chromium.org/*"})
            .Build();
    ScriptingPermissionsModifier modifier(profile(), extension.get());
  
    modifier.SetWithholdHostPermissions(true);
  
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
  
    modifier.GrantHostPermission(GURL("https://example.com"));
    modifier.GrantHostPermission(GURL("https://chromium.org"));
  
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension),
                testing::UnorderedElementsAre("https://example.com/*",
                                              "https://chromium.org/*"));
  
    modifier.RemoveAllGrantedHostPermissions();
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
  }
  
  // Test ScriptingPermissionsModifier::RemoveAllGrantedHostPermissions() revokes
  // granted optional host permissions.
  TEST_F(ScriptingPermissionsModifierUnitTest,
         RemoveAllGrantedHostPermissions_GrantedOptionalPermissions) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("test")
            .SetManifestKey("optional_permissions",
                            ListBuilder().Append("https://example.com/*").Build())
            .Build();
  
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
  
    {
      // Simulate adding an optional permission, which should also be revokable.
      URLPatternSet patterns;
      patterns.AddPattern(URLPattern(Extension::kValidHostPermissionSchemes,
                                     "https://example.com/*"));
      permissions_test_util::GrantOptionalPermissionsAndWaitForCompletion(
          profile(), *extension,
          PermissionSet(APIPermissionSet(), ManifestPermissionSet(),
                        std::move(patterns), URLPatternSet()));
    }
  
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension),
                testing::UnorderedElementsAre("https://example.com/*"));
  
    ScriptingPermissionsModifier modifier(profile(), extension.get());
    modifier.RemoveAllGrantedHostPermissions();
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
  }
  
  // Tests that HasBroadGrantedHostPermissions detects cases where there is a
  // granted permission that is sufficiently broad enough to be counted as akin to
  // <all_urls> type permissions.
  TEST_F(ScriptingPermissionsModifierUnitTest, HasBroadGrantedHostPermissions) {
    InitializeEmptyExtensionService();
  
    struct {
      std::vector<std::string> hosts;
      bool expected_broad_permissions;
    } test_cases[] = {{{}, false},
                      {{"https://www.google.com/*"}, false},
                      {{"https://www.google.com/*", "*://chromium.org/*"}, false},
                      {{"*://*.google.*/*"}, false},
                      {{"<all_urls>"}, true},
                      {{"https://*/*"}, true},
                      {{"*://*/*"}, true},
                      {{"https://www.google.com/*", "<all_urls>"}, true}};
  
    for (const auto& test_case : test_cases) {
      std::string test_case_name = base::JoinString(test_case.hosts, ",");
      SCOPED_TRACE(test_case_name);
      scoped_refptr<const Extension> extension =
          ExtensionBuilder("test: " + test_case_name)
              .AddPermission("<all_urls>")
              .Build();
      ScriptingPermissionsModifier modifier(profile(), extension.get());
  
      modifier.SetWithholdHostPermissions(true);
  
      EXPECT_FALSE(modifier.HasBroadGrantedHostPermissions());
  
      std::string error;
      bool allow_file_access = false;
      URLPatternSet patterns;
      patterns.Populate(test_case.hosts, Extension::kValidHostPermissionSchemes,
                        allow_file_access, &error);
      permissions_test_util::GrantRuntimePermissionsAndWaitForCompletion(
          profile(), *extension,
          PermissionSet(APIPermissionSet(), ManifestPermissionSet(),
                        std::move(patterns), URLPatternSet()));
  
      EXPECT_EQ(test_case.expected_broad_permissions,
                modifier.HasBroadGrantedHostPermissions());
    }
  }
  
  // Tests RemoveBroadGrantedHostPermissions only removes the broad permissions
  // and leaves others intact.
  TEST_F(ScriptingPermissionsModifierUnitTest,
         RemoveBroadGrantedHostPermissions) {
    InitializeEmptyExtensionService();
  
    const GURL google_com = GURL("https://google.com/*");
    const GURL example_com = GURL("https://example.com/*");
  
    // Define a list of broad patters that should give access to both URLs.
    std::string broad_patterns[] = {"https://*/*", "<all_urls>",
                                    "https://*.com/*"};
  
    for (const auto& broad_pattern : broad_patterns) {
      SCOPED_TRACE(broad_pattern);
      scoped_refptr<const Extension> extension =
          ExtensionBuilder("test: " + broad_pattern)
              .AddPermission("<all_urls>")
              .Build();
      ScriptingPermissionsModifier modifier(profile(), extension.get());
  
      modifier.SetWithholdHostPermissions(true);
  
      // Explicitly grant google.com and the broad pattern.
      modifier.GrantHostPermission(google_com);
      const URLPattern pattern(Extension::kValidHostPermissionSchemes,
                               broad_pattern);
      permissions_test_util::GrantRuntimePermissionsAndWaitForCompletion(
          profile(), *extension,
          PermissionSet(APIPermissionSet(), ManifestPermissionSet(),
                        URLPatternSet({pattern}), URLPatternSet()));
  
      EXPECT_TRUE(modifier.HasGrantedHostPermission(google_com));
      EXPECT_TRUE(modifier.HasGrantedHostPermission(example_com));
  
      // Now removing the broad patterns should leave it only with the explicit
      // google permission.
      modifier.RemoveBroadGrantedHostPermissions();
      EXPECT_TRUE(modifier.HasGrantedHostPermission(google_com));
      EXPECT_FALSE(modifier.HasGrantedHostPermission(example_com));
      EXPECT_FALSE(modifier.HasBroadGrantedHostPermissions());
    }
  }
  
  // Tests granting runtime permissions for a full host when the extension only
  // wants to run on a subset of that host.
  TEST_F(ScriptingPermissionsModifierUnitTest,
         GrantingHostPermissionsBeyondRequested) {
    InitializeEmptyExtensionService();
  
    DictionaryBuilder content_script;
    content_script
        .Set("matches", ListBuilder().Append("https://google.com/maps").Build())
        .Set("js", ListBuilder().Append("foo.js").Build());
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("test")
            .SetManifestKey("content_scripts",
                            ListBuilder().Append(content_script.Build()).Build())
            .Build();
  
    // At installation, all permissions granted.
    ScriptingPermissionsModifier modifier(profile(), extension);
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension),
                testing::UnorderedElementsAre("https://google.com/maps"));
  
    // Withhold host permissions.
    modifier.SetWithholdHostPermissions(true);
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
  
    // Grant the requested host permission. We grant origins (rather than just
    // paths), but we don't over-grant permissions to the actual extension object.
    // The active permissions on the extension should be restricted to the
    // permissions explicitly requested (google.com/maps), but the granted
    // permissions in preferences will be the full host (google.com).
    modifier.GrantHostPermission(GURL("https://google.com/maps"));
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension),
                testing::UnorderedElementsAre("https://google.com/maps"));
    EXPECT_THAT(GetPatternsAsStrings(
                    modifier.GetRevokablePermissions()->effective_hosts()),
                // Subtle: revokable permissions include permissions either in
                // the runtime granted permissions preference or active on the
                // extension object. In this case, that includes both google.com/*
                // and google.com/maps.
                testing::UnorderedElementsAre("https://google.com/maps",
                                              "https://google.com/*"));
  
    // Remove the granted permission. This should remove the permission from both
    // the active permissions on the extension object and the entry in the
    // preferences.
    modifier.RemoveAllGrantedHostPermissions();
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
    EXPECT_THAT(GetPatternsAsStrings(
                    modifier.GetRevokablePermissions()->effective_hosts()),
                testing::IsEmpty());
  }
  
  TEST_F(ScriptingPermissionsModifierUnitTest, GetSiteAccess_AllHostsExtension) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("extension").AddPermission("<all_urls>").Build();
    InitializeExtensionPermissions(profile(), *extension);
    ScriptingPermissionsModifier modifier(profile(), extension.get());
  
    const GURL example_com("https://www.example.com");
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(example_com);
      EXPECT_TRUE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_TRUE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  
    // Chrome pages should be restricted, and the extension shouldn't have access
    // to them granted or withheld.
    const GURL chrome_extensions("chrome://extensions");
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(chrome_extensions);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_TRUE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  
    // Other restricted urls should also be protected, and the extension shouldn't
    // have or want access.
    const GURL webstore = ExtensionsClient::Get()->GetWebstoreBaseURL();
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(webstore);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_TRUE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  
    modifier.SetWithholdHostPermissions(true);
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(example_com);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_TRUE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_TRUE(site_access.withheld_all_sites_access);
    }
  
    // Restricted sites should not be considered "withheld".
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(chrome_extensions);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_TRUE(site_access.withheld_all_sites_access);
    }
  
    modifier.GrantHostPermission(example_com);
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(example_com);
      EXPECT_TRUE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_TRUE(site_access.withheld_all_sites_access);
    }
  
    const GURL google_com("https://google.com");
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(google_com);
      EXPECT_TRUE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_TRUE(site_access.withheld_all_sites_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
    }
  }
  
  TEST_F(ScriptingPermissionsModifierUnitTest,
         GetSiteAccess_AllHostsLikeExtension) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("extension").AddPermission("*://*.com/*").Build();
    InitializeExtensionPermissions(profile(), *extension);
    ScriptingPermissionsModifier modifier(profile(), extension.get());
  
    const GURL example_com("https://www.example.com");
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(example_com);
      EXPECT_TRUE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_TRUE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  
    const GURL google_org("https://google.org");
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(google_org);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_TRUE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  
    modifier.SetWithholdHostPermissions(true);
  
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(example_com);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_TRUE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_TRUE(site_access.withheld_all_sites_access);
    }
  }
  
  TEST_F(ScriptingPermissionsModifierUnitTest, GetSiteAccess_SpecificSites) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("extension")
            .AddPermission("*://*.example.com/*")
            .Build();
    InitializeExtensionPermissions(profile(), *extension);
    ScriptingPermissionsModifier modifier(profile(), extension.get());
  
    const GURL example_com("https://www.example.com");
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(example_com);
      EXPECT_TRUE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  
    const GURL google_com("https://google.com");
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(google_com);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  
    modifier.SetWithholdHostPermissions(true);
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(example_com);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_TRUE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  }
  
  TEST_F(ScriptingPermissionsModifierUnitTest,
         GetSiteAccess_GrantedButNotRequested) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("extension")
            .AddPermission("*://*.example.com/*")
            .Build();
    InitializeExtensionPermissions(profile(), *extension);
    ScriptingPermissionsModifier modifier(profile(), extension.get());
    modifier.SetWithholdHostPermissions(true);
  
    const URLPatternSet google_com_pattern({URLPattern(
        Extension::kValidHostPermissionSchemes, "https://google.com/*")});
    ExtensionPrefs::Get(profile())->AddRuntimeGrantedPermissions(
        extension->id(),
        PermissionSet(APIPermissionSet(), ManifestPermissionSet(),
                      google_com_pattern.Clone(), google_com_pattern.Clone()));
  
    const GURL google_com("https://google.com");
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(google_com);
      // The has_access and withheld_access bits should be set appropriately, even
      // if the extension has access to a site it didn't request.
      EXPECT_TRUE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  }
  
  // Tests that for the purposes of displaying an extension's site access to the
  // user (or granting/revoking permissions), we ignore paths in the URL.
  TEST_F(ScriptingPermissionsModifierUnitTest, GetSiteAccess_IgnorePaths) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("extension")
            .AddContentScript("foo.js", {"https://www.example.com/foo"})
            .SetLocation(ManifestLocation::kInternal)
            .Build();
    InitializeExtensionPermissions(profile(), *extension);
  
    ScriptingPermissionsModifier modifier(profile(), extension.get());
  
    const GURL example_com("https://www.example.com/bar");
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(example_com);
      // Even though the path doesn't exactly match one in the content scripts,
      // the domain is requested, and thus we treat it as if the site was
      // requested.
      EXPECT_TRUE(site_access.has_site_access);
      EXPECT_FALSE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  
    modifier.SetWithholdHostPermissions(true);
    {
      const ScriptingPermissionsModifier::SiteAccess site_access =
          modifier.GetSiteAccess(example_com);
      EXPECT_FALSE(site_access.has_site_access);
      EXPECT_TRUE(site_access.withheld_site_access);
      EXPECT_FALSE(site_access.has_all_sites_access);
      EXPECT_FALSE(site_access.withheld_all_sites_access);
    }
  }
  
  // Tests that removing access for a host removes all patterns that grant access
  // to that host.
  TEST_F(ScriptingPermissionsModifierUnitTest,
         RemoveHostAccess_RemovesOverlappingPatterns) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("extension").AddPermission("*://*/*").Build();
    InitializeExtensionPermissions(profile(), *extension);
    ScriptingPermissionsModifier modifier(profile(), extension.get());
    modifier.SetWithholdHostPermissions(true);
  
    const URLPattern all_com_pattern(Extension::kValidHostPermissionSchemes,
                                     "https://*.com/*");
    permissions_test_util::GrantRuntimePermissionsAndWaitForCompletion(
        profile(), *extension,
        PermissionSet(APIPermissionSet(), ManifestPermissionSet(),
                      URLPatternSet({all_com_pattern}), URLPatternSet()));
  
    // Removing example.com access should result in *.com access being revoked,
    // since that is the pattern that grants access to example.com.
    const GURL example_com("https://www.example.com");
    EXPECT_TRUE(modifier.HasGrantedHostPermission(example_com));
    modifier.RemoveGrantedHostPermission(example_com);
    EXPECT_FALSE(modifier.HasGrantedHostPermission(example_com));
    EXPECT_TRUE(ExtensionPrefs::Get(profile())
                    ->GetRuntimeGrantedPermissions(extension->id())
                    ->explicit_hosts()
                    .is_empty());
  }
  
  // Test that granting <all_urls> as an optional permission, and then revoking
  // it, behaves properly. Regression test for https://crbug.com/930062.
  TEST_F(ScriptingPermissionsModifierUnitTest,
         RemoveAllURLsGrantedOptionalPermission) {
    InitializeEmptyExtensionService();
  
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("extension")
            .SetManifestKey("optional_permissions",
                            ListBuilder().Append("<all_urls>").Build())
            .Build();
    InitializeExtensionPermissions(profile(), *extension);
  
    // Also verify the extension doesn't have file access, so that <all_urls>
    // shouldn't match file URLs either.
    EXPECT_FALSE(util::AllowFileAccess(extension->id(), profile()));
  
    {
      PermissionsUpdater updater(profile());
      updater.GrantOptionalPermissions(
          *extension, PermissionsParser::GetOptionalPermissions(extension.get()),
          base::DoNothing());
    }
  
    ScriptingPermissionsModifier(profile(), extension.get())
        .SetWithholdHostPermissions(true);
  
    EXPECT_THAT(GetEffectivePatternsAsStrings(*extension), testing::IsEmpty());
  }
  
  }  // namespace extensions
  