// Copyright 2023 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/chromeos/enterprise/incognito_navigation_throttle.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/navigation_extension_enabler.h"
#include "chrome/browser/sync/test/integration/extensions_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_navigation_observer.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/window_open_disposition.h"
using testing::NotNull;
namespace chromeos {
const char16_t kSimplePageContent[] = u"Basic html test.";
const char kBlockingPageContentSingular[] =
"To use Incognito, your organization requires an extension";
const char kBlockingPageContentPlural[] =
"To use Incognito, your organization requires some extensions";
const char kMissingPageContentSingular[] = "Can’t find extension";
const char kMissingPageContentPlural[] = "Can’t find extensions";
class IncognitoNavigationThrottleBrowserTest
: public extensions::ExtensionBrowserTest {
protected:
IncognitoNavigationThrottleBrowserTest() = default;
~IncognitoNavigationThrottleBrowserTest() override = default;
void SetUpInProcessBrowserTestFixture() override {
policy_provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&policy_provider_);
}
void SetUpOnMainThread() override {
embedded_test_server()->AddDefaultHandlers(
base::FilePath("content/test/data"));
ASSERT_TRUE(embedded_test_server()->Start());
}
const extensions::Extension* InstallExtension() {
extension_ =
LoadExtension(test_data_dir_.AppendASCII("api_test/proxy/system"));
registry_ = extensions::ExtensionRegistry::Get(profile());
EXPECT_TRUE(registry_->enabled_extensions().Contains(extension_->id()));
return extension_.get();
}
void SetMandatoryExtensionsForIncognitoNavigation(
const std::vector<std::string>& extensions) {
base::Value::List values;
for (const auto& ids : extensions) {
values.Append(ids);
}
policy::PolicyMap policies;
policies.Set(policy::key::kMandatoryExtensionsForIncognitoNavigation,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
policy::POLICY_SOURCE_CLOUD, base::Value(std::move(values)),
nullptr);
policy_provider_.UpdateChromePolicy(policies);
}
bool IsPageWithContentLoaded(Browser* browser, const std::u16string& text) {
if (!browser) {
return false;
}
return 1 == ui_test_utils::FindInPage(
browser->tab_strip_model()->GetActiveWebContents(), text,
/*forward=*/false,
/*case_sensitive=*/false,
/*ordinal*/ nullptr,
/*selection_rect=*/nullptr);
}
void NavigateToSimplePage(Browser* browser) {
GURL url(embedded_test_server()->GetURL("/simple_page.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser, url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
}
bool IsSimplePageSown(Browser* browser) {
return IsPageWithContentLoaded(browser, kSimplePageContent);
}
// Checks that an HTML page which informs the user that navigation is blocked
// because `extensions` were not allowed to run in Incognito. The method also
// verifies that the correct singular/plural form of the text is shown,
// depending on the size of `extensions`.
bool IsUnallowedExtensionsBlockingPageSown(
Browser* browser,
const std::vector<std::string>& extensions) {
return VerifyContentExistsInPage(browser,
extensions.size() > 1
? kBlockingPageContentPlural
: kBlockingPageContentSingular,
extensions);
}
// Checks that an HTML page which informs the user that navigation is blocked
// because `extensions` are not installed in the browser. The method also
// verifies that the correct singular/plural form of the text is shown,
// depending on the size of `extensions`.
bool IsMissingExtensionsBlockingPageSown(
Browser* browser,
const std::vector<std::string>& extensions) {
return VerifyContentExistsInPage(browser,
extensions.size() > 1
? kMissingPageContentPlural
: kMissingPageContentSingular,
extensions);
}
Browser* incognito_browser() {
if (!incognito_browser_) {
incognito_browser_ = CreateIncognitoBrowser();
}
return incognito_browser_.get();
}
private:
bool VerifyContentExistsInPage(
Browser* browser,
const std::string& page_heading,
const std::vector<std::string>& extension_names_or_ids) {
if (!IsPageWithContentLoaded(browser, base::UTF8ToUTF16(page_heading))) {
return false;
}
for (const auto& ext : extension_names_or_ids) {
if (!IsPageWithContentLoaded(browser, base::UTF8ToUTF16(ext))) {
return false;
}
}
return true;
}
scoped_refptr<const extensions::Extension> extension_;
raw_ptr<extensions::ExtensionRegistry, AcrossTasksDanglingUntriaged>
registry_;
testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
raw_ptr<Browser, AcrossTasksDanglingUntriaged> incognito_browser_ = nullptr;
};
// Verify that when the `MandatoryExtensionsForIncognitoNavigation` policy is
// set, Incognito mode can only be used if the user allows the configured
// mandatory extensions to run in Incognito.
IN_PROC_BROWSER_TEST_F(IncognitoNavigationThrottleBrowserTest,
PolicySetBlockingExtension) {
const extensions::Extension* extension = InstallExtension();
ASSERT_THAT(extension, NotNull());
SetMandatoryExtensionsForIncognitoNavigation({extension->id()});
// Verify that the primary profile is not affected.
NavigateToSimplePage(browser());
EXPECT_TRUE(IsSimplePageSown(browser()));
// Verify that Incognito mode is blocked.
NavigateToSimplePage(incognito_browser());
EXPECT_TRUE(IsUnallowedExtensionsBlockingPageSown(incognito_browser(),
{extension->name()}));
// Allow the extension to run in Incognito and verify that navigation in
// Incognito mode is allowed.
extensions::util::SetIsIncognitoEnabled(extension->id(), profile(),
/*enabled=*/true);
NavigateToSimplePage(incognito_browser());
EXPECT_TRUE(IsSimplePageSown(incognito_browser()));
// Disallow the extension to run in Incognito and verify that navigaion is
// again blocked.
extensions::util::SetIsIncognitoEnabled(extension->id(), browser()->profile(),
/*enabled=*/false);
NavigateToSimplePage(incognito_browser());
EXPECT_TRUE(IsUnallowedExtensionsBlockingPageSown(incognito_browser(),
{extension->name()}));
}
// Verify that Incognito mode can be used when the policy is not set.
IN_PROC_BROWSER_TEST_F(IncognitoNavigationThrottleBrowserTest, PolicyNotSet) {
NavigateToSimplePage(incognito_browser());
EXPECT_TRUE(IsSimplePageSown(incognito_browser()));
}
// Verify that Incognito mode can be used if the extension configured via the
// `MandatoryExtensionsForIncognitoNavigation` policy is uninstalled.
IN_PROC_BROWSER_TEST_F(IncognitoNavigationThrottleBrowserTest,
ExtensionUninstalled) {
const extensions::Extension* extension = InstallExtension();
SetMandatoryExtensionsForIncognitoNavigation({extension->id()});
// Incognito mode is blocked.
NavigateToSimplePage(incognito_browser());
EXPECT_TRUE(IsUnallowedExtensionsBlockingPageSown(incognito_browser(),
{extension->name()}));
// Incognito mode is allowed.
UninstallExtension(extension->id());
NavigateToSimplePage(incognito_browser());
EXPECT_TRUE(IsMissingExtensionsBlockingPageSown(incognito_browser(),
{extension->id()}));
}
IN_PROC_BROWSER_TEST_F(IncognitoNavigationThrottleBrowserTest,
MissingExtensionsPluralForm) {
std::vector<std::string> extensions = {"aaaa", "bbbbb"};
SetMandatoryExtensionsForIncognitoNavigation(extensions);
NavigateToSimplePage(incognito_browser());
EXPECT_TRUE(
IsMissingExtensionsBlockingPageSown(incognito_browser(), extensions));
}
} // namespace chromeos