910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/browser/permissions/permissions_updater.h"

#include <memory>
#include <utility>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_file_value_serializer.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_test_util.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_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/permissions/permissions_test_util.h"
#include "extensions/browser/permissions/scripting_permissions_modifier.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/test/permissions_manager_waiter.h"
#include "extensions/test/test_extension_dir.h"
#include "testing/gtest/include/gtest/gtest.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

using extension_test_util::LoadManifest;
using extensions::mojom::APIPermissionID;

namespace extensions {

namespace {

scoped_refptr<const Extension> CreateExtensionWithOptionalPermissions(
    base::Value::List optional_permissions,
    base::Value::List permissions,
    const std::string& name) {
  return ExtensionBuilder()
      .SetLocation(mojom::ManifestLocation::kInternal)
      .SetManifest(
          base::Value::Dict()
              .Set("name", name)
              .Set("description", "foo")
              .Set("manifest_version", 2)
              .Set("version", "0.1.2.3")
              .Set("permissions", std::move(permissions))
              .Set("optional_permissions", std::move(optional_permissions)))
      .SetID(crx_file::id_util::GenerateId(name))
      .Build();
}

class PermissionsUpdaterTest : public ExtensionServiceTestBase {};

void AddPattern(URLPatternSet* extent, const std::string& pattern) {
  int schemes = URLPattern::SCHEME_ALL;
  extent->AddPattern(URLPattern(schemes, pattern));
}

}  // namespace

// Test that the PermissionUpdater can correctly add and remove active
// permissions. This tests all of PermissionsUpdater's public methods because
// GrantActivePermissions and SetPermissions are used by AddPermissions.
TEST_F(PermissionsUpdaterTest, GrantAndRevokeOptionalPermissions) {
  InitializeEmptyExtensionService();

  // Load the test extension.
  scoped_refptr<const Extension> extension =
      ExtensionBuilder("permissions")
          .AddAPIPermission("management")
          .AddHostPermission("http://a.com/*")
          .AddOptionalAPIPermission("notifications")
          .AddOptionalHostPermission("http://*.c.com/*")
          .Build();

  {
    PermissionsUpdater updater(profile());
    updater.InitializePermissions(extension.get());
    // Grant the active permissions, as if the extension had just been
    // installed.
    updater.GrantActivePermissions(extension.get());
  }

  APIPermissionSet default_apis;
  default_apis.insert(APIPermissionID::kManagement);

  URLPatternSet default_hosts;
  AddPattern(&default_hosts, "http://a.com/*");
  PermissionSet default_permissions(default_apis.Clone(),
                                    ManifestPermissionSet(),
                                    std::move(default_hosts), URLPatternSet());

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
  std::unique_ptr<const PermissionSet> active_permissions;
  std::unique_ptr<const PermissionSet> granted_permissions;

  // Make sure it loaded properly.
  ASSERT_EQ(default_permissions,
            extension->permissions_data()->active_permissions());
  EXPECT_EQ(default_permissions,
            *prefs->GetGrantedPermissions(extension->id()));

  // Add a few permissions.
  APIPermissionSet apis;
  apis.insert(APIPermissionID::kNotifications);
  URLPatternSet hosts;
  AddPattern(&hosts, "http://*.c.com/*");

  {
    PermissionSet delta(apis.Clone(), ManifestPermissionSet(), hosts.Clone(),
                        URLPatternSet());

    PermissionsManagerWaiter waiter(PermissionsManager::Get(profile()));
    PermissionsUpdater(profile()).GrantOptionalPermissions(*extension, delta,
                                                           base::DoNothing());
    waiter.WaitForExtensionPermissionsUpdate(base::BindOnce(
        [](scoped_refptr<const Extension> extension, PermissionSet* delta,
           const Extension& actual_extension,
           const PermissionSet& actual_permissions,
           PermissionsManager::UpdateReason actual_reason) {
          ASSERT_EQ(extension.get()->id(), actual_extension.id());
          ASSERT_EQ(*delta, actual_permissions);
          ASSERT_EQ(PermissionsManager::UpdateReason::kAdded, actual_reason);
        },
        extension, &delta));

    // Make sure the extension's active permissions reflect the change.
    active_permissions = PermissionSet::CreateUnion(default_permissions, delta);
    ASSERT_EQ(*active_permissions,
              extension->permissions_data()->active_permissions());

    // Verify that the new granted and active permissions were also stored
    // in the extension preferences. In this case, the granted permissions
    // should be equal to the active permissions.
    ASSERT_EQ(*active_permissions,
              *prefs->GetDesiredActivePermissions(extension->id()));
    granted_permissions = active_permissions->Clone();
    ASSERT_EQ(*granted_permissions,
              *prefs->GetGrantedPermissions(extension->id()));
  }

  {
    // In the second part of the test, we'll remove the permissions that we
    // just added except for 'notifications'.
    apis.erase(APIPermissionID::kNotifications);
    PermissionSet delta(apis.Clone(), ManifestPermissionSet(), hosts.Clone(),
                        URLPatternSet());

    PermissionsManagerWaiter waiter(PermissionsManager::Get(profile()));
    PermissionsUpdater(profile()).RevokeOptionalPermissions(
        *extension, delta, PermissionsUpdater::REMOVE_SOFT, base::DoNothing());
    waiter.WaitForExtensionPermissionsUpdate(base::BindOnce(
        [](scoped_refptr<const Extension> extension, PermissionSet* delta,
           const Extension& actual_extension,
           const PermissionSet& actual_permissions,
           PermissionsManager::UpdateReason actual_reason) {
          ASSERT_EQ(extension.get()->id(), actual_extension.id());
          ASSERT_EQ(*delta, actual_permissions);
          ASSERT_EQ(PermissionsManager::UpdateReason::kRemoved, actual_reason);
        },
        extension, &delta));

    // Make sure the extension's active permissions reflect the change.
    active_permissions =
        PermissionSet::CreateDifference(*active_permissions, delta);
    ASSERT_EQ(*active_permissions,
              extension->permissions_data()->active_permissions());

    // Verify that the extension prefs hold the new active permissions and the
    // same granted permissions.
    ASSERT_EQ(*active_permissions,
              *prefs->GetDesiredActivePermissions(extension->id()));

    ASSERT_EQ(*granted_permissions,
              *prefs->GetGrantedPermissions(extension->id()));
  }
}

TEST_F(PermissionsUpdaterTest, RevokingPermissions) {
  InitializeEmptyExtensionService();

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());

  auto api_permission_set = [](APIPermissionID id) {
    APIPermissionSet apis;
    apis.insert(id);
    return std::make_unique<PermissionSet>(std::move(apis),
                                           ManifestPermissionSet(),
                                           URLPatternSet(), URLPatternSet());
  };

  auto can_access_page =
      [](scoped_refptr<const extensions::Extension> extension,
         const GURL& document_url) -> bool {
    PermissionsData::PageAccess access =
        extension->permissions_data()->GetPageAccess(document_url, -1, nullptr);
    return access == PermissionsData::PageAccess::kAllowed;
  };

  {
    // Test revoking optional permissions.
    auto optional_permissions =
        base::Value::List().Append("tabs").Append("cookies").Append(
            "management");
    base::Value::List required_permissions;
    required_permissions.Append("topSites");
    scoped_refptr<const Extension> extension =
        CreateExtensionWithOptionalPermissions(std::move(optional_permissions),
                                               std::move(required_permissions),
                                               "My Extension");

    PermissionsUpdater updater(profile());
    EXPECT_TRUE(updater.GetRevokablePermissions(extension.get())->IsEmpty());

    // Add the optional "cookies" permission.
    permissions_test_util::GrantOptionalPermissionsAndWaitForCompletion(
        profile(), *extension, *api_permission_set(APIPermissionID::kCookie));
    const PermissionsData* permissions = extension->permissions_data();
    // The extension should have the permission in its active permissions and
    // its granted permissions (stored in prefs). And, the permission should
    // be revokable.
    EXPECT_TRUE(permissions->HasAPIPermission(APIPermissionID::kCookie));
    std::unique_ptr<const PermissionSet> granted_permissions =
        prefs->GetGrantedPermissions(extension->id());
    EXPECT_TRUE(
        granted_permissions->HasAPIPermission(APIPermissionID::kCookie));
    EXPECT_TRUE(updater.GetRevokablePermissions(extension.get())
                    ->HasAPIPermission(APIPermissionID::kCookie));

    // Repeat with "tabs".
    permissions_test_util::GrantOptionalPermissionsAndWaitForCompletion(
        profile(), *extension, *api_permission_set(APIPermissionID::kTab));
    EXPECT_TRUE(permissions->HasAPIPermission(APIPermissionID::kTab));
    granted_permissions = prefs->GetGrantedPermissions(extension->id());
    EXPECT_TRUE(granted_permissions->HasAPIPermission(APIPermissionID::kTab));
    EXPECT_TRUE(updater.GetRevokablePermissions(extension.get())
                    ->HasAPIPermission(APIPermissionID::kTab));

    // Remove the "tabs" permission. The extension should no longer have it
    // in its active or granted permissions, and it shouldn't be revokable.
    // The extension should still have the "cookies" permission.
    permissions_test_util::RevokeOptionalPermissionsAndWaitForCompletion(
        profile(), *extension, *api_permission_set(APIPermissionID::kTab),
        PermissionsUpdater::REMOVE_HARD);
    EXPECT_FALSE(permissions->HasAPIPermission(APIPermissionID::kTab));
    granted_permissions = prefs->GetGrantedPermissions(extension->id());
    EXPECT_FALSE(granted_permissions->HasAPIPermission(APIPermissionID::kTab));
    EXPECT_FALSE(updater.GetRevokablePermissions(extension.get())
                     ->HasAPIPermission(APIPermissionID::kTab));
    EXPECT_TRUE(permissions->HasAPIPermission(APIPermissionID::kCookie));
    granted_permissions = prefs->GetGrantedPermissions(extension->id());
    EXPECT_TRUE(
        granted_permissions->HasAPIPermission(APIPermissionID::kCookie));
    EXPECT_TRUE(updater.GetRevokablePermissions(extension.get())
                    ->HasAPIPermission(APIPermissionID::kCookie));
  }

  {
    // Make sure policy restriction updates update permission data.
    URLPatternSet default_policy_blocked_hosts;
    URLPatternSet default_policy_allowed_hosts;
    URLPatternSet policy_blocked_hosts;
    URLPatternSet policy_allowed_hosts;
    base::Value::List optional_permissions;
    base::Value::List required_permissions =
        base::Value::List().Append("tabs").Append("http://*/*");
    scoped_refptr<const Extension> extension =
        CreateExtensionWithOptionalPermissions(std::move(optional_permissions),
                                               std::move(required_permissions),
                                               "ExtensionSettings");
    AddPattern(&default_policy_blocked_hosts, "http://*.google.com/*");
    PermissionsUpdater updater(profile());
    updater.InitializePermissions(extension.get());
    PermissionsData::SetDefaultPolicyHostRestrictions(
        util::GetBrowserContextId(profile()), default_policy_blocked_hosts,
        default_policy_allowed_hosts);

    // By default, all subdomains of google.com should be blocked.
    const GURL kOrigin("http://foo.com");
    const GURL kGoogle("http://www.google.com");
    const GURL kExampleGoogle("http://example.google.com");
    EXPECT_TRUE(
        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
    EXPECT_TRUE(can_access_page(extension, kOrigin));
    EXPECT_FALSE(can_access_page(extension, kGoogle));
    EXPECT_FALSE(can_access_page(extension, kExampleGoogle));

    AddPattern(&default_policy_allowed_hosts, "http://example.google.com/*");
    // Give the extension access to example.google.com. Now the
    // example.google.com should not be a runtime blocked host.
    updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
                                             default_policy_allowed_hosts);

    EXPECT_TRUE(
        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
    EXPECT_TRUE(can_access_page(extension, kOrigin));
    EXPECT_FALSE(can_access_page(extension, kGoogle));
    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));

