blob: abb8df85003568b7efd5e94169b0683409af71ff [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/extensions/extensions_menu_view_model.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/permissions/permissions_updater.h"
#include "chrome/browser/extensions/permissions/scripting_permissions_modifier.h"
#include "chrome/browser/extensions/permissions/site_permissions_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/extensions/extensions_menu_view_platform_delegate.h"
#include "components/crx_file/id_util.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/common/extension_builder.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
namespace {
using PermissionsManager = extensions::PermissionsManager;
using SitePermissionsHelper = extensions::SitePermissionsHelper;
// A mock extensions menu platform delegate.
class TestPlatformDelegate : public ExtensionsMenuViewPlatformDelegate {
public:
TestPlatformDelegate() = default;
~TestPlatformDelegate() override = default;
void AttachToModel(ExtensionsMenuViewModel* model) override {}
void DetachFromModel() override {}
void OnHostAccessRequestAddedOrUpdated(
const extensions::ExtensionId& extension_id,
content::WebContents* web_contents) override {}
void OnAccessRequestRemoved(
const extensions::ExtensionId& extension_id) override {}
void OnActionAdded(const ToolbarActionsModel::ActionId& action_id) override {}
};
} // namespace
class ExtensionsMenuViewModelBrowserTest
: public extensions::ExtensionBrowserTest {
public:
ExtensionsMenuViewModelBrowserTest() = default;
~ExtensionsMenuViewModelBrowserTest() override = default;
// Adds an extension with the given `host_permission`.
scoped_refptr<const extensions::Extension> AddExtensionWithHostPermission(
const std::string& name,
const std::string& host_permission);
// Adds an extension with `activeTab` permission.
scoped_refptr<const extensions::Extension> AddActiveTabExtension(
const std::string& name);
// Adds an `extension` with the given `host_permissions`,
// `permissions` and `location`.
scoped_refptr<const extensions::Extension> AddExtension(
const std::string& name,
const std::vector<std::string>& permissions,
const std::vector<std::string>& host_permissions);
ExtensionsMenuViewModel* menu_model() { return menu_model_.get(); }
SitePermissionsHelper* permissions_helper() {
return permissions_helper_.get();
}
PermissionsManager* permissions_manager() { return permissions_manager_; }
// ExtensionBrowserTest:
void SetUpOnMainThread() override;
void TearDownOnMainThread() override;
private:
std::unique_ptr<ExtensionsMenuViewModel> menu_model_;
std::unique_ptr<SitePermissionsHelper> permissions_helper_;
raw_ptr<PermissionsManager> permissions_manager_;
};
scoped_refptr<const extensions::Extension>
ExtensionsMenuViewModelBrowserTest::AddExtensionWithHostPermission(
const std::string& name,
const std::string& host_permission) {
return AddExtension(name, /*permissions=*/{}, {host_permission});
}
scoped_refptr<const extensions::Extension>
ExtensionsMenuViewModelBrowserTest::AddActiveTabExtension(
const std::string& name) {
return AddExtension(name, /*permissions=*/{"activeTab"},
/*host_permissions=*/{});
}
scoped_refptr<const extensions::Extension>
ExtensionsMenuViewModelBrowserTest::AddExtension(
const std::string& name,
const std::vector<std::string>& permissions,
const std::vector<std::string>& host_permissions) {
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder(name)
.AddAPIPermissions(permissions)
.AddHostPermissions(host_permissions)
.SetID(crx_file::id_util::GenerateId(name))
.Build();
extension_registrar()->AddExtension(extension.get());
return extension;
}
void ExtensionsMenuViewModelBrowserTest::SetUpOnMainThread() {
ExtensionBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
menu_model_ = std::make_unique<ExtensionsMenuViewModel>(
browser_window_interface(), std::make_unique<TestPlatformDelegate>());
permissions_helper_ = std::make_unique<SitePermissionsHelper>(profile());
permissions_manager_ = PermissionsManager::Get(profile());
}
void ExtensionsMenuViewModelBrowserTest::ExtensionsMenuViewModelBrowserTest::
TearDownOnMainThread() {
permissions_manager_ = nullptr;
permissions_helper_.reset();
menu_model_.reset();
ExtensionBrowserTest::TearDownOnMainThread();
}
// Tests that the extensions menu view model correctly updates the site access
// for an extension.
IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewModelBrowserTest, UpdateSiteAccess) {
// Add extension that requests host permissions.
scoped_refptr<const extensions::Extension> extension =
AddExtensionWithHostPermission("Extension", "<all_urls>");
// Navigate to a site the extension has site access to.
const GURL url =
embedded_test_server()->GetURL("example.com", "/simple.html");
ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), url));
content::WebContents* web_contents = GetActiveWebContents();
// Verify default initial site access is "on all sites".
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnAllSites);
// Update site access to "on site".
menu_model()->UpdateSiteAccess(extension->id(),
PermissionsManager::UserSiteAccess::kOnSite);
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kGranted);
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnSite);
// Update site access to "on click".
menu_model()->UpdateSiteAccess(extension->id(),
PermissionsManager::UserSiteAccess::kOnClick);
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kWithheld);
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnClick);
}
// Tests that the extensions menu view model correctly grants site access to an
// extension that requests hosts permissions and access is currently withheld.
IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewModelBrowserTest,
GrantSiteAccess_HostPermission) {
// Add extension that requests host permissions, and withheld site access.
scoped_refptr<const extensions::Extension> extension =
AddExtensionWithHostPermission("Extension", "*://example.com/*");
extensions::ScriptingPermissionsModifier modifier(profile(), extension);
modifier.SetWithholdHostPermissions(true);
// Navigate to a site the extension requested access to.
const GURL url =
embedded_test_server()->GetURL("example.com", "/simple.html");
ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), url));
content::WebContents* web_contents = GetActiveWebContents();
// Verify site interaction is 'withheld' and site access is 'on click'
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kWithheld);
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnClick);
// Granting site access changes site interaction to 'granted' and site access
// to 'on site'.
menu_model()->GrantSiteAccess(extension->id());
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kGranted);
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnSite);
}
// Tests that the extensions menu view model correctly grants site
// access for an extension with activeTab permission.
IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewModelBrowserTest,
GrantSiteAccess_ActiveTab) {
// Add extension with activeTab permission.
scoped_refptr<const extensions::Extension> extension =
AddActiveTabExtension("Extension");
// Navigate to any (unrestricted) site.
const GURL url =
embedded_test_server()->GetURL("example.com", "/simple.html");
ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), url));
content::WebContents* web_contents = GetActiveWebContents();
// Verify site interaction is 'activeTab' and site access is 'on click'
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnClick);
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kActiveTab);
// Granting site access changes site interaction to 'granted' but site access
// remains 'on click', since it's a one-time grant.
menu_model()->GrantSiteAccess(extension->id());
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kGranted);
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnClick);
}
// Tests that the extensions menu view model correctly revokes site access to an
// extension that requests hosts permissions and access is currently granted.
IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewModelBrowserTest,
RevokeSiteAccess_HostPermission) {
// Add extension that requests host permissions, which are granted by default.
scoped_refptr<const extensions::Extension> extension =
AddExtensionWithHostPermission("Extension", "*://example.com/*");
// Navigate to a site the extension requested access to.
const GURL url =
embedded_test_server()->GetURL("example.com", "/simple.html");
ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), url));
content::WebContents* web_contents = GetActiveWebContents();
// Verify site interaction is 'granted' and site access is 'on site'
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kGranted);
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnSite);
// Revoking site access changes site interaction to 'withheld' and site access
// to 'on click'
menu_model()->RevokeSiteAccess(extension->id());
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kWithheld);
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnClick);
}
// Tests that the extensions menu view model correctly revokes site
// access for an extension with granted activeTab permission.
IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewModelBrowserTest,
RevokeSiteAccess_ActiveTab) {
// Add extension with activeTab permission.
scoped_refptr<const extensions::Extension> extension =
AddActiveTabExtension("Extension");
// Navigate to any (unrestricted) site.
const GURL url =
embedded_test_server()->GetURL("example.com", "/simple.html");
ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), url));
content::WebContents* web_contents = GetActiveWebContents();
// Grant one-time site access to the extension.
extensions::ExtensionActionRunner* action_runner =
extensions::ExtensionActionRunner::GetForWebContents(web_contents);
ASSERT_TRUE(action_runner);
action_runner->GrantTabPermissions({extension.get()});
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kGranted);
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnClick);
// Revoking site access changes site interaction to 'activeTab' and site
// access remains 'on click'.
menu_model()->RevokeSiteAccess(extension->id());
EXPECT_EQ(permissions_helper()->GetSiteInteraction(*extension, web_contents),
SitePermissionsHelper::SiteInteraction::kActiveTab);
EXPECT_EQ(permissions_manager()->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteAccess::kOnClick);
}
// Tests that the extensions menu view model correctly updates the site setting
// for an extension.
IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewModelBrowserTest, UpdateSiteSetting) {
// Add extension that requests host permissions.
scoped_refptr<const extensions::Extension> extension =
AddExtensionWithHostPermission("Extension", "<all_urls>");
// Navigate to a site the extension has site access to.
const GURL url =
embedded_test_server()->GetURL("example.com", "/simple.html");
ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), url));
auto origin = url::Origin::Create(url);
// Verify default initial site setting is "customize by extension".
EXPECT_EQ(permissions_manager()->GetUserSiteSetting(origin),
PermissionsManager::UserSiteSetting::kCustomizeByExtension);
// Update site setting to "block all extensions".
menu_model()->UpdateSiteSetting(
PermissionsManager::UserSiteSetting::kBlockAllExtensions);
EXPECT_EQ(permissions_manager()->GetUserSiteSetting(origin),
PermissionsManager::UserSiteSetting::kBlockAllExtensions);
}