// Copyright 2021 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_manager.h"

#include "base/memory/raw_ptr.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/platform_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_test.h"
#include "extensions/browser/permissions/scripting_permissions_modifier.h"
#include "extensions/browser/pref_types.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/url_pattern_set.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"

namespace {

std::unique_ptr<KeyedService> SetTestingPermissionsManager(
    content::BrowserContext* browser_context) {
  return std::make_unique<extensions::PermissionsManager>(browser_context);
}

}  // namespace

namespace extensions {

using UserSiteSetting = PermissionsManager::UserSiteSetting;
using UserSiteAccess = PermissionsManager::UserSiteAccess;

class PermissionsManagerUnittest : public ExtensionsTest {
 public:
  PermissionsManagerUnittest()
      : ExtensionsTest(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  ~PermissionsManagerUnittest() override = default;
  PermissionsManagerUnittest(const PermissionsManagerUnittest&) = delete;
  PermissionsManagerUnittest& operator=(const PermissionsManagerUnittest&) =
      delete;

  scoped_refptr<const Extension> AddExtension(const std::string& name);
  scoped_refptr<const Extension> AddExtensionWithAPIPermission(
      const std::string& name,
      const std::string& permission,
      extensions::mojom::ManifestLocation location =
          extensions::mojom::ManifestLocation::kUnpacked);
  scoped_refptr<const Extension> AddExtensionWithHostPermission(
      const std::string& name,
      const std::string& host_permission);
  scoped_refptr<const Extension> AddExtensionWithActiveTab(
      const std::string& name,
      extensions::mojom::ManifestLocation location =
          extensions::mojom::ManifestLocation::kUnpacked);

  // Returns the restricted sites stored in `manager_`.
  std::set<url::Origin> GetRestrictedSitesFromManager();
  // Returns the permittes sites stored in `manager_`.
  std::set<url::Origin> GetPermittedSitesFromManager();

  // Returns the restricted sites stored in `extension_prefs_`.
  const base::Value* GetRestrictedSitesFromPrefs();
  // Returns the permitted sites stored in `extension_prefs_`.
  const base::Value* GetPermittedSitesFromPrefs();

  // Returns the restricted sites stored in `PermissionsData`.
  std::set<std::string> GetRestrictedSitesFromPermissionsData();
  // Returns the permitted sites stored in `PermissionsData`.
  std::set<std::string> GetPermittedSitesFromPermissionsData();

 protected:
  // ExtensionsTest:
  void SetUp() override;

  // PermissionsManager being tested.
  raw_ptr<PermissionsManager, DanglingUntriaged> manager_;

  raw_ptr<ExtensionPrefs, DanglingUntriaged> extension_prefs_;
};

void PermissionsManagerUnittest::SetUp() {
  ExtensionsTest::SetUp();
  manager_ = static_cast<PermissionsManager*>(
      PermissionsManager::GetFactory()->SetTestingFactoryAndUse(
          browser_context(),
          base::BindRepeating(&SetTestingPermissionsManager)));

  extension_prefs_ = ExtensionPrefs::Get(browser_context());
}

scoped_refptr<const Extension> PermissionsManagerUnittest::AddExtension(
    const std::string& name) {
  return AddExtensionWithHostPermission(name, "");
}

scoped_refptr<const Extension>
PermissionsManagerUnittest::AddExtensionWithAPIPermission(
    const std::string& name,
    const std::string& permission,
    extensions::mojom::ManifestLocation location) {
  scoped_refptr<const extensions::Extension> extension =
      extensions::ExtensionBuilder(name)
          .AddAPIPermission(permission)
          .SetLocation(location)
          .Build();
  DCHECK(extension->permissions_data()->HasAPIPermission(permission));

  ExtensionRegistryFactory::GetForBrowserContext(browser_context())
      ->AddEnabled(extension);

  return extension;
}

scoped_refptr<const Extension>
PermissionsManagerUnittest::AddExtensionWithHostPermission(
    const std::string& name,
    const std::string& host_permission) {
  scoped_refptr<const extensions::Extension> extension =
      extensions::ExtensionBuilder(name)
          .AddHostPermission(host_permission)
          .Build();

  ExtensionRegistryFactory::GetForBrowserContext(browser_context())
      ->AddEnabled(extension);

  return extension;
}

scoped_refptr<const Extension>
PermissionsManagerUnittest::AddExtensionWithActiveTab(
    const std::string& name,
    extensions::mojom::ManifestLocation location) {
  return AddExtensionWithAPIPermission(name, "activeTab", location);
}

const base::Value* PermissionsManagerUnittest::GetRestrictedSitesFromPrefs() {
  const base::DictValue& permissions =
      extension_prefs_->GetPrefAsDictionary(kUserPermissions);
  return permissions.Find("restricted_sites");
}

const base::Value* PermissionsManagerUnittest::GetPermittedSitesFromPrefs() {
  const base::DictValue& permissions =
      extension_prefs_->GetPrefAsDictionary(kUserPermissions);
  return permissions.Find("permitted_sites");
}

std::set<url::Origin>
PermissionsManagerUnittest::GetRestrictedSitesFromManager() {
  const PermissionsManager::UserPermissionsSettings& permissions =
      manager_->GetUserPermissionsSettings();
  return permissions.restricted_sites;
}

std::set<url::Origin>
PermissionsManagerUnittest::GetPermittedSitesFromManager() {
  const PermissionsManager::UserPermissionsSettings& permissions =
      manager_->GetUserPermissionsSettings();
  return permissions.permitted_sites;
}

std::set<std::string>
PermissionsManagerUnittest::GetRestrictedSitesFromPermissionsData() {
  std::set<std::string> string_patterns;
  URLPatternSet patterns = PermissionsData::GetUserBlockedHosts(
      util::GetBrowserContextId(browser_context()));
  for (const auto& pattern : patterns)
    string_patterns.insert(pattern.GetAsString());
  return string_patterns;
}

std::set<std::string>
PermissionsManagerUnittest::GetPermittedSitesFromPermissionsData() {
  std::set<std::string> string_patterns;
  URLPatternSet patterns = PermissionsData::GetUserAllowedHosts(
      util::GetBrowserContextId(browser_context()));
  for (const auto& pattern : patterns)
    string_patterns.insert(pattern.GetAsString());
  return string_patterns;
}

TEST_F(PermissionsManagerUnittest, AddAndRemoveRestrictedSite) {
  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
  const std::string expected_url_pattern = "http://a.example.com/*";
  std::set<url::Origin> set_with_url;
  set_with_url.insert(url);
  base::ListValue value_with_url;
  value_with_url.Append(url.Serialize());

  // Verify the restricted sites list is empty.
  EXPECT_EQ(GetRestrictedSitesFromManager(), std::set<url::Origin>());
  EXPECT_EQ(GetRestrictedSitesFromPrefs(), nullptr);
  EXPECT_THAT(GetRestrictedSitesFromPermissionsData(), testing::IsEmpty());
  EXPECT_EQ(manager_->GetUserSiteSetting(url),
            UserSiteSetting::kCustomizeByExtension);

  // Add `url` to restricted sites. Verify the site is stored both in manager
  // and prefs restricted sites.
  manager_->AddUserRestrictedSite(url);
  EXPECT_EQ(GetRestrictedSitesFromManager(), set_with_url);
  EXPECT_EQ(*GetRestrictedSitesFromPrefs(), value_with_url);
  EXPECT_THAT(GetRestrictedSitesFromPermissionsData(),
              testing::UnorderedElementsAre(expected_url_pattern));
  EXPECT_EQ(manager_->GetUserSiteSetting(url),
            UserSiteSetting::kBlockAllExtensions);

  // Adding an existent restricted site. Verify the entry is not duplicated.
  manager_->AddUserRestrictedSite(url);
  EXPECT_EQ(GetRestrictedSitesFromManager(), set_with_url);
  EXPECT_EQ(*GetRestrictedSitesFromPrefs(), value_with_url);
  EXPECT_THAT(GetRestrictedSitesFromPermissionsData(),
              testing::UnorderedElementsAre(expected_url_pattern));

  // Remove `url` from restricted sites. Verify the site is removed from both
  // manager and prefs restricted sites.
  manager_->RemoveUserRestrictedSite(url);
  EXPECT_EQ(GetRestrictedSitesFromManager(), std::set<url::Origin>());
  EXPECT_EQ(*GetRestrictedSitesFromPrefs(),
            base::Value(base::Value::Type::LIST));
  EXPECT_THAT(GetRestrictedSitesFromPermissionsData(), testing::IsEmpty());
  EXPECT_EQ(manager_->GetUserSiteSetting(url),
            UserSiteSetting::kCustomizeByExtension);
}

TEST_F(PermissionsManagerUnittest, AddAndRemovePermittedSite) {
  // Verify the permitted sites list is empty.
  EXPECT_EQ(GetPermittedSitesFromManager(), std::set<url::Origin>());
  EXPECT_EQ(GetPermittedSitesFromPrefs(), nullptr);
  EXPECT_THAT(GetPermittedSitesFromPermissionsData(), testing::IsEmpty());

  // Adding or removing a permitted site is only supported when
  // kExtensionsMenuAccessControlWithPermittedSites is enabled.
  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
  EXPECT_DCHECK_DEATH(manager_->AddUserPermittedSite(url));
  EXPECT_DCHECK_DEATH(manager_->RemoveUserPermittedSite(url));
}

TEST_F(PermissionsManagerUnittest, UpdateUserSiteSetting) {
  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
  std::set<url::Origin> empty_set;
  std::set<url::Origin> set_with_url;
  set_with_url.insert(url);

  {
    // Granting all extensions is only supported when
    // kExtensionsMenuAccessControlWithPermittedSites flag is enabled.
    EXPECT_DCHECK_DEATH(manager_->UpdateUserSiteSetting(
        url, PermissionsManager::UserSiteSetting::kGrantAllExtensions));
  }

  {
    manager_->UpdateUserSiteSetting(
        url, PermissionsManager::UserSiteSetting::kBlockAllExtensions);
    const PermissionsManager::UserPermissionsSettings& actual_permissions =
        manager_->GetUserPermissionsSettings();
    EXPECT_EQ(actual_permissions.restricted_sites, set_with_url);
    EXPECT_EQ(actual_permissions.permitted_sites, empty_set);
    EXPECT_EQ(manager_->GetUserSiteSetting(url),
              PermissionsManager::UserSiteSetting::kBlockAllExtensions);
  }

  {
    manager_->UpdateUserSiteSetting(
        url, PermissionsManager::UserSiteSetting::kCustomizeByExtension);
    const PermissionsManager::UserPermissionsSettings& actual_permissions =
        manager_->GetUserPermissionsSettings();
    EXPECT_EQ(actual_permissions.restricted_sites, empty_set);
    EXPECT_EQ(actual_permissions.permitted_sites, empty_set);
    EXPECT_EQ(manager_->GetUserSiteSetting(url),
              PermissionsManager::UserSiteSetting::kCustomizeByExtension);
  }
}

TEST_F(PermissionsManagerUnittest, GetSiteAccess_AllUrls) {
  auto extension =
      AddExtensionWithHostPermission("AllUrls Extension", "<all_urls>");

  const GURL non_restricted_url("https://www.non-restricted.com");
  {
    const PermissionsManager::ExtensionSiteAccess site_access =
        manager_->GetSiteAccess(*extension, non_restricted_url);
    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 grant
  // or withheld site access.
  const GURL restricted_url("chrome://extensions");
  {
    const PermissionsManager::ExtensionSiteAccess site_access =
        manager_->GetSiteAccess(*extension, restricted_url);
    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);
  }
}

TEST_F(PermissionsManagerUnittest, GetSiteAccess_RequestedUrl) {
  auto extension = AddExtensionWithHostPermission("RequestedUrl Extension",
                                                  "*://*.requested.com/*");

  const GURL requested_url("https://www.requested.com");
  {
    const PermissionsManager::ExtensionSiteAccess site_access =
        manager_->GetSiteAccess(*extension, requested_url);
    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 non_requested_url("https://non-requested.com");
  {
    const PermissionsManager::ExtensionSiteAccess site_access =
        manager_->GetSiteAccess(*extension, non_requested_url);
    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);
  }
}

// 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. We
// always strip the path from host permissions directly, but we don't strip the
// path from content scripts.
TEST_F(PermissionsManagerUnittest,
       GetSiteAccess_ContentScript_RequestedUrlWithPath) {
  scoped_refptr<const Extension> extension =
      ExtensionBuilder("extension")
          .AddContentScript("foo.js", {"https://www.example.com/foo"})
          .SetLocation(mojom::ManifestLocation::kInternal)
          .Build();
  ExtensionRegistryFactory::GetForBrowserContext(browser_context())
      ->AddEnabled(extension);

  const GURL other_path_url("https://www.example.com/bar");
  {
    const PermissionsManager::ExtensionSiteAccess site_access =
        manager_->GetSiteAccess(*extension, other_path_url);
    // Even though the path doesn't match the one requested, the domain does
    // match 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);
  }
}

TEST_F(PermissionsManagerUnittest, GetSiteAccess_ActiveTab) {
  auto extension = AddExtensionWithActiveTab("ActiveTab Extension");

  const GURL url("https://example.com");
  {
    const PermissionsManager::ExtensionSiteAccess site_access =
        manager_->GetSiteAccess(*extension, url);
    // The site access computation does not take into account active tab, and
    // therefore it does not have or withheld any access.
    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);
  }
}

TEST_F(PermissionsManagerUnittest, GetSiteAccess_NoHostPermissions) {
  auto extension = AddExtension("Test");

  const GURL url("https://example.com");
  {
    const PermissionsManager::ExtensionSiteAccess site_access =
        manager_->GetSiteAccess(*extension, url);
    // The site access computation does not take into account active tab, and
    // therefore it does not have or withheld any access.
    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);
  }
}