    // Revoke extension access to foo.com. Now, foo.com should be a runtime
    // blocked host.
    AddPattern(&default_policy_blocked_hosts, "*://*.foo.com/");
    updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
                                             default_policy_allowed_hosts);
    EXPECT_TRUE(
        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
    EXPECT_FALSE(can_access_page(extension, kOrigin));
    EXPECT_FALSE(can_access_page(extension, kGoogle));
    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));

    // Remove foo.com from blocked hosts. The extension should no longer have
    // be a runtime blocked host.
    default_policy_blocked_hosts.ClearPatterns();
    AddPattern(&default_policy_blocked_hosts, "*://*.foo.com/");
    updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
                                             default_policy_allowed_hosts);
    EXPECT_TRUE(
        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
    EXPECT_FALSE(can_access_page(extension, kOrigin));
    EXPECT_TRUE(can_access_page(extension, kGoogle));
    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));

    // Set an empty individual policy, should not affect default policy.
    updater.SetPolicyHostRestrictions(extension.get(), policy_blocked_hosts,
                                      policy_allowed_hosts);
    EXPECT_FALSE(
        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
    EXPECT_TRUE(can_access_page(extension, kOrigin));
    EXPECT_TRUE(can_access_page(extension, kGoogle));
    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));

    // Block google.com for the Individual scope.
    // Allowlist example.google.com for the Indiviaul scope.
    // Leave google.com and example.google.com off both the allowlist and
    // blocklist for Default scope.
    AddPattern(&policy_blocked_hosts, "*://*.google.com/*");
    AddPattern(&policy_allowed_hosts, "*://example.google.com/*");
    updater.SetPolicyHostRestrictions(extension.get(), policy_blocked_hosts,
                                      policy_allowed_hosts);
    EXPECT_FALSE(
        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
    EXPECT_TRUE(can_access_page(extension, kOrigin));
    EXPECT_FALSE(can_access_page(extension, kGoogle));
    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));

    // Switch back to default scope for extension.
    updater.SetUsesDefaultHostRestrictions(extension.get());
    EXPECT_TRUE(
        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
    default_policy_blocked_hosts.ClearPatterns();
    default_policy_allowed_hosts.ClearPatterns();
    updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
                                             default_policy_allowed_hosts);
  }
}

TEST_F(PermissionsUpdaterTest,
       UpdatingRuntimeGrantedPermissionsWithOptionalPermissions) {
  InitializeEmptyExtensionService();

  scoped_refptr<const Extension> extension =
      ExtensionBuilder("extension").AddOptionalAPIPermission("tabs").Build();

  PermissionsUpdater updater(profile());
  updater.InitializePermissions(extension.get());
  // Grant the active permissions, as if the extension had just been installed.
  updater.GrantActivePermissions(extension.get());

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());

  // Initially, there should be no runtime-granted permissions or granted
  // permissions.
  EXPECT_TRUE(prefs->GetRuntimeGrantedPermissions(extension->id())->IsEmpty());
  EXPECT_TRUE(prefs->GetGrantedPermissions(extension->id())->IsEmpty());

  APIPermissionSet apis;
  apis.insert(APIPermissionID::kTab);
  PermissionSet optional_permissions(std::move(apis), ManifestPermissionSet(),
                                     URLPatternSet(), URLPatternSet());

  // Granting permissions should update both runtime-granted permissions and
  // granted permissions.
  permissions_test_util::GrantOptionalPermissionsAndWaitForCompletion(
      profile(), *extension, optional_permissions);
  EXPECT_EQ(optional_permissions,
            *prefs->GetRuntimeGrantedPermissions(extension->id()));
  EXPECT_EQ(optional_permissions,
            *prefs->GetGrantedPermissions(extension->id()));

  // Removing permissions with REMOVE_SOFT should not remove the permission
  // from runtime-granted permissions or granted permissions; this happens when
  // the extension opts into lower privilege.
  permissions_test_util::RevokeOptionalPermissionsAndWaitForCompletion(
      profile(), *extension, optional_permissions,
      PermissionsUpdater::REMOVE_SOFT);
  EXPECT_EQ(optional_permissions,
            *prefs->GetRuntimeGrantedPermissions(extension->id()));
  EXPECT_EQ(optional_permissions,
            *prefs->GetGrantedPermissions(extension->id()));

  // Removing permissions with REMOVE_HARD should remove the permission from
  // runtime-granted and granted permissions; this happens when the user chooses
  // to revoke the permission.
  // Note: we need to add back the permission first, so it shows up as a
  // revokable permission.
  // TODO(devlin): Inactive, but granted, permissions should be revokable.
  permissions_test_util::GrantOptionalPermissionsAndWaitForCompletion(
      profile(), *extension, optional_permissions);
  permissions_test_util::RevokeOptionalPermissionsAndWaitForCompletion(
      profile(), *extension, optional_permissions,
      PermissionsUpdater::REMOVE_HARD);
  EXPECT_TRUE(prefs->GetRuntimeGrantedPermissions(extension->id())->IsEmpty());
  EXPECT_TRUE(prefs->GetGrantedPermissions(extension->id())->IsEmpty());
}

TEST_F(PermissionsUpdaterTest,
       UpdatingRuntimeGrantedPermissionsWithRuntimePermissions) {
  InitializeEmptyExtensionService();

  scoped_refptr<const Extension> extension =
      ExtensionBuilder("extension").AddHostPermission("*://*/*").Build();

  PermissionsUpdater updater(profile());
  updater.InitializePermissions(extension.get());
  // Grant the active permissions, as if the extension had just been installed.
  updater.GrantActivePermissions(extension.get());
  ScriptingPermissionsModifier(profile(), extension)
      .SetWithholdHostPermissions(true);

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());

  // Initially, there should be no runtime-granted permissions.
  EXPECT_TRUE(prefs->GetRuntimeGrantedPermissions(extension->id())->IsEmpty());
  std::unique_ptr<const PermissionSet> initial_granted_permissions =
      prefs->GetGrantedPermissions(extension->id());
  // Granted permissions should contain the required permissions from the
  // extension.
  EXPECT_TRUE(initial_granted_permissions->explicit_hosts().ContainsPattern(
      URLPattern(Extension::kValidHostPermissionSchemes, "*://*/*")));

  URLPatternSet explicit_hosts({URLPattern(
      Extension::kValidHostPermissionSchemes, "https://example.com/*")});
  PermissionSet runtime_granted_permissions(
      APIPermissionSet(), ManifestPermissionSet(), std::move(explicit_hosts),
      URLPatternSet());

  // Granting runtime-granted permissions should update the runtime granted
  // permissions store in preferences, but *not* granted permissions in
  // preferences.
  permissions_test_util::GrantRuntimePermissionsAndWaitForCompletion(
      profile(), *extension, runtime_granted_permissions);
  EXPECT_EQ(runtime_granted_permissions,
            *prefs->GetRuntimeGrantedPermissions(extension->id()));
  EXPECT_EQ(*initial_granted_permissions,
            *prefs->GetGrantedPermissions(extension->id()));

  // Removing runtime-granted permissions should not remove the permission
  // from runtime-granted permissions; granted permissions should remain
  // unchanged.
  permissions_test_util::RevokeRuntimePermissionsAndWaitForCompletion(
      profile(), *extension, runtime_granted_permissions);

  EXPECT_TRUE(prefs->GetRuntimeGrantedPermissions(extension->id())->IsEmpty());
  EXPECT_EQ(*initial_granted_permissions,
            *prefs->GetGrantedPermissions(extension->id()));
}

TEST_F(PermissionsUpdaterTest, RevokingPermissionsWithRuntimeHostPermissions) {
  InitializeEmptyExtensionService();

  constexpr struct {
    const char* permission;
    const char* test_url;
  } test_cases[] = {
      {"http://*/*", "http://foo.com"},
      {"http://google.com/*", "http://google.com"},
  };

  for (const auto& test_case : test_cases) {
    std::string test_name =
        base::StringPrintf("%s, %s", test_case.permission, test_case.test_url);
    SCOPED_TRACE(test_name);
    scoped_refptr<const Extension> extension =
        CreateExtensionWithOptionalPermissions(
            base::Value::List(),
            base::Value::List().Append(test_case.permission), test_name);
    PermissionsUpdater updater(profile());
    updater.InitializePermissions(extension.get());

    ScriptingPermissionsModifier(profile(), extension)
        .SetWithholdHostPermissions(true);

    // Host access was withheld, so the extension shouldn't have access to the
    // test site.
    const GURL kOrigin(test_case.test_url);

    EXPECT_FALSE(extension->permissions_data()
                     ->active_permissions()
                     .HasExplicitAccessToOrigin(kOrigin));
    EXPECT_TRUE(updater.GetRevokablePermissions(extension.get())->IsEmpty());
    EXPECT_TRUE(extension->permissions_data()
                    ->withheld_permissions()
                    .HasExplicitAccessToOrigin(kOrigin));

    URLPatternSet url_pattern_set;
    url_pattern_set.AddOrigin(URLPattern::SCHEME_ALL, kOrigin);
    const PermissionSet permission_set(
        APIPermissionSet(), ManifestPermissionSet(), std::move(url_pattern_set),
        URLPatternSet());
    // Give the extension access to the test site. Now, the test site permission
    // should be revokable.
    permissions_test_util::GrantRuntimePermissionsAndWaitForCompletion(
        profile(), *extension, permission_set);
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .HasExplicitAccessToOrigin(kOrigin));
    EXPECT_TRUE(updater.GetRevokablePermissions(extension.get())
                    ->HasExplicitAccessToOrigin(kOrigin));

    // Revoke the test site permission. The extension should no longer have
    // access to test site, and the revokable permissions should be empty.
    permissions_test_util::RevokeRuntimePermissionsAndWaitForCompletion(
        profile(), *extension, permission_set);
    EXPECT_FALSE(extension->permissions_data()
                     ->active_permissions()
                     .HasExplicitAccessToOrigin(kOrigin));
    EXPECT_TRUE(extension->permissions_data()
                    ->withheld_permissions()
                    .HasExplicitAccessToOrigin(kOrigin));
    EXPECT_TRUE(updater.GetRevokablePermissions(extension.get())->IsEmpty());
  }
}