TEST_F(PermissionsManagerUnittest, CanAffectExtension_ByLocation) {
  struct {
    mojom::ManifestLocation location;
    bool can_be_affected;
  } test_cases[] = {
      {mojom::ManifestLocation::kInternal, true},
      {mojom::ManifestLocation::kExternalPref, true},
      {mojom::ManifestLocation::kUnpacked, true},
      {mojom::ManifestLocation::kExternalPolicyDownload, false},
      {mojom::ManifestLocation::kComponent, false},
  };

  for (const auto& test_case : test_cases) {
    scoped_refptr<const Extension> extension =
        ExtensionBuilder("test")
            .SetLocation(test_case.location)
            .AddHostPermission("<all_urls>")
            .Build();
    EXPECT_EQ(manager_->CanAffectExtension(*extension),
              test_case.can_be_affected)
        << test_case.location;
  }
}

TEST_F(PermissionsManagerUnittest, CanUserSelectSiteAccess_AllUrls) {
  auto extension =
      AddExtensionWithHostPermission("AllUrls Extension", "<all_urls>");

  // Verify "on click", "on site" and "on all sites" site access can be selected
  // for a non-restricted url.
  const GURL url("http://www.example.com");
  EXPECT_TRUE(manager_->CanUserSelectSiteAccess(*extension, url,
                                                UserSiteAccess::kOnClick));
  EXPECT_TRUE(manager_->CanUserSelectSiteAccess(*extension, url,
                                                UserSiteAccess::kOnSite));
  EXPECT_TRUE(manager_->CanUserSelectSiteAccess(*extension, url,
                                                UserSiteAccess::kOnAllSites));

  // Verify "on click", "on site" and "on all sites" cannot be selected for a
  // restricted url.
  const GURL chrome_url("chrome://settings");
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, chrome_url,
                                                 UserSiteAccess::kOnClick));
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, chrome_url,
                                                 UserSiteAccess::kOnSite));
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, chrome_url,
                                                 UserSiteAccess::kOnAllSites));
}

TEST_F(PermissionsManagerUnittest, CanUserSelectSiteAccess_SpecificUrl) {
  const GURL url_a("http://www.a.com");
  auto extension = AddExtensionWithHostPermission("A Extension", url_a.spec());

  // Verify "on click" and "on site" can be selected for the specific url, but
  // "on all sites" cannot be selected.
  EXPECT_TRUE(manager_->CanUserSelectSiteAccess(*extension, url_a,
                                                UserSiteAccess::kOnClick));
  EXPECT_TRUE(manager_->CanUserSelectSiteAccess(*extension, url_a,
                                                UserSiteAccess::kOnSite));
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, url_a,
                                                 UserSiteAccess::kOnAllSites));

  // Verify "on click", "on site" and "on all sites" cannot be selected for any
  // other url.
  const GURL url_b("http://www.b.com");
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, url_b,
                                                 UserSiteAccess::kOnClick));
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, url_b,
                                                 UserSiteAccess::kOnSite));
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, url_b,
                                                 UserSiteAccess::kOnAllSites));
}

TEST_F(PermissionsManagerUnittest, CanUserSelectSiteAccess_NoHostPermissions) {
  auto extension = AddExtension("Extension");

  // Verify "on click", "on site" and "on all sites" cannot be selected for any
  // url.
  const GURL url("http://www.example.com");
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, url,
                                                 UserSiteAccess::kOnClick));
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, url,
                                                 UserSiteAccess::kOnSite));
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, url,
                                                 UserSiteAccess::kOnAllSites));
}

TEST_F(PermissionsManagerUnittest, CanUserSelectSiteAccess_ActiveTab) {
  auto extension = AddExtensionWithActiveTab("ActiveTab Extension");

  // Verify "on click" can be selected for the specific url, but "on site" and
  // "on all sites" cannot be selected.
  const GURL url("http://www.example.com");
  EXPECT_TRUE(manager_->CanUserSelectSiteAccess(*extension, url,
                                                UserSiteAccess::kOnClick));
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, url,
                                                 UserSiteAccess::kOnSite));
  EXPECT_FALSE(manager_->CanUserSelectSiteAccess(*extension, url,
                                                 UserSiteAccess::kOnAllSites));
}

TEST_F(PermissionsManagerUnittest, HasActiveTabAndCanAccess_PolicyUrl) {
  auto extension = AddExtensionWithActiveTab("ActiveTab Extension");
  auto enterprise_extension = AddExtensionWithActiveTab(
      "ActiveTab Extension",
      extensions::mojom::ManifestLocation::kExternalPolicy);

  int context_id = extensions::util::GetBrowserContextId(browser_context());
  extension->permissions_data()->SetContextId(context_id);
  extension->permissions_data()->SetUsesDefaultHostRestrictions();
  enterprise_extension->permissions_data()->SetContextId(context_id);
  enterprise_extension->permissions_data()->SetUsesDefaultHostRestrictions();

  // Add a policy-blocked site.
  URLPattern default_policy_blocked_pattern =
      URLPattern(URLPattern::SCHEME_ALL, "*://*.policy-blocked.com/*");
  extensions::URLPatternSet default_allowed_hosts;
  extensions::URLPatternSet default_blocked_hosts;
  default_blocked_hosts.AddPattern(default_policy_blocked_pattern);
  extensions::PermissionsData::SetDefaultPolicyHostRestrictions(
      context_id, default_blocked_hosts, default_allowed_hosts);

  // Allow enterprise extension access to policy-blocked site.
  extensions::URLPatternSet allowed_hosts;
  extensions::URLPatternSet blocked_hosts;
  allowed_hosts.AddPattern(default_policy_blocked_pattern);
  enterprise_extension->permissions_data()->SetPolicyHostRestrictions(
      blocked_hosts, allowed_hosts);

  // Verify only enterprise extension can have access with activeTab to
  // policy-blocked site.
  const GURL policy_url("http://www.policy-blocked.com");
  EXPECT_FALSE(manager_->HasActiveTabAndCanAccess(*extension, policy_url));
  EXPECT_TRUE(
      manager_->HasActiveTabAndCanAccess(*enterprise_extension, policy_url));
}