TEST_F(PermissionsUpdaterTest, ChromeFaviconIsNotARevokableHost) {
  InitializeEmptyExtensionService();

  URLPattern chrome_favicon_pattern(Extension::kValidHostPermissionSchemes,
                                    "chrome://favicon/");

  {
    // This test exercises chrome://favicon, which is only available in MV2.
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("favicon extension")
            .SetManifestVersion(2)
            .AddHostPermissions({"https://example.com/*", "chrome://favicon/*"})
            .Build();
    URLPattern example_com_pattern(Extension::kValidHostPermissionSchemes,
                                   "https://example.com/*");
    PermissionsUpdater updater(profile());
    updater.InitializePermissions(extension.get());

    // To start, the extension should have both example.com and chrome://favicon
    // permissions.
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .ContainsPattern(chrome_favicon_pattern));
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .ContainsPattern(example_com_pattern));

    // Only example.com should be revokable - chrome://favicon is not a real
    // host permission.
    std::unique_ptr<const PermissionSet> revokable_permissions =
        updater.GetRevokablePermissions(extension.get());
    EXPECT_FALSE(revokable_permissions->explicit_hosts().ContainsPattern(
        chrome_favicon_pattern));
    EXPECT_TRUE(revokable_permissions->explicit_hosts().ContainsPattern(
        example_com_pattern));

    // Withholding host permissions shouldn't withhold example.com.
    ScriptingPermissionsModifier(profile(), extension)
        .SetWithholdHostPermissions(true);
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .ContainsPattern(chrome_favicon_pattern));
    EXPECT_FALSE(extension->permissions_data()
                     ->active_permissions()
                     .explicit_hosts()
                     .ContainsPattern(example_com_pattern));
  }
  {
    // This test exercises chrome://favicon, which is only available in MV2.
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("all urls extension")
            .SetManifestVersion(2)
            .AddHostPermission("<all_urls>")
            .Build();
    URLPattern all_urls_pattern(
        Extension::kValidHostPermissionSchemes &
            ~(URLPattern::SCHEME_CHROMEUI | URLPattern::SCHEME_FILE),
        "<all_urls>");
    PermissionsUpdater updater(profile());
    updater.InitializePermissions(extension.get());

    // <all_urls> (strangely) includes the chrome://favicon/ permission.
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .ContainsPattern(chrome_favicon_pattern));
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .ContainsPattern(all_urls_pattern));

    std::unique_ptr<const PermissionSet> revokable_permissions =
        updater.GetRevokablePermissions(extension.get());
    EXPECT_FALSE(revokable_permissions->explicit_hosts().ContainsPattern(
        chrome_favicon_pattern));
    EXPECT_TRUE(revokable_permissions->explicit_hosts().ContainsPattern(
        all_urls_pattern));

    ScriptingPermissionsModifier(profile(), extension)
        .SetWithholdHostPermissions(true);
    EXPECT_TRUE(extension->permissions_data()
                    ->active_permissions()
                    .explicit_hosts()
                    .ContainsPattern(chrome_favicon_pattern));
    EXPECT_FALSE(extension->permissions_data()
                     ->active_permissions()
                     .explicit_hosts()
                     .ContainsPattern(all_urls_pattern));
  }
}

// Tests runtime-granting permissions beyond what are explicitly requested by
// the extension.
TEST_F(PermissionsUpdaterTest, GrantingBroadRuntimePermissions) {
  InitializeEmptyExtensionService();

  scoped_refptr<const Extension> extension =
      ExtensionBuilder("extension")
          .AddHostPermission("https://maps.google.com/*")
          .Build();

  const URLPattern kMapsPattern(Extension::kValidHostPermissionSchemes,
                                "https://maps.google.com/*");
  const URLPattern kAllGooglePattern(Extension::kValidHostPermissionSchemes,
                                     "https://*.google.com/*");

  // Withhold host permissions. Effective hosts should be empty.
  PermissionsUpdater updater(profile());
  updater.InitializePermissions(extension.get());
  ScriptingPermissionsModifier(profile(), extension)
      .SetWithholdHostPermissions(true);
  EXPECT_TRUE(extension->permissions_data()
                  ->active_permissions()
                  .effective_hosts()
                  .is_empty());

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());

  {
    // Verify initial state. The extension "active" permissions in preferences
    // represent the permissions that would be active on the extension without
    // the runtime host permissions feature. Thus, this should include the
    // requested host permissions, and nothing more.
    std::unique_ptr<const PermissionSet> active_prefs =
        prefs->GetDesiredActivePermissions(extension->id());
    EXPECT_TRUE(active_prefs->effective_hosts().ContainsPattern(kMapsPattern));
    EXPECT_FALSE(
        active_prefs->effective_hosts().ContainsPattern(kAllGooglePattern));

    // Runtime granted permissions should not contain any permissions (all
    // hosts are withheld).
    std::unique_ptr<const PermissionSet> runtime_granted_prefs =
        prefs->GetRuntimeGrantedPermissions(extension->id());
    EXPECT_FALSE(
        runtime_granted_prefs->effective_hosts().ContainsPattern(kMapsPattern));
    EXPECT_FALSE(runtime_granted_prefs->effective_hosts().ContainsPattern(
        kAllGooglePattern));
  }

  // Grant permission to all google.com domains.
  const PermissionSet runtime_permissions(
      APIPermissionSet(), ManifestPermissionSet(),
      URLPatternSet({kAllGooglePattern}), URLPatternSet());
  permissions_test_util::GrantRuntimePermissionsAndWaitForCompletion(
      profile(), *extension, runtime_permissions);

  // The extension object's permission should never include un-requested
  // permissions, so it should only include maps.google.com.
  EXPECT_TRUE(extension->permissions_data()
                  ->active_permissions()
                  .effective_hosts()
                  .ContainsPattern(kMapsPattern));
  EXPECT_FALSE(extension->permissions_data()
                   ->active_permissions()
                   .effective_hosts()
                   .ContainsPattern(kAllGooglePattern));

  {
    // The active permissions in preferences should reflect the extension's
    // permission state without the runtime host permissions feature, so should
    // still include exactly the requested permissions.
    std::unique_ptr<const PermissionSet> active_prefs =
        prefs->GetDesiredActivePermissions(extension->id());
    EXPECT_TRUE(active_prefs->effective_hosts().ContainsPattern(kMapsPattern));
    EXPECT_FALSE(
        active_prefs->effective_hosts().ContainsPattern(kAllGooglePattern));
    // The runtime-granted permissions should include all permissions that have
    // been granted, which in this case includes google.com subdomains.
    std::unique_ptr<const PermissionSet> runtime_granted_prefs =
        prefs->GetRuntimeGrantedPermissions(extension->id());
    EXPECT_TRUE(
        runtime_granted_prefs->effective_hosts().ContainsPattern(kMapsPattern));
    EXPECT_TRUE(runtime_granted_prefs->effective_hosts().ContainsPattern(
        kAllGooglePattern));
  }

  // Revoke the host permission.
  permissions_test_util::RevokeRuntimePermissionsAndWaitForCompletion(
      profile(), *extension, runtime_permissions);

  EXPECT_FALSE(extension->permissions_data()
                   ->active_permissions()
                   .effective_hosts()
                   .ContainsPattern(kMapsPattern));

  {
    // Active permissions in the preferences should remain constant (unaffected
    // by the runtime host permissions feature).
    std::unique_ptr<const PermissionSet> active_prefs =
        prefs->GetDesiredActivePermissions(extension->id());
    EXPECT_TRUE(active_prefs->effective_hosts().ContainsPattern(kMapsPattern));
    EXPECT_FALSE(
        active_prefs->effective_hosts().ContainsPattern(kAllGooglePattern));
    // The runtime granted preferences should be empty again.
    std::unique_ptr<const PermissionSet> runtime_granted_prefs =
        prefs->GetRuntimeGrantedPermissions(extension->id());
    EXPECT_FALSE(
        runtime_granted_prefs->effective_hosts().ContainsPattern(kMapsPattern));
    EXPECT_FALSE(runtime_granted_prefs->effective_hosts().ContainsPattern(
        kAllGooglePattern));
  }
}

// Validates that we don't overwrite an extension's desired active permissions
// based on its current active permissions during an optional permissions grant.
// Regression test for https://crbug.com/1343643.
TEST_F(PermissionsUpdaterTest,
       DontOverwriteDesiredActivePermissionsOnOptionalPermissionsGrant) {
  InitializeEmptyExtensionService();

  scoped_refptr<const Extension> extension =
      CreateExtensionWithOptionalPermissions(
          /*optional_permissions=*/base::Value::List().Append("tabs"),
          /*permissions=*/
          base::Value::List().Append("https://example.com/*"),
          "optional grant");
  ASSERT_TRUE(extension);

  {
    // Grant the active permissions, as if the extension had just been
    // installed.
    PermissionsUpdater updater(profile());
    updater.InitializePermissions(extension.get());
    updater.GrantActivePermissions(extension.get());
  }

  // Withhold host permissions. This shouldn't affect the extension's
  // desired active permissions.
  ScriptingPermissionsModifier(profile(), extension)
      .SetWithholdHostPermissions(true);

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());

  GURL example_com("https://example.com");
  EXPECT_FALSE(extension->permissions_data()->HasHostPermission(example_com));
  EXPECT_TRUE(prefs->GetDesiredActivePermissions(extension->id())
                  ->effective_hosts()
                  .MatchesURL(example_com));

  {
    // Grant an optional permission.
    APIPermissionSet apis;
    apis.insert(APIPermissionID::kTab);
    permissions_test_util::GrantOptionalPermissionsAndWaitForCompletion(
        profile(), *extension,
        PermissionSet(std::move(apis), ManifestPermissionSet(), URLPatternSet(),
                      URLPatternSet()));
  }

  // Verify the desired active permissions. The extension should still have
  // example.com as a desired host.
  EXPECT_FALSE(extension->permissions_data()->HasHostPermission(example_com));
  EXPECT_TRUE(prefs->GetDesiredActivePermissions(extension->id())
                  ->effective_hosts()
                  .MatchesURL(example_com));
}

// Validates that we don't overwrite an extension's desired active permissions
// based on its initial effective active permissions on load (which could be
// different, in the case of withheld host permissions).
// Regression test for https://crbug.com/1343643.
TEST_F(PermissionsUpdaterTest,
       DontOverwriteDesiredActivePermissionsOnExtensionLoad) {
  InitializeEmptyExtensionService();

  static constexpr char kManifest[] =
      R"({
           "name": "Test Extension",
           "manifest_version": 3,
           "version": "0.1",
           "host_permissions": ["<all_urls>"]
         })";
  TestExtensionDir test_dir;
  test_dir.WriteManifest(kManifest);

  scoped_refptr<const Extension> extension =
      ChromeTestExtensionLoader(profile()).LoadExtension(
          test_dir.UnpackedPath());

  const ExtensionId id = extension->id();
  ASSERT_TRUE(registry()->enabled_extensions().Contains(id));

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());

  // The extension's desired active permissions should include <all_urls>.
  EXPECT_TRUE(prefs->GetDesiredActivePermissions(id)
                  ->effective_hosts()
                  .MatchesAllURLs());

  // Withhold host permissions. This shouldn't affect the extension's desired
  // active permissions, which should still include <all_urls>.
  ScriptingPermissionsModifier(profile(), extension)
      .SetWithholdHostPermissions(true);
  EXPECT_TRUE(prefs->GetDesiredActivePermissions(extension->id())
                  ->effective_hosts()
                  .MatchesAllURLs());
  EXPECT_FALSE(extension->permissions_data()
                   ->active_permissions()
                   .effective_hosts()
                   .MatchesAllURLs());

  // Reload extensions.
  service()->ReloadExtensionsForTest();
  extension = registry()->enabled_extensions().GetByID(id);
  ASSERT_TRUE(extension);

  // The extension's desired active permissions should remain unchanged, and
  // should include <all_urls>.
  EXPECT_TRUE(prefs->GetDesiredActivePermissions(id)
                  ->effective_hosts()
                  .MatchesAllURLs());
  EXPECT_FALSE(extension->permissions_data()
                   ->active_permissions()
                   .effective_hosts()
                   .MatchesAllURLs());
}