// Tests that HasRequestedHostPermissions returns true only for extensions
// that explicitly requested host permissions.
TEST_F(PermissionsManagerUnittest, HasRequestedHostPermissions) {
  auto no_permissions_extension = AddExtension("Extension");
  auto requested_site_extension = AddExtensionWithHostPermission(
      "RequestedUrl Extension", "*://*.requested.com/*");
  auto all_urls_extension = AddExtensionWithHostPermission(
      "RequestedUrl Extension", "*://*.requested.com/*");
  auto active_tab_extension = AddExtensionWithActiveTab("ActiveTab Extension");

  EXPECT_FALSE(
      manager_->HasRequestedHostPermissions(*no_permissions_extension));
  EXPECT_TRUE(manager_->HasRequestedHostPermissions(*requested_site_extension));
  EXPECT_TRUE(manager_->HasRequestedHostPermissions(*all_urls_extension));
  EXPECT_FALSE(manager_->HasRequestedHostPermissions(*active_tab_extension));
}

TEST_F(PermissionsManagerUnittest, HasRequestedActiveTab) {
  auto no_permissions_extension = AddExtension("Extension");
  auto requested_site_extension = AddExtensionWithHostPermission(
      "RequestedUrl Extension", "*://*.requested.com/*");
  auto dnr_extension =
      AddExtensionWithAPIPermission("DNR extension", "declarativeNetRequest");
  auto active_tab_extension = AddExtensionWithActiveTab("ActiveTab Extension");

  // Verify that HasRequestedActiveTab returns true only for extensions
  // that explicitly requested activeTab.
  EXPECT_FALSE(manager_->HasRequestedActiveTab(*no_permissions_extension));
  EXPECT_FALSE(manager_->HasRequestedActiveTab(*requested_site_extension));
  EXPECT_FALSE(manager_->HasRequestedActiveTab(*dnr_extension));
  EXPECT_TRUE(manager_->HasRequestedActiveTab(*active_tab_extension));
}

class PermissionsManagerWithPermittedSitesUnitTest
    : public PermissionsManagerUnittest {
 public:
  PermissionsManagerWithPermittedSitesUnitTest();
  PermissionsManagerWithPermittedSitesUnitTest(
      const PermissionsManagerWithPermittedSitesUnitTest&) = delete;
  const PermissionsManagerWithPermittedSitesUnitTest& operator=(
      const PermissionsManagerWithPermittedSitesUnitTest&) = delete;
  ~PermissionsManagerWithPermittedSitesUnitTest() override = default;

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

PermissionsManagerWithPermittedSitesUnitTest::
    PermissionsManagerWithPermittedSitesUnitTest() {
  feature_list_.InitAndEnableFeature(
      extensions_features::kExtensionsMenuAccessControlWithPermittedSites);
}

TEST_F(PermissionsManagerWithPermittedSitesUnitTest,
       AddAndRemovePermittedSite) {
  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
  const std::string expected_url_pattern = "http://a.example.com/*";
  std::set<url::Origin> set_with_url;
  set_with_url.insert(url);
  base::ListValue value_with_url;
  value_with_url.Append(url.Serialize());

  // Verify the permitted sites list is empty.
  EXPECT_EQ(GetPermittedSitesFromManager(), std::set<url::Origin>());
  EXPECT_EQ(GetPermittedSitesFromPrefs(), nullptr);
  EXPECT_THAT(GetPermittedSitesFromPermissionsData(), testing::IsEmpty());
  EXPECT_EQ(manager_->GetUserSiteSetting(url),
            PermissionsManager::UserSiteSetting::kCustomizeByExtension);

  manager_->AddUserPermittedSite(url);

  // Verify the site is stored both in manager and prefs permitted sites.
  EXPECT_EQ(GetPermittedSitesFromManager(), set_with_url);
  EXPECT_EQ(*GetPermittedSitesFromPrefs(), value_with_url);
  EXPECT_THAT(GetPermittedSitesFromPermissionsData(),
              testing::UnorderedElementsAre(expected_url_pattern));
  EXPECT_EQ(manager_->GetUserSiteSetting(url),
            PermissionsManager::UserSiteSetting::kGrantAllExtensions);

  // Adding an existent permitted site.
  manager_->AddUserPermittedSite(url);

  // Verify the entry is not duplicated.
  EXPECT_EQ(GetPermittedSitesFromManager(), set_with_url);
  EXPECT_EQ(*GetPermittedSitesFromPrefs(), value_with_url);
  EXPECT_THAT(GetPermittedSitesFromPermissionsData(),
              testing::UnorderedElementsAre(expected_url_pattern));

  // Remove `url` from permitted sites. Verify the site is removed from both
  // manager and prefs permitted sites.
  manager_->RemoveUserPermittedSite(url);
  EXPECT_EQ(GetPermittedSitesFromManager(), std::set<url::Origin>());
  EXPECT_EQ(*GetPermittedSitesFromPrefs(),
            base::Value(base::Value::Type::LIST));
  EXPECT_THAT(GetPermittedSitesFromPermissionsData(), testing::IsEmpty());
  EXPECT_EQ(manager_->GetUserSiteSetting(url),
            PermissionsManager::UserSiteSetting::kCustomizeByExtension);
}

TEST_F(PermissionsManagerWithPermittedSitesUnitTest, GrantAllExtensionsAccess) {
  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
  std::set<url::Origin> empty_set;
  std::set<url::Origin> set_with_url;
  set_with_url.insert(url);

  manager_->UpdateUserSiteSetting(
      url, PermissionsManager::UserSiteSetting::kGrantAllExtensions);
  const PermissionsManager::UserPermissionsSettings& actual_permissions =
      manager_->GetUserPermissionsSettings();
  EXPECT_EQ(actual_permissions.restricted_sites, empty_set);
  EXPECT_EQ(actual_permissions.permitted_sites, set_with_url);
  EXPECT_EQ(manager_->GetUserSiteSetting(url),
            PermissionsManager::UserSiteSetting::kGrantAllExtensions);
}

TEST_F(PermissionsManagerWithPermittedSitesUnitTest,
       RestrictedAndPermittedSitesAreMutuallyExclusive) {
  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
  std::set<url::Origin> empty_set;
  std::set<url::Origin> set_with_url;
  set_with_url.insert(url);

  {
    manager_->AddUserRestrictedSite(url);
    const PermissionsManager::UserPermissionsSettings& actual_permissions =
        manager_->GetUserPermissionsSettings();
    EXPECT_EQ(actual_permissions.restricted_sites, set_with_url);
    EXPECT_EQ(actual_permissions.permitted_sites, empty_set);
    EXPECT_EQ(manager_->GetUserSiteSetting(url),
              PermissionsManager::UserSiteSetting::kBlockAllExtensions);
  }

  {
    // Adding an url to the permitted sites that is already in the restricted
    // sites should remove it from restricted sites and add it to permitted
    // sites.
    manager_->AddUserPermittedSite(url);
    const PermissionsManager::UserPermissionsSettings& actual_permissions =
        manager_->GetUserPermissionsSettings();
    EXPECT_EQ(actual_permissions.restricted_sites, empty_set);
    EXPECT_EQ(actual_permissions.permitted_sites, set_with_url);
    EXPECT_EQ(manager_->GetUserSiteSetting(url),
              PermissionsManager::UserSiteSetting::kGrantAllExtensions);
  }

  {
    // Adding an url to the restricted sites that is already in the permitted
    // sites should remove it from permitted sites and add it to restricted
    // sites.
    manager_->AddUserRestrictedSite(url);
    const PermissionsManager::UserPermissionsSettings& actual_permissions =
        manager_->GetUserPermissionsSettings();
    EXPECT_EQ(actual_permissions.restricted_sites, set_with_url);
    EXPECT_EQ(actual_permissions.permitted_sites, empty_set);
    EXPECT_EQ(manager_->GetUserSiteSetting(url),
              PermissionsManager::UserSiteSetting::kBlockAllExtensions);
  }
}

TEST_F(PermissionsManagerUnittest, HostAccessRequestCooldown) {
  auto extension =
      AddExtensionWithHostPermission("Extension", "https://example.com/*");
  ScriptingPermissionsModifier(browser_context(), extension)
      .SetWithholdHostPermissions(true);

  std::unique_ptr<content::WebContents> web_contents(
      content::WebContentsTester::CreateTestWebContents(browser_context(),
                                                        nullptr));
  int tab_id = 1;

  content::WebContentsTester::For(web_contents.get())
      ->NavigateAndCommit(GURL("https://example.com"));

  // Add request.
  EXPECT_EQ(
      PermissionsManager::AddRequestResult::kSuccess,
      manager_->AddHostAccessRequest(web_contents.get(), tab_id, *extension));
  EXPECT_TRUE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));

  // Remove request immediately. Should fail due to cooldown.
  auto result = manager_->RemoveHostAccessRequest(tab_id, extension->id());
  EXPECT_EQ(result, PermissionsManager::RemoveRequestResult::kThrottled);
  EXPECT_TRUE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));

  // Wait for cooldown to expire.
  task_environment()->FastForwardBy(base::Seconds(2));

  // Remove request again. Should succeed.
  result = manager_->RemoveHostAccessRequest(tab_id, extension->id());
  EXPECT_EQ(result, PermissionsManager::RemoveRequestResult::kSuccess);
  EXPECT_FALSE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));
}

TEST_F(PermissionsManagerUnittest, HostAccessRequestCooldownBypass) {
  auto extension =
      AddExtensionWithHostPermission("Extension", "https://example.com/*");
  ScriptingPermissionsModifier(browser_context(), extension)
      .SetWithholdHostPermissions(true);

  std::unique_ptr<content::WebContents> web_contents(
      content::WebContentsTester::CreateTestWebContents(browser_context(),
                                                        nullptr));
  int tab_id = 1;

  content::WebContentsTester::For(web_contents.get())
      ->NavigateAndCommit(GURL("https://example.com"));

  // Add request.
  EXPECT_EQ(
      PermissionsManager::AddRequestResult::kSuccess,
      manager_->AddHostAccessRequest(web_contents.get(), tab_id, *extension));
  EXPECT_TRUE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));

  // Remove request immediately with bypass_cooldown=true. Should succeed.
  auto result =
      manager_->RemoveHostAccessRequest(tab_id, extension->id(), std::nullopt,
                                        /*bypass_cooldown=*/true);
  EXPECT_EQ(result, PermissionsManager::RemoveRequestResult::kSuccess);
  EXPECT_FALSE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));
}

TEST_F(PermissionsManagerUnittest,
       HostAccessRequestDismissalBypassesAndThrottles) {
  auto extension =
      AddExtensionWithHostPermission("Extension", "https://example.com/*");
  ScriptingPermissionsModifier(browser_context(), extension)
      .SetWithholdHostPermissions(true);

  std::unique_ptr<content::WebContents> web_contents(
      content::WebContentsTester::CreateTestWebContents(browser_context(),
                                                        nullptr));
  int tab_id = 1;

  content::WebContentsTester::For(web_contents.get())
      ->NavigateAndCommit(GURL("https://example.com"));

  // Add request.
  EXPECT_EQ(
      PermissionsManager::AddRequestResult::kSuccess,
      manager_->AddHostAccessRequest(web_contents.get(), tab_id, *extension));
  EXPECT_TRUE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));

  // Dismiss request. Dismissal bypasses cooldown for removal.
  manager_->UserDismissedHostAccessRequest(web_contents.get(), tab_id,
                                           extension->id());
  EXPECT_FALSE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));

  // However, trying to immediately re-add the request should fail (cooldown
  // applies).
  EXPECT_EQ(
      PermissionsManager::AddRequestResult::kThrottled,
      manager_->AddHostAccessRequest(web_contents.get(), tab_id, *extension));
  EXPECT_FALSE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));

  // Wait for cooldown to expire.
  task_environment()->FastForwardBy(base::Seconds(2));

  // Adding the request should succeed now.
  EXPECT_EQ(
      PermissionsManager::AddRequestResult::kSuccess,
      manager_->AddHostAccessRequest(web_contents.get(), tab_id, *extension));
  EXPECT_FALSE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));
}

TEST_F(PermissionsManagerUnittest, HostAccessRequestUpdateCooldown) {
  auto extension =
      AddExtensionWithHostPermission("Extension", "https://example.com/*");
  ScriptingPermissionsModifier(browser_context(), extension)
      .SetWithholdHostPermissions(true);

  std::unique_ptr<content::WebContents> web_contents(
      content::WebContentsTester::CreateTestWebContents(browser_context(),
                                                        nullptr));
  int tab_id = 1;

  content::WebContentsTester::For(web_contents.get())
      ->NavigateAndCommit(GURL("https://example.com"));

  class TestObserver : public PermissionsManager::Observer {
   public:
    explicit TestObserver(PermissionsManager* manager) : manager_(manager) {
      manager_->AddObserver(this);
    }
    ~TestObserver() override { manager_->RemoveObserver(this); }

    void OnHostAccessRequestUpdated(const ExtensionId& extension_id,
                                    int tab_id) override {
      updated_count_++;
    }

    int updated_count() const { return updated_count_; }

   private:
    raw_ptr<PermissionsManager> manager_;
    int updated_count_ = 0;
  };

  TestObserver observer(manager_);

  // Add request. Should succeed.
  EXPECT_EQ(
      PermissionsManager::AddRequestResult::kSuccess,
      manager_->AddHostAccessRequest(web_contents.get(), tab_id, *extension));
  EXPECT_TRUE(manager_->HasActiveHostAccessRequest(tab_id, extension->id()));
  EXPECT_EQ(observer.updated_count(), 0);

  // Update request immediately. Should fail due to cooldown.
  EXPECT_EQ(
      PermissionsManager::AddRequestResult::kThrottled,
      manager_->AddHostAccessRequest(web_contents.get(), tab_id, *extension));
  EXPECT_EQ(observer.updated_count(), 0);

  // Wait for cooldown to expire.
  task_environment()->FastForwardBy(base::Seconds(2));

  // Update request again. Should succeed.
  EXPECT_EQ(
      PermissionsManager::AddRequestResult::kSuccess,
      manager_->AddHostAccessRequest(web_contents.get(), tab_id, *extension));
  EXPECT_EQ(observer.updated_count(), 1);
}

}  // namespace extensions