// Validates that extension desired active permissions are restored to a sane
// state on extension load (including all required permissions).
TEST_F(PermissionsUpdaterTest, DesiredActivePermissionsAreFixedOnLoad) {
  InitializeEmptyExtensionService();

  static constexpr char kManifest[] =
      R"({
           "name": "Test Extension",
           "manifest_version": 3,
           "version": "0.1",
           "permissions": ["tabs"],
           "host_permissions": ["https://requested.example/*"]
         })";
  TestExtensionDir test_dir;
  test_dir.WriteManifest(kManifest);

  scoped_refptr<const Extension> extension =
      ChromeTestExtensionLoader(profile()).LoadExtension(
          test_dir.UnpackedPath());
  ASSERT_TRUE(extension);

  const ExtensionId id = extension->id();
  ASSERT_TRUE(registry()->enabled_extensions().Contains(id));

  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());

  const GURL requested_url("https://requested.example");
  const GURL unrequested_url("https://unrequested.example");

  // The extension's desired active permissions should include example.com and
  // tabs.
  {
    std::unique_ptr<const PermissionSet> desired =
        prefs->GetDesiredActivePermissions(id);
    EXPECT_TRUE(desired->effective_hosts().MatchesURL(requested_url));
    EXPECT_FALSE(desired->effective_hosts().MatchesURL(unrequested_url));
    EXPECT_TRUE(desired->HasAPIPermission(APIPermissionID::kTab));
    EXPECT_FALSE(desired->HasAPIPermission(APIPermissionID::kBookmark));
  }

  // Mangle the desired permissions in prefs (a la pref corruption, bugs, etc).
  {
    APIPermissionSet apis;
    apis.insert(APIPermissionID::kBookmark);
    URLPatternSet patterns;
    patterns.AddOrigin(Extension::kValidHostPermissionSchemes, unrequested_url);
    prefs->SetDesiredActivePermissions(
        id, PermissionSet(std::move(apis), ManifestPermissionSet(),
                          std::move(patterns), URLPatternSet()));
  }

  // Reload extensions.
  service()->ReloadExtensionsForTest();
  extension = registry()->enabled_extensions().GetByID(id);
  ASSERT_TRUE(extension);

  // The extension's desired active permissions should have been restored to
  // their sane state of example.com and tabs.
  {
    std::unique_ptr<const PermissionSet> desired =
        prefs->GetDesiredActivePermissions(id);
    EXPECT_TRUE(desired->effective_hosts().MatchesURL(requested_url));
    EXPECT_FALSE(desired->effective_hosts().MatchesURL(unrequested_url));
    EXPECT_TRUE(desired->HasAPIPermission(APIPermissionID::kTab));
    EXPECT_FALSE(desired->HasAPIPermission(APIPermissionID::kBookmark));
  }
}

class PermissionsUpdaterTestWithEnhancedHostControls
    : public PermissionsUpdaterTest {
 public:
  PermissionsUpdaterTestWithEnhancedHostControls() {
    std::vector<base::test::FeatureRef> enabled_features = {
        extensions_features::kExtensionsMenuAccessControl,
        extensions_features::kExtensionsMenuAccessControlWithPermittedSites};
    std::vector<base::test::FeatureRef> disabled_features = {};
    feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }
  ~PermissionsUpdaterTestWithEnhancedHostControls() override = default;

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Tests the behavior of revoking permissions from the extension while the
// user has specified a set of sites that all extensions are allowed to run on.
TEST_F(PermissionsUpdaterTestWithEnhancedHostControls,
       RevokingPermissionsWithUserPermittedSites) {
  InitializeEmptyExtensionService();

  // Install and initialize an extension that wants to run everywhere.
  scoped_refptr<const Extension> extension =
      ExtensionBuilder("extension").AddHostPermission("<all_urls>").Build();

  {
    PermissionsUpdater updater(profile());
    updater.InitializePermissions(extension.get());
    updater.GrantActivePermissions(extension.get());
  }

  // Note that the PermissionsManger requires the extension to be in the
  // ExtensionRegistry, so add it through the ExtensionRegistrar.
  registrar()->AddExtension(extension);

  const GURL first_url("http://first.example");
  const GURL second_url("http://second.example");

  PermissionsManager* permissions_manager = PermissionsManager::Get(profile());
  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());

  {
    // Simulate the user allowing all extensions to run on `first_url`.
    PermissionsManagerWaiter waiter(permissions_manager);
    permissions_manager->AddUserPermittedSite(url::Origin::Create(first_url));
    waiter.WaitForUserPermissionsSettingsChange();
  }

  auto get_site_access = [extension](const GURL& url) {
    return extension->permissions_data()->GetPageAccess(url, -1, nullptr);
  };

  auto has_desired_active_permission_for_url = [extension,
                                                prefs](const GURL& url) {
    return prefs->GetDesiredActivePermissions(extension->id())
        ->effective_hosts()
        .MatchesURL(url);
  };

  auto has_runtime_permission_for_url = [extension, prefs](const GURL& url) {
    return prefs->GetRuntimeGrantedPermissions(extension->id())
        ->effective_hosts()
        .MatchesURL(url);
  };

  auto has_granted_permission_for_url = [extension, prefs](const GURL& url) {
    return prefs->GetGrantedPermissions(extension->id())
        ->effective_hosts()
        .MatchesURL(url);
  };

  // By default, the extension should have permission to both sites, since it
  // has access to all URLs.
  EXPECT_EQ(PermissionsData::PageAccess::kAllowed, get_site_access(first_url));
  EXPECT_EQ(PermissionsData::PageAccess::kAllowed, get_site_access(second_url));
  // The desired permission should include both, as well, as should the
  // granted.
  EXPECT_TRUE(has_desired_active_permission_for_url(first_url));
  EXPECT_TRUE(has_desired_active_permission_for_url(second_url));
  EXPECT_TRUE(has_granted_permission_for_url(first_url));
  EXPECT_TRUE(has_granted_permission_for_url(second_url));
  // The extension does not yet have any runtime granted permissions.
  EXPECT_FALSE(has_runtime_permission_for_url(first_url));
  EXPECT_FALSE(has_runtime_permission_for_url(second_url));

  // Withhold host permissions from the extension.
  ScriptingPermissionsModifier(profile(), extension)
      .SetWithholdHostPermissions(true);

  // The extension should be allowed to run on `first_url`, since the
  // user indicated all extensions can always run there. However, it should not
  // be allowed on `second_url`.
  EXPECT_EQ(PermissionsData::PageAccess::kAllowed, get_site_access(first_url));
  EXPECT_EQ(PermissionsData::PageAccess::kWithheld,
            get_site_access(second_url));
  // The desired permissions (indicating the extension's desired state) and
  // the granted permissions (indicating the install-time granted permissions)
  // should be unchanged, including both sites.
  EXPECT_TRUE(has_desired_active_permission_for_url(first_url));
  EXPECT_TRUE(has_desired_active_permission_for_url(second_url));
  EXPECT_TRUE(has_granted_permission_for_url(first_url));
  EXPECT_TRUE(has_granted_permission_for_url(second_url));
  // The runtime permissions should also be unchanged. Even though the extension
  // is allowed to run on `first_url`, it does not have runtime access to
  // that site (this is important if the user later removes the site from
  // permitted sites).
  EXPECT_FALSE(has_runtime_permission_for_url(first_url));
  EXPECT_FALSE(has_runtime_permission_for_url(second_url));

  // Now, grant the extension explicit access to `second_url`.
  ScriptingPermissionsModifier(profile(), extension)
      .GrantHostPermission(second_url);

  // The extension should now be allowed to run on both sites.
  EXPECT_EQ(PermissionsData::PageAccess::kAllowed, get_site_access(first_url));
  EXPECT_EQ(PermissionsData::PageAccess::kAllowed, get_site_access(second_url));
  // Desired and granted permissions remain unchanged.
  EXPECT_TRUE(has_desired_active_permission_for_url(first_url));
  EXPECT_TRUE(has_desired_active_permission_for_url(second_url));
  EXPECT_TRUE(has_granted_permission_for_url(first_url));
  EXPECT_TRUE(has_granted_permission_for_url(second_url));
  // The extension should have runtime access for `second_url`, since it
  // was granted explicit access to it by the user.
  EXPECT_FALSE(has_runtime_permission_for_url(first_url));
  EXPECT_TRUE(has_runtime_permission_for_url(second_url));

  {
    // (Temporarily) add `second_url` as a user-permitted site.
    PermissionsManagerWaiter waiter(permissions_manager);
    permissions_manager->AddUserPermittedSite(url::Origin::Create(second_url));
    waiter.WaitForUserPermissionsSettingsChange();
  }

  // All sites should be accessible; permissions should be unchanged.
  EXPECT_EQ(PermissionsData::PageAccess::kAllowed, get_site_access(first_url));
  EXPECT_EQ(PermissionsData::PageAccess::kAllowed, get_site_access(second_url));
  EXPECT_TRUE(has_desired_active_permission_for_url(first_url));
  EXPECT_TRUE(has_desired_active_permission_for_url(second_url));
  EXPECT_TRUE(has_granted_permission_for_url(first_url));
  EXPECT_TRUE(has_granted_permission_for_url(second_url));
  EXPECT_FALSE(has_runtime_permission_for_url(first_url));
  EXPECT_TRUE(has_runtime_permission_for_url(second_url));

  // Remove both sites from the permitted sites.
  {
    PermissionsManagerWaiter waiter(permissions_manager);
    permissions_manager->RemoveUserPermittedSite(
        url::Origin::Create(first_url));
    waiter.WaitForUserPermissionsSettingsChange();
  }
  {
    PermissionsManagerWaiter waiter(permissions_manager);
    permissions_manager->RemoveUserPermittedSite(
        url::Origin::Create(second_url));
    waiter.WaitForUserPermissionsSettingsChange();
  }

  // Now, `first_url` should be withheld, since it's no longer a permitted
  // site. However, `second_url` should still be accessible, because the
  // extension had explicit access to that site.
  EXPECT_EQ(PermissionsData::PageAccess::kWithheld, get_site_access(first_url));
  EXPECT_EQ(PermissionsData::PageAccess::kAllowed, get_site_access(second_url));
  EXPECT_TRUE(has_desired_active_permission_for_url(first_url));
  EXPECT_TRUE(has_desired_active_permission_for_url(second_url));
  EXPECT_TRUE(has_granted_permission_for_url(first_url));
  EXPECT_TRUE(has_granted_permission_for_url(second_url));
  EXPECT_FALSE(has_runtime_permission_for_url(first_url));
  EXPECT_TRUE(has_runtime_permission_for_url(second_url));
}

}  // namespace extensions